]>
Commit | Line | Data |
---|---|---|
bf972790 | 1 | // SPDX-License-Identifier: GPL-2.0 |
759f5f37 VD |
2 | /* |
3 | * Digital I/O driver for Technologic Systems TS-5500 | |
4 | * | |
5 | * Copyright (c) 2012 Savoir-faire Linux Inc. | |
6 | * Vivien Didelot <[email protected]> | |
7 | * | |
8 | * Technologic Systems platforms have pin blocks, exposing several Digital | |
9 | * Input/Output lines (DIO). This driver aims to support single pin blocks. | |
10 | * In that sense, the support is not limited to the TS-5500 blocks. | |
11 | * Actually, the following platforms have DIO support: | |
12 | * | |
13 | * TS-5500: | |
24f71ae5 | 14 | * Documentation: https://docs.embeddedts.com/TS-5500 |
759f5f37 VD |
15 | * Blocks: DIO1, DIO2 and LCD port. |
16 | * | |
17 | * TS-5600: | |
24f71ae5 | 18 | * Documentation: https://docs.embeddedts.com/TS-5600 |
759f5f37 | 19 | * Blocks: LCD port (identical to TS-5500 LCD). |
759f5f37 VD |
20 | */ |
21 | ||
22 | #include <linux/bitops.h> | |
c99601f6 | 23 | #include <linux/gpio/driver.h> |
759f5f37 VD |
24 | #include <linux/io.h> |
25 | #include <linux/module.h> | |
759f5f37 VD |
26 | #include <linux/platform_device.h> |
27 | #include <linux/slab.h> | |
28 | ||
29 | /* List of supported Technologic Systems platforms DIO blocks */ | |
30 | enum ts5500_blocks { TS5500_DIO1, TS5500_DIO2, TS5500_LCD, TS5600_LCD }; | |
31 | ||
32 | struct ts5500_priv { | |
33 | const struct ts5500_dio *pinout; | |
34 | struct gpio_chip gpio_chip; | |
35 | spinlock_t lock; | |
36 | bool strap; | |
37 | u8 hwirq; | |
38 | }; | |
39 | ||
40 | /* | |
41 | * Hex 7D is used to control several blocks (e.g. DIO2 and LCD port). | |
42 | * This flag ensures that the region has been requested by this driver. | |
43 | */ | |
44 | static bool hex7d_reserved; | |
45 | ||
46 | /* | |
47 | * This structure is used to describe capabilities of DIO lines, | |
48 | * such as available directions and connected interrupt (if any). | |
49 | */ | |
50 | struct ts5500_dio { | |
51 | const u8 value_addr; | |
52 | const u8 value_mask; | |
53 | const u8 control_addr; | |
54 | const u8 control_mask; | |
55 | const bool no_input; | |
56 | const bool no_output; | |
57 | const u8 irq; | |
58 | }; | |
59 | ||
60 | #define TS5500_DIO_IN_OUT(vaddr, vbit, caddr, cbit) \ | |
61 | { \ | |
62 | .value_addr = vaddr, \ | |
63 | .value_mask = BIT(vbit), \ | |
64 | .control_addr = caddr, \ | |
65 | .control_mask = BIT(cbit), \ | |
66 | } | |
67 | ||
68 | #define TS5500_DIO_IN(addr, bit) \ | |
69 | { \ | |
70 | .value_addr = addr, \ | |
71 | .value_mask = BIT(bit), \ | |
72 | .no_output = true, \ | |
73 | } | |
74 | ||
75 | #define TS5500_DIO_IN_IRQ(addr, bit, _irq) \ | |
76 | { \ | |
77 | .value_addr = addr, \ | |
78 | .value_mask = BIT(bit), \ | |
79 | .no_output = true, \ | |
80 | .irq = _irq, \ | |
81 | } | |
82 | ||
83 | #define TS5500_DIO_OUT(addr, bit) \ | |
84 | { \ | |
85 | .value_addr = addr, \ | |
86 | .value_mask = BIT(bit), \ | |
87 | .no_input = true, \ | |
88 | } | |
89 | ||
90 | /* | |
91 | * Input/Output DIO lines are programmed in groups of 4. Their values are | |
92 | * available through 4 consecutive bits in a value port, whereas the direction | |
93 | * of these 4 lines is driven by only 1 bit in a control port. | |
94 | */ | |
95 | #define TS5500_DIO_GROUP(vaddr, vbitfrom, caddr, cbit) \ | |
96 | TS5500_DIO_IN_OUT(vaddr, vbitfrom + 0, caddr, cbit), \ | |
97 | TS5500_DIO_IN_OUT(vaddr, vbitfrom + 1, caddr, cbit), \ | |
98 | TS5500_DIO_IN_OUT(vaddr, vbitfrom + 2, caddr, cbit), \ | |
99 | TS5500_DIO_IN_OUT(vaddr, vbitfrom + 3, caddr, cbit) | |
100 | ||
101 | /* | |
102 | * TS-5500 DIO1 block | |
103 | * | |
104 | * value control dir hw | |
105 | * addr bit addr bit in out irq name pin offset | |
106 | * | |
107 | * 0x7b 0 0x7a 0 x x DIO1_0 1 0 | |
108 | * 0x7b 1 0x7a 0 x x DIO1_1 3 1 | |
109 | * 0x7b 2 0x7a 0 x x DIO1_2 5 2 | |
110 | * 0x7b 3 0x7a 0 x x DIO1_3 7 3 | |
111 | * 0x7b 4 0x7a 1 x x DIO1_4 9 4 | |
112 | * 0x7b 5 0x7a 1 x x DIO1_5 11 5 | |
113 | * 0x7b 6 0x7a 1 x x DIO1_6 13 6 | |
114 | * 0x7b 7 0x7a 1 x x DIO1_7 15 7 | |
115 | * 0x7c 0 0x7a 5 x x DIO1_8 4 8 | |
116 | * 0x7c 1 0x7a 5 x x DIO1_9 6 9 | |
117 | * 0x7c 2 0x7a 5 x x DIO1_10 8 10 | |
118 | * 0x7c 3 0x7a 5 x x DIO1_11 10 11 | |
119 | * 0x7c 4 x DIO1_12 12 12 | |
120 | * 0x7c 5 x 7 DIO1_13 14 13 | |
121 | */ | |
122 | static const struct ts5500_dio ts5500_dio1[] = { | |
123 | TS5500_DIO_GROUP(0x7b, 0, 0x7a, 0), | |
124 | TS5500_DIO_GROUP(0x7b, 4, 0x7a, 1), | |
125 | TS5500_DIO_GROUP(0x7c, 0, 0x7a, 5), | |
126 | TS5500_DIO_IN(0x7c, 4), | |
127 | TS5500_DIO_IN_IRQ(0x7c, 5, 7), | |
128 | }; | |
129 | ||
130 | /* | |
131 | * TS-5500 DIO2 block | |
132 | * | |
133 | * value control dir hw | |
134 | * addr bit addr bit in out irq name pin offset | |
135 | * | |
136 | * 0x7e 0 0x7d 0 x x DIO2_0 1 0 | |
137 | * 0x7e 1 0x7d 0 x x DIO2_1 3 1 | |
138 | * 0x7e 2 0x7d 0 x x DIO2_2 5 2 | |
139 | * 0x7e 3 0x7d 0 x x DIO2_3 7 3 | |
140 | * 0x7e 4 0x7d 1 x x DIO2_4 9 4 | |
141 | * 0x7e 5 0x7d 1 x x DIO2_5 11 5 | |
142 | * 0x7e 6 0x7d 1 x x DIO2_6 13 6 | |
143 | * 0x7e 7 0x7d 1 x x DIO2_7 15 7 | |
144 | * 0x7f 0 0x7d 5 x x DIO2_8 4 8 | |
145 | * 0x7f 1 0x7d 5 x x DIO2_9 6 9 | |
146 | * 0x7f 2 0x7d 5 x x DIO2_10 8 10 | |
147 | * 0x7f 3 0x7d 5 x x DIO2_11 10 11 | |
148 | * 0x7f 4 x 6 DIO2_13 14 12 | |
149 | */ | |
150 | static const struct ts5500_dio ts5500_dio2[] = { | |
151 | TS5500_DIO_GROUP(0x7e, 0, 0x7d, 0), | |
152 | TS5500_DIO_GROUP(0x7e, 4, 0x7d, 1), | |
153 | TS5500_DIO_GROUP(0x7f, 0, 0x7d, 5), | |
154 | TS5500_DIO_IN_IRQ(0x7f, 4, 6), | |
155 | }; | |
156 | ||
157 | /* | |
158 | * TS-5500 LCD port used as DIO block | |
159 | * TS-5600 LCD port is identical | |
160 | * | |
161 | * value control dir hw | |
162 | * addr bit addr bit in out irq name pin offset | |
163 | * | |
164 | * 0x72 0 0x7d 2 x x LCD_0 8 0 | |
165 | * 0x72 1 0x7d 2 x x LCD_1 7 1 | |
166 | * 0x72 2 0x7d 2 x x LCD_2 10 2 | |
167 | * 0x72 3 0x7d 2 x x LCD_3 9 3 | |
168 | * 0x72 4 0x7d 3 x x LCD_4 12 4 | |
169 | * 0x72 5 0x7d 3 x x LCD_5 11 5 | |
170 | * 0x72 6 0x7d 3 x x LCD_6 14 6 | |
171 | * 0x72 7 0x7d 3 x x LCD_7 13 7 | |
172 | * 0x73 0 x LCD_EN 5 8 | |
173 | * 0x73 6 x LCD_WR 6 9 | |
174 | * 0x73 7 x 1 LCD_RS 3 10 | |
175 | */ | |
176 | static const struct ts5500_dio ts5500_lcd[] = { | |
177 | TS5500_DIO_GROUP(0x72, 0, 0x7d, 2), | |
178 | TS5500_DIO_GROUP(0x72, 4, 0x7d, 3), | |
179 | TS5500_DIO_OUT(0x73, 0), | |
180 | TS5500_DIO_IN(0x73, 6), | |
181 | TS5500_DIO_IN_IRQ(0x73, 7, 1), | |
182 | }; | |
183 | ||
759f5f37 VD |
184 | static inline void ts5500_set_mask(u8 mask, u8 addr) |
185 | { | |
186 | u8 val = inb(addr); | |
187 | val |= mask; | |
188 | outb(val, addr); | |
189 | } | |
190 | ||
191 | static inline void ts5500_clear_mask(u8 mask, u8 addr) | |
192 | { | |
193 | u8 val = inb(addr); | |
194 | val &= ~mask; | |
195 | outb(val, addr); | |
196 | } | |
197 | ||
198 | static int ts5500_gpio_input(struct gpio_chip *chip, unsigned offset) | |
199 | { | |
11ab89ac | 200 | struct ts5500_priv *priv = gpiochip_get_data(chip); |
759f5f37 VD |
201 | const struct ts5500_dio line = priv->pinout[offset]; |
202 | unsigned long flags; | |
203 | ||
204 | if (line.no_input) | |
205 | return -ENXIO; | |
206 | ||
207 | if (line.no_output) | |
208 | return 0; | |
209 | ||
210 | spin_lock_irqsave(&priv->lock, flags); | |
211 | ts5500_clear_mask(line.control_mask, line.control_addr); | |
212 | spin_unlock_irqrestore(&priv->lock, flags); | |
213 | ||
214 | return 0; | |
215 | } | |
216 | ||
217 | static int ts5500_gpio_get(struct gpio_chip *chip, unsigned offset) | |
218 | { | |
11ab89ac | 219 | struct ts5500_priv *priv = gpiochip_get_data(chip); |
759f5f37 VD |
220 | const struct ts5500_dio line = priv->pinout[offset]; |
221 | ||
222 | return !!(inb(line.value_addr) & line.value_mask); | |
223 | } | |
224 | ||
225 | static int ts5500_gpio_output(struct gpio_chip *chip, unsigned offset, int val) | |
226 | { | |
11ab89ac | 227 | struct ts5500_priv *priv = gpiochip_get_data(chip); |
759f5f37 VD |
228 | const struct ts5500_dio line = priv->pinout[offset]; |
229 | unsigned long flags; | |
230 | ||
231 | if (line.no_output) | |
232 | return -ENXIO; | |
233 | ||
234 | spin_lock_irqsave(&priv->lock, flags); | |
235 | if (!line.no_input) | |
236 | ts5500_set_mask(line.control_mask, line.control_addr); | |
237 | ||
238 | if (val) | |
239 | ts5500_set_mask(line.value_mask, line.value_addr); | |
240 | else | |
241 | ts5500_clear_mask(line.value_mask, line.value_addr); | |
242 | spin_unlock_irqrestore(&priv->lock, flags); | |
243 | ||
244 | return 0; | |
245 | } | |
246 | ||
247 | static void ts5500_gpio_set(struct gpio_chip *chip, unsigned offset, int val) | |
248 | { | |
11ab89ac | 249 | struct ts5500_priv *priv = gpiochip_get_data(chip); |
759f5f37 VD |
250 | const struct ts5500_dio line = priv->pinout[offset]; |
251 | unsigned long flags; | |
252 | ||
253 | spin_lock_irqsave(&priv->lock, flags); | |
254 | if (val) | |
255 | ts5500_set_mask(line.value_mask, line.value_addr); | |
256 | else | |
257 | ts5500_clear_mask(line.value_mask, line.value_addr); | |
258 | spin_unlock_irqrestore(&priv->lock, flags); | |
259 | } | |
260 | ||
261 | static int ts5500_gpio_to_irq(struct gpio_chip *chip, unsigned offset) | |
262 | { | |
11ab89ac | 263 | struct ts5500_priv *priv = gpiochip_get_data(chip); |
759f5f37 VD |
264 | const struct ts5500_dio *block = priv->pinout; |
265 | const struct ts5500_dio line = block[offset]; | |
266 | ||
267 | /* Only one pin is connected to an interrupt */ | |
268 | if (line.irq) | |
269 | return line.irq; | |
270 | ||
271 | /* As this pin is input-only, we may strap it to another in/out pin */ | |
272 | if (priv->strap) | |
273 | return priv->hwirq; | |
274 | ||
275 | return -ENXIO; | |
276 | } | |
277 | ||
278 | static int ts5500_enable_irq(struct ts5500_priv *priv) | |
279 | { | |
280 | int ret = 0; | |
281 | unsigned long flags; | |
282 | ||
283 | spin_lock_irqsave(&priv->lock, flags); | |
284 | if (priv->hwirq == 7) | |
285 | ts5500_set_mask(BIT(7), 0x7a); /* DIO1_13 on IRQ7 */ | |
286 | else if (priv->hwirq == 6) | |
287 | ts5500_set_mask(BIT(7), 0x7d); /* DIO2_13 on IRQ6 */ | |
288 | else if (priv->hwirq == 1) | |
289 | ts5500_set_mask(BIT(6), 0x7d); /* LCD_RS on IRQ1 */ | |
290 | else | |
291 | ret = -EINVAL; | |
292 | spin_unlock_irqrestore(&priv->lock, flags); | |
293 | ||
294 | return ret; | |
295 | } | |
296 | ||
297 | static void ts5500_disable_irq(struct ts5500_priv *priv) | |
298 | { | |
299 | unsigned long flags; | |
300 | ||
301 | spin_lock_irqsave(&priv->lock, flags); | |
302 | if (priv->hwirq == 7) | |
303 | ts5500_clear_mask(BIT(7), 0x7a); /* DIO1_13 on IRQ7 */ | |
304 | else if (priv->hwirq == 6) | |
305 | ts5500_clear_mask(BIT(7), 0x7d); /* DIO2_13 on IRQ6 */ | |
306 | else if (priv->hwirq == 1) | |
307 | ts5500_clear_mask(BIT(6), 0x7d); /* LCD_RS on IRQ1 */ | |
308 | else | |
58383c78 LW |
309 | dev_err(priv->gpio_chip.parent, "invalid hwirq %d\n", |
310 | priv->hwirq); | |
759f5f37 VD |
311 | spin_unlock_irqrestore(&priv->lock, flags); |
312 | } | |
313 | ||
0fe763c5 | 314 | static int ts5500_dio_probe(struct platform_device *pdev) |
759f5f37 VD |
315 | { |
316 | enum ts5500_blocks block = platform_get_device_id(pdev)->driver_data; | |
759f5f37 VD |
317 | struct device *dev = &pdev->dev; |
318 | const char *name = dev_name(dev); | |
319 | struct ts5500_priv *priv; | |
759f5f37 VD |
320 | unsigned long flags; |
321 | int ret; | |
322 | ||
6408693f LP |
323 | ret = platform_get_irq(pdev, 0); |
324 | if (ret < 0) | |
325 | return ret; | |
759f5f37 VD |
326 | |
327 | priv = devm_kzalloc(dev, sizeof(struct ts5500_priv), GFP_KERNEL); | |
328 | if (!priv) | |
329 | return -ENOMEM; | |
330 | ||
331 | platform_set_drvdata(pdev, priv); | |
6408693f | 332 | priv->hwirq = ret; |
759f5f37 VD |
333 | spin_lock_init(&priv->lock); |
334 | ||
335 | priv->gpio_chip.owner = THIS_MODULE; | |
336 | priv->gpio_chip.label = name; | |
58383c78 | 337 | priv->gpio_chip.parent = dev; |
759f5f37 VD |
338 | priv->gpio_chip.direction_input = ts5500_gpio_input; |
339 | priv->gpio_chip.direction_output = ts5500_gpio_output; | |
340 | priv->gpio_chip.get = ts5500_gpio_get; | |
341 | priv->gpio_chip.set = ts5500_gpio_set; | |
342 | priv->gpio_chip.to_irq = ts5500_gpio_to_irq; | |
343 | priv->gpio_chip.base = -1; | |
759f5f37 VD |
344 | |
345 | switch (block) { | |
346 | case TS5500_DIO1: | |
347 | priv->pinout = ts5500_dio1; | |
348 | priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_dio1); | |
349 | ||
350 | if (!devm_request_region(dev, 0x7a, 3, name)) { | |
351 | dev_err(dev, "failed to request %s ports\n", name); | |
352 | return -EBUSY; | |
353 | } | |
354 | break; | |
355 | case TS5500_DIO2: | |
356 | priv->pinout = ts5500_dio2; | |
357 | priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_dio2); | |
358 | ||
359 | if (!devm_request_region(dev, 0x7e, 2, name)) { | |
360 | dev_err(dev, "failed to request %s ports\n", name); | |
361 | return -EBUSY; | |
362 | } | |
363 | ||
364 | if (hex7d_reserved) | |
365 | break; | |
366 | ||
367 | if (!devm_request_region(dev, 0x7d, 1, name)) { | |
368 | dev_err(dev, "failed to request %s 7D\n", name); | |
369 | return -EBUSY; | |
370 | } | |
371 | ||
372 | hex7d_reserved = true; | |
373 | break; | |
374 | case TS5500_LCD: | |
375 | case TS5600_LCD: | |
376 | priv->pinout = ts5500_lcd; | |
377 | priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_lcd); | |
378 | ||
379 | if (!devm_request_region(dev, 0x72, 2, name)) { | |
380 | dev_err(dev, "failed to request %s ports\n", name); | |
381 | return -EBUSY; | |
382 | } | |
383 | ||
384 | if (!hex7d_reserved) { | |
385 | if (!devm_request_region(dev, 0x7d, 1, name)) { | |
386 | dev_err(dev, "failed to request %s 7D\n", name); | |
387 | return -EBUSY; | |
388 | } | |
389 | ||
390 | hex7d_reserved = true; | |
391 | } | |
392 | ||
393 | /* Ensure usage of LCD port as DIO */ | |
394 | spin_lock_irqsave(&priv->lock, flags); | |
395 | ts5500_clear_mask(BIT(4), 0x7d); | |
396 | spin_unlock_irqrestore(&priv->lock, flags); | |
397 | break; | |
398 | } | |
399 | ||
973eff01 | 400 | ret = devm_gpiochip_add_data(dev, &priv->gpio_chip, priv); |
759f5f37 VD |
401 | if (ret) { |
402 | dev_err(dev, "failed to register the gpio chip\n"); | |
403 | return ret; | |
404 | } | |
405 | ||
406 | ret = ts5500_enable_irq(priv); | |
407 | if (ret) { | |
408 | dev_err(dev, "invalid interrupt %d\n", priv->hwirq); | |
973eff01 | 409 | return ret; |
759f5f37 VD |
410 | } |
411 | ||
412 | return 0; | |
759f5f37 VD |
413 | } |
414 | ||
a2e09217 | 415 | static void ts5500_dio_remove(struct platform_device *pdev) |
759f5f37 VD |
416 | { |
417 | struct ts5500_priv *priv = platform_get_drvdata(pdev); | |
418 | ||
419 | ts5500_disable_irq(priv); | |
759f5f37 VD |
420 | } |
421 | ||
f4f79d40 | 422 | static const struct platform_device_id ts5500_dio_ids[] = { |
759f5f37 VD |
423 | { "ts5500-dio1", TS5500_DIO1 }, |
424 | { "ts5500-dio2", TS5500_DIO2 }, | |
425 | { "ts5500-dio-lcd", TS5500_LCD }, | |
426 | { "ts5600-dio-lcd", TS5600_LCD }, | |
427 | { } | |
428 | }; | |
429 | MODULE_DEVICE_TABLE(platform, ts5500_dio_ids); | |
430 | ||
431 | static struct platform_driver ts5500_dio_driver = { | |
432 | .driver = { | |
433 | .name = "ts5500-dio", | |
759f5f37 VD |
434 | }, |
435 | .probe = ts5500_dio_probe, | |
678eefc1 | 436 | .remove = ts5500_dio_remove, |
759f5f37 VD |
437 | .id_table = ts5500_dio_ids, |
438 | }; | |
439 | ||
440 | module_platform_driver(ts5500_dio_driver); | |
441 | ||
442 | MODULE_LICENSE("GPL"); | |
443 | MODULE_AUTHOR("Savoir-faire Linux Inc. <[email protected]>"); | |
444 | MODULE_DESCRIPTION("Technologic Systems TS-5500 Digital I/O driver"); |