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