summaryrefslogtreecommitdiffstats
path: root/drivers/gpio/gpio-104-idi-48.c
diff options
context:
space:
mode:
authorWilliam Breathitt Gray <vilhelm.gray@gmail.com>2015-11-23 12:54:50 -0500
committerLinus Walleij <linus.walleij@linaro.org>2015-12-01 03:56:36 -0500
commit6ddcf9b486f134f1a1544c82b36b0876ef2f33e6 (patch)
tree51840f748c49bd5613f8841766c4bbc877b57ec1 /drivers/gpio/gpio-104-idi-48.c
parentab128afce4ea8a496fc42553215f6635a14f05c3 (diff)
gpio: Add GPIO support for the ACCES 104-IDI-48
The ACCES 104-IDI-48 family of PC/104 utility boards feature 48 individually optically isolated digital inputs. Enabled inputs feature change-of-state detection capability; if change-of-state detection is enabled, an interrupt is fired off if a change of input level (low-to-high or high-to-low) is detected. Change-of-state IRQs are enabled/disabled on 8-bit boundaries, for a total of six boundaries. This driver provides GPIO and IRQ support for these 48 channels of digital input. The base port address for the device may be configured via the idi_48_base module parameter. The interrupt line number for the device may be configured via the idi_48_irq module parameter. Signed-off-by: William Breathitt Gray <vilhelm.gray@gmail.com> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Diffstat (limited to 'drivers/gpio/gpio-104-idi-48.c')
-rw-r--r--drivers/gpio/gpio-104-idi-48.c349
1 files changed, 349 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-104-idi-48.c b/drivers/gpio/gpio-104-idi-48.c
new file mode 100644
index 000000000000..b5c693409a58
--- /dev/null
+++ b/drivers/gpio/gpio-104-idi-48.c
@@ -0,0 +1,349 @@
1/*
2 * GPIO driver for the ACCES 104-IDI-48 family
3 * Copyright (C) 2015 William Breathitt Gray
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License, version 2, as
7 * published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 */
14#include <linux/bitops.h>
15#include <linux/device.h>
16#include <linux/errno.h>
17#include <linux/gpio/driver.h>
18#include <linux/io.h>
19#include <linux/ioport.h>
20#include <linux/interrupt.h>
21#include <linux/irqdesc.h>
22#include <linux/kernel.h>
23#include <linux/module.h>
24#include <linux/moduleparam.h>
25#include <linux/platform_device.h>
26#include <linux/spinlock.h>
27
28static unsigned idi_48_base;
29module_param(idi_48_base, uint, 0);
30MODULE_PARM_DESC(idi_48_base, "ACCES 104-IDI-48 base address");
31static unsigned idi_48_irq;
32module_param(idi_48_irq, uint, 0);
33MODULE_PARM_DESC(idi_48_irq, "ACCES 104-IDI-48 interrupt line number");
34
35/**
36 * struct idi_48_gpio - GPIO device private data structure
37 * @chip: instance of the gpio_chip
38 * @lock: synchronization lock to prevent I/O race conditions
39 * @irq_mask: input bits affected by interrupts
40 * @base: base port address of the GPIO device
41 * @extent: extent of port address region of the GPIO device
42 * @irq: Interrupt line number
43 * @cos_enb: Change-Of-State IRQ enable boundaries mask
44 */
45struct idi_48_gpio {
46 struct gpio_chip chip;
47 spinlock_t lock;
48 unsigned char irq_mask[6];
49 unsigned base;
50 unsigned extent;
51 unsigned irq;
52 unsigned char cos_enb;
53};
54
55static int idi_48_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
56{
57 return 1;
58}
59
60static int idi_48_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
61{
62 return 0;
63}
64
65static struct idi_48_gpio *to_idi48gpio(struct gpio_chip *gc)
66{
67 return container_of(gc, struct idi_48_gpio, chip);
68}
69
70static int idi_48_gpio_get(struct gpio_chip *chip, unsigned offset)
71{
72 struct idi_48_gpio *const idi48gpio = to_idi48gpio(chip);
73 unsigned i;
74 const unsigned register_offset[6] = { 0, 1, 2, 4, 5, 6 };
75 unsigned base_offset;
76 unsigned mask;
77
78 for (i = 0; i < 48; i += 8)
79 if (offset < i + 8) {
80 base_offset = register_offset[i / 8];
81 mask = BIT(offset - i);
82
83 return !!(inb(idi48gpio->base + base_offset) & mask);
84 }
85
86 /* The following line should never execute since offset < 48 */
87 return 0;
88}
89
90static void idi_48_irq_ack(struct irq_data *data)
91{
92 struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
93 struct idi_48_gpio *const idi48gpio = to_idi48gpio(chip);
94 unsigned long flags;
95
96 spin_lock_irqsave(&idi48gpio->lock, flags);
97
98 inb(idi48gpio->base + 7);
99
100 spin_unlock_irqrestore(&idi48gpio->lock, flags);
101}
102
103static void idi_48_irq_mask(struct irq_data *data)
104{
105 struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
106 struct idi_48_gpio *const idi48gpio = to_idi48gpio(chip);
107 const unsigned offset = irqd_to_hwirq(data);
108 unsigned i;
109 unsigned mask;
110 unsigned boundary;
111 unsigned long flags;
112
113 for (i = 0; i < 48; i += 8)
114 if (offset < i + 8) {
115 mask = BIT(offset - i);
116 boundary = i / 8;
117
118 idi48gpio->irq_mask[boundary] &= ~mask;
119
120 if (!idi48gpio->irq_mask[boundary]) {
121 idi48gpio->cos_enb &= ~BIT(boundary);
122
123 spin_lock_irqsave(&idi48gpio->lock, flags);
124
125 outb(idi48gpio->cos_enb, idi48gpio->base + 7);
126
127 spin_unlock_irqrestore(&idi48gpio->lock, flags);
128 }
129
130 return;
131 }
132}
133
134static void idi_48_irq_unmask(struct irq_data *data)
135{
136 struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
137 struct idi_48_gpio *const idi48gpio = to_idi48gpio(chip);
138 const unsigned offset = irqd_to_hwirq(data);
139 unsigned i;
140 unsigned mask;
141 unsigned boundary;
142 unsigned prev_irq_mask;
143 unsigned long flags;
144
145 for (i = 0; i < 48; i += 8)
146 if (offset < i + 8) {
147 mask = BIT(offset - i);
148 boundary = i / 8;
149 prev_irq_mask = idi48gpio->irq_mask[boundary];
150
151 idi48gpio->irq_mask[boundary] |= mask;
152
153 if (!prev_irq_mask) {
154 idi48gpio->cos_enb |= BIT(boundary);
155
156 spin_lock_irqsave(&idi48gpio->lock, flags);
157
158 outb(idi48gpio->cos_enb, idi48gpio->base + 7);
159
160 spin_unlock_irqrestore(&idi48gpio->lock, flags);
161 }
162
163 return;
164 }
165}
166
167static int idi_48_irq_set_type(struct irq_data *data, unsigned flow_type)
168{
169 /* The only valid irq types are none and both-edges */
170 if (flow_type != IRQ_TYPE_NONE &&
171 (flow_type & IRQ_TYPE_EDGE_BOTH) != IRQ_TYPE_EDGE_BOTH)
172 return -EINVAL;
173
174 return 0;
175}
176
177static struct irq_chip idi_48_irqchip = {
178 .name = "104-idi-48",
179 .irq_ack = idi_48_irq_ack,
180 .irq_mask = idi_48_irq_mask,
181 .irq_unmask = idi_48_irq_unmask,
182 .irq_set_type = idi_48_irq_set_type
183};
184
185static irqreturn_t idi_48_irq_handler(int irq, void *dev_id)
186{
187 struct idi_48_gpio *const idi48gpio = dev_id;
188 unsigned long cos_status;
189 unsigned long boundary;
190 unsigned long irq_mask;
191 unsigned long bit_num;
192 unsigned long gpio;
193 struct gpio_chip *const chip = &idi48gpio->chip;
194
195 spin_lock(&idi48gpio->lock);
196
197 cos_status = inb(idi48gpio->base + 7);
198
199 spin_unlock(&idi48gpio->lock);
200
201 /* IRQ Status (bit 6) is active low (0 = IRQ generated by device) */
202 if (cos_status & BIT(6))
203 return IRQ_NONE;
204
205 /* Bit 0-5 indicate which Change-Of-State boundary triggered the IRQ */
206 cos_status &= 0x3F;
207
208 for_each_set_bit(boundary, &cos_status, 6) {
209 irq_mask = idi48gpio->irq_mask[boundary];
210
211 for_each_set_bit(bit_num, &irq_mask, 8) {
212 gpio = bit_num + boundary * 8;
213
214 generic_handle_irq(irq_find_mapping(chip->irqdomain,
215 gpio));
216 }
217 }
218
219 return IRQ_HANDLED;
220}
221
222static int __init idi_48_probe(struct platform_device *pdev)
223{
224 struct device *dev = &pdev->dev;
225 struct idi_48_gpio *idi48gpio;
226 const unsigned base = idi_48_base;
227 const unsigned extent = 8;
228 const char *const name = dev_name(dev);
229 int err;
230 const unsigned irq = idi_48_irq;
231
232 idi48gpio = devm_kzalloc(dev, sizeof(*idi48gpio), GFP_KERNEL);
233 if (!idi48gpio)
234 return -ENOMEM;
235
236 if (!request_region(base, extent, name)) {
237 dev_err(dev, "Unable to lock %s port addresses (0x%X-0x%X)\n",
238 name, base, base + extent);
239 err = -EBUSY;
240 goto err_lock_io_port;
241 }
242
243 idi48gpio->chip.label = name;
244 idi48gpio->chip.parent = dev;
245 idi48gpio->chip.owner = THIS_MODULE;
246 idi48gpio->chip.base = -1;
247 idi48gpio->chip.ngpio = 48;
248 idi48gpio->chip.get_direction = idi_48_gpio_get_direction;
249 idi48gpio->chip.direction_input = idi_48_gpio_direction_input;
250 idi48gpio->chip.get = idi_48_gpio_get;
251 idi48gpio->base = base;
252 idi48gpio->extent = extent;
253 idi48gpio->irq = irq;
254
255 spin_lock_init(&idi48gpio->lock);
256
257 dev_set_drvdata(dev, idi48gpio);
258
259 err = gpiochip_add(&idi48gpio->chip);
260 if (err) {
261 dev_err(dev, "GPIO registering failed (%d)\n", err);
262 goto err_gpio_register;
263 }
264
265 /* Disable IRQ by default */
266 outb(0, base + 7);
267 inb(base + 7);
268
269 err = gpiochip_irqchip_add(&idi48gpio->chip, &idi_48_irqchip, 0,
270 handle_edge_irq, IRQ_TYPE_NONE);
271 if (err) {
272 dev_err(dev, "Could not add irqchip (%d)\n", err);
273 goto err_gpiochip_irqchip_add;
274 }
275
276 err = request_irq(irq, idi_48_irq_handler, 0, name, idi48gpio);
277 if (err) {
278 dev_err(dev, "IRQ handler registering failed (%d)\n", err);
279 goto err_request_irq;
280 }
281
282 return 0;
283
284err_request_irq:
285err_gpiochip_irqchip_add:
286 gpiochip_remove(&idi48gpio->chip);
287err_gpio_register:
288 release_region(base, extent);
289err_lock_io_port:
290 return err;
291}
292
293static int idi_48_remove(struct platform_device *pdev)
294{
295 struct idi_48_gpio *const idi48gpio = platform_get_drvdata(pdev);
296
297 free_irq(idi48gpio->irq, idi48gpio);
298 gpiochip_remove(&idi48gpio->chip);
299 release_region(idi48gpio->base, idi48gpio->extent);
300
301 return 0;
302}
303
304static struct platform_device *idi_48_device;
305
306static struct platform_driver idi_48_driver = {
307 .driver = {
308 .name = "104-idi-48"
309 },
310 .remove = idi_48_remove
311};
312
313static void __exit idi_48_exit(void)
314{
315 platform_device_unregister(idi_48_device);
316 platform_driver_unregister(&idi_48_driver);
317}
318
319static int __init idi_48_init(void)
320{
321 int err;
322
323 idi_48_device = platform_device_alloc(idi_48_driver.driver.name, -1);
324 if (!idi_48_device)
325 return -ENOMEM;
326
327 err = platform_device_add(idi_48_device);
328 if (err)
329 goto err_platform_device;
330
331 err = platform_driver_probe(&idi_48_driver, idi_48_probe);
332 if (err)
333 goto err_platform_driver;
334
335 return 0;
336
337err_platform_driver:
338 platform_device_del(idi_48_device);
339err_platform_device:
340 platform_device_put(idi_48_device);
341 return err;
342}
343
344module_init(idi_48_init);
345module_exit(idi_48_exit);
346
347MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
348MODULE_DESCRIPTION("ACCES 104-IDI-48 GPIO driver");
349MODULE_LICENSE("GPL");