1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Altera University Program PS2 controller driver |
4 | * |
5 | * Copyright (C) 2008 Thomas Chou <thomas@wytron.com.tw> |
6 | * |
7 | * Based on sa1111ps2.c, which is: |
8 | * Copyright (C) 2002 Russell King |
9 | */ |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/input.h> |
13 | #include <linux/serio.h> |
14 | #include <linux/interrupt.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/io.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/of.h> |
19 | |
20 | #define DRV_NAME "altera_ps2" |
21 | |
22 | struct ps2if { |
23 | struct serio *io; |
24 | void __iomem *base; |
25 | }; |
26 | |
27 | /* |
28 | * Read all bytes waiting in the PS2 port. There should be |
29 | * at the most one, but we loop for safety. |
30 | */ |
31 | static irqreturn_t altera_ps2_rxint(int irq, void *dev_id) |
32 | { |
33 | struct ps2if *ps2if = dev_id; |
34 | unsigned int status; |
35 | irqreturn_t handled = IRQ_NONE; |
36 | |
37 | while ((status = readl(addr: ps2if->base)) & 0xffff0000) { |
38 | serio_interrupt(serio: ps2if->io, data: status & 0xff, flags: 0); |
39 | handled = IRQ_HANDLED; |
40 | } |
41 | |
42 | return handled; |
43 | } |
44 | |
45 | /* |
46 | * Write a byte to the PS2 port. |
47 | */ |
48 | static int altera_ps2_write(struct serio *io, unsigned char val) |
49 | { |
50 | struct ps2if *ps2if = io->port_data; |
51 | |
52 | writel(val, addr: ps2if->base); |
53 | return 0; |
54 | } |
55 | |
56 | static int altera_ps2_open(struct serio *io) |
57 | { |
58 | struct ps2if *ps2if = io->port_data; |
59 | |
60 | /* clear fifo */ |
61 | while (readl(addr: ps2if->base) & 0xffff0000) |
62 | /* empty */; |
63 | |
64 | writel(val: 1, addr: ps2if->base + 4); /* enable rx irq */ |
65 | return 0; |
66 | } |
67 | |
68 | static void altera_ps2_close(struct serio *io) |
69 | { |
70 | struct ps2if *ps2if = io->port_data; |
71 | |
72 | writel(val: 0, addr: ps2if->base + 4); /* disable rx irq */ |
73 | } |
74 | |
75 | /* |
76 | * Add one device to this driver. |
77 | */ |
78 | static int altera_ps2_probe(struct platform_device *pdev) |
79 | { |
80 | struct ps2if *ps2if; |
81 | struct serio *serio; |
82 | int error, irq; |
83 | |
84 | ps2if = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct ps2if), GFP_KERNEL); |
85 | if (!ps2if) |
86 | return -ENOMEM; |
87 | |
88 | ps2if->base = devm_platform_get_and_ioremap_resource(pdev, index: 0, NULL); |
89 | if (IS_ERR(ptr: ps2if->base)) |
90 | return PTR_ERR(ptr: ps2if->base); |
91 | |
92 | irq = platform_get_irq(pdev, 0); |
93 | if (irq < 0) |
94 | return -ENXIO; |
95 | |
96 | error = devm_request_irq(dev: &pdev->dev, irq, handler: altera_ps2_rxint, irqflags: 0, |
97 | devname: pdev->name, dev_id: ps2if); |
98 | if (error) { |
99 | dev_err(&pdev->dev, "could not request IRQ %d\n" , irq); |
100 | return error; |
101 | } |
102 | |
103 | serio = kzalloc(size: sizeof(struct serio), GFP_KERNEL); |
104 | if (!serio) |
105 | return -ENOMEM; |
106 | |
107 | serio->id.type = SERIO_8042; |
108 | serio->write = altera_ps2_write; |
109 | serio->open = altera_ps2_open; |
110 | serio->close = altera_ps2_close; |
111 | strscpy(p: serio->name, q: dev_name(dev: &pdev->dev), size: sizeof(serio->name)); |
112 | strscpy(p: serio->phys, q: dev_name(dev: &pdev->dev), size: sizeof(serio->phys)); |
113 | serio->port_data = ps2if; |
114 | serio->dev.parent = &pdev->dev; |
115 | ps2if->io = serio; |
116 | |
117 | dev_info(&pdev->dev, "base %p, irq %d\n" , ps2if->base, irq); |
118 | |
119 | serio_register_port(ps2if->io); |
120 | platform_set_drvdata(pdev, data: ps2if); |
121 | |
122 | return 0; |
123 | } |
124 | |
125 | /* |
126 | * Remove one device from this driver. |
127 | */ |
128 | static int altera_ps2_remove(struct platform_device *pdev) |
129 | { |
130 | struct ps2if *ps2if = platform_get_drvdata(pdev); |
131 | |
132 | serio_unregister_port(serio: ps2if->io); |
133 | |
134 | return 0; |
135 | } |
136 | |
137 | #ifdef CONFIG_OF |
138 | static const struct of_device_id altera_ps2_match[] = { |
139 | { .compatible = "ALTR,ps2-1.0" , }, |
140 | { .compatible = "altr,ps2-1.0" , }, |
141 | {}, |
142 | }; |
143 | MODULE_DEVICE_TABLE(of, altera_ps2_match); |
144 | #endif /* CONFIG_OF */ |
145 | |
146 | /* |
147 | * Our device driver structure |
148 | */ |
149 | static struct platform_driver altera_ps2_driver = { |
150 | .probe = altera_ps2_probe, |
151 | .remove = altera_ps2_remove, |
152 | .driver = { |
153 | .name = DRV_NAME, |
154 | .of_match_table = of_match_ptr(altera_ps2_match), |
155 | }, |
156 | }; |
157 | module_platform_driver(altera_ps2_driver); |
158 | |
159 | MODULE_DESCRIPTION("Altera University Program PS2 controller driver" ); |
160 | MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>" ); |
161 | MODULE_LICENSE("GPL" ); |
162 | MODULE_ALIAS("platform:" DRV_NAME); |
163 | |