1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Amstrad E3 (Delta) keyboard port driver |
4 | * |
5 | * Copyright (c) 2006 Matt Callow |
6 | * Copyright (c) 2010 Janusz Krzysztofik |
7 | * |
8 | * Thanks to Cliff Lawson for his help |
9 | * |
10 | * The Amstrad Delta keyboard (aka mailboard) uses normal PC-AT style serial |
11 | * transmission. The keyboard port is formed of two GPIO lines, for clock |
12 | * and data. Due to strict timing requirements of the interface, |
13 | * the serial data stream is read and processed by a FIQ handler. |
14 | * The resulting words are fetched by this driver from a circular buffer. |
15 | * |
16 | * Standard AT keyboard driver (atkbd) is used for handling the keyboard data. |
17 | * However, when used with the E3 mailboard that producecs non-standard |
18 | * scancodes, a custom key table must be prepared and loaded from userspace. |
19 | */ |
20 | #include <linux/irq.h> |
21 | #include <linux/platform_data/ams-delta-fiq.h> |
22 | #include <linux/platform_device.h> |
23 | #include <linux/regulator/consumer.h> |
24 | #include <linux/serio.h> |
25 | #include <linux/slab.h> |
26 | #include <linux/module.h> |
27 | |
28 | #define DRIVER_NAME "ams-delta-serio" |
29 | |
30 | MODULE_AUTHOR("Matt Callow" ); |
31 | MODULE_DESCRIPTION("AMS Delta (E3) keyboard port driver" ); |
32 | MODULE_LICENSE("GPL" ); |
33 | |
34 | struct ams_delta_serio { |
35 | struct serio *serio; |
36 | struct regulator *vcc; |
37 | unsigned int *fiq_buffer; |
38 | }; |
39 | |
40 | static int check_data(struct serio *serio, int data) |
41 | { |
42 | int i, parity = 0; |
43 | |
44 | /* check valid stop bit */ |
45 | if (!(data & 0x400)) { |
46 | dev_warn(&serio->dev, "invalid stop bit, data=0x%X\n" , data); |
47 | return SERIO_FRAME; |
48 | } |
49 | /* calculate the parity */ |
50 | for (i = 1; i < 10; i++) { |
51 | if (data & (1 << i)) |
52 | parity++; |
53 | } |
54 | /* it should be odd */ |
55 | if (!(parity & 0x01)) { |
56 | dev_warn(&serio->dev, |
57 | "parity check failed, data=0x%X parity=0x%X\n" , data, |
58 | parity); |
59 | return SERIO_PARITY; |
60 | } |
61 | return 0; |
62 | } |
63 | |
64 | static irqreturn_t ams_delta_serio_interrupt(int irq, void *dev_id) |
65 | { |
66 | struct ams_delta_serio *priv = dev_id; |
67 | int *circ_buff = &priv->fiq_buffer[FIQ_CIRC_BUFF]; |
68 | int data, dfl; |
69 | u8 scancode; |
70 | |
71 | priv->fiq_buffer[FIQ_IRQ_PEND] = 0; |
72 | |
73 | /* |
74 | * Read data from the circular buffer, check it |
75 | * and then pass it on the serio |
76 | */ |
77 | while (priv->fiq_buffer[FIQ_KEYS_CNT] > 0) { |
78 | |
79 | data = circ_buff[priv->fiq_buffer[FIQ_HEAD_OFFSET]++]; |
80 | priv->fiq_buffer[FIQ_KEYS_CNT]--; |
81 | if (priv->fiq_buffer[FIQ_HEAD_OFFSET] == |
82 | priv->fiq_buffer[FIQ_BUF_LEN]) |
83 | priv->fiq_buffer[FIQ_HEAD_OFFSET] = 0; |
84 | |
85 | dfl = check_data(serio: priv->serio, data); |
86 | scancode = (u8) (data >> 1) & 0xFF; |
87 | serio_interrupt(serio: priv->serio, data: scancode, flags: dfl); |
88 | } |
89 | return IRQ_HANDLED; |
90 | } |
91 | |
92 | static int ams_delta_serio_open(struct serio *serio) |
93 | { |
94 | struct ams_delta_serio *priv = serio->port_data; |
95 | |
96 | /* enable keyboard */ |
97 | return regulator_enable(regulator: priv->vcc); |
98 | } |
99 | |
100 | static void ams_delta_serio_close(struct serio *serio) |
101 | { |
102 | struct ams_delta_serio *priv = serio->port_data; |
103 | |
104 | /* disable keyboard */ |
105 | regulator_disable(regulator: priv->vcc); |
106 | } |
107 | |
108 | static int ams_delta_serio_init(struct platform_device *pdev) |
109 | { |
110 | struct ams_delta_serio *priv; |
111 | struct serio *serio; |
112 | int irq, err; |
113 | |
114 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
115 | if (!priv) |
116 | return -ENOMEM; |
117 | |
118 | priv->fiq_buffer = pdev->dev.platform_data; |
119 | if (!priv->fiq_buffer) |
120 | return -EINVAL; |
121 | |
122 | priv->vcc = devm_regulator_get(dev: &pdev->dev, id: "vcc" ); |
123 | if (IS_ERR(ptr: priv->vcc)) { |
124 | err = PTR_ERR(ptr: priv->vcc); |
125 | dev_err(&pdev->dev, "regulator request failed (%d)\n" , err); |
126 | /* |
127 | * When running on a non-dt platform and requested regulator |
128 | * is not available, devm_regulator_get() never returns |
129 | * -EPROBE_DEFER as it is not able to justify if the regulator |
130 | * may still appear later. On the other hand, the board can |
131 | * still set full constriants flag at late_initcall in order |
132 | * to instruct devm_regulator_get() to returnn a dummy one |
133 | * if sufficient. Hence, if we get -ENODEV here, let's convert |
134 | * it to -EPROBE_DEFER and wait for the board to decide or |
135 | * let Deferred Probe infrastructure handle this error. |
136 | */ |
137 | if (err == -ENODEV) |
138 | err = -EPROBE_DEFER; |
139 | return err; |
140 | } |
141 | |
142 | irq = platform_get_irq(pdev, 0); |
143 | if (irq < 0) |
144 | return -ENXIO; |
145 | |
146 | err = devm_request_irq(dev: &pdev->dev, irq, handler: ams_delta_serio_interrupt, |
147 | irqflags: IRQ_TYPE_EDGE_RISING, DRIVER_NAME, dev_id: priv); |
148 | if (err < 0) { |
149 | dev_err(&pdev->dev, "IRQ request failed (%d)\n" , err); |
150 | return err; |
151 | } |
152 | |
153 | serio = kzalloc(size: sizeof(*serio), GFP_KERNEL); |
154 | if (!serio) |
155 | return -ENOMEM; |
156 | |
157 | priv->serio = serio; |
158 | |
159 | serio->id.type = SERIO_8042; |
160 | serio->open = ams_delta_serio_open; |
161 | serio->close = ams_delta_serio_close; |
162 | strscpy(p: serio->name, q: "AMS DELTA keyboard adapter" , size: sizeof(serio->name)); |
163 | strscpy(p: serio->phys, q: dev_name(dev: &pdev->dev), size: sizeof(serio->phys)); |
164 | serio->dev.parent = &pdev->dev; |
165 | serio->port_data = priv; |
166 | |
167 | serio_register_port(serio); |
168 | |
169 | platform_set_drvdata(pdev, data: priv); |
170 | |
171 | dev_info(&serio->dev, "%s\n" , serio->name); |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static int ams_delta_serio_exit(struct platform_device *pdev) |
177 | { |
178 | struct ams_delta_serio *priv = platform_get_drvdata(pdev); |
179 | |
180 | serio_unregister_port(serio: priv->serio); |
181 | |
182 | return 0; |
183 | } |
184 | |
185 | static struct platform_driver ams_delta_serio_driver = { |
186 | .probe = ams_delta_serio_init, |
187 | .remove = ams_delta_serio_exit, |
188 | .driver = { |
189 | .name = DRIVER_NAME |
190 | }, |
191 | }; |
192 | module_platform_driver(ams_delta_serio_driver); |
193 | |