diff options
author | William Breathitt Gray <vilhelm.gray@gmail.com> | 2016-08-02 09:57:47 -0400 |
---|---|---|
committer | Linus Walleij <linus.walleij@linaro.org> | 2016-08-11 07:37:25 -0400 |
commit | 6ea5dcdf7924d967a4d4b3aa7170e37a1be5cf0f (patch) | |
tree | 31a35232f999e9dd4b0d42e3ec9e1b083611c923 | |
parent | 0ba19cfc2a667ca986355e11d122c465482f12e2 (diff) |
gpio: Add GPIO support for the Diamond Systems GPIO-MM
The Diamond Systems GPIO-MM device features 48 lines of digital I/O via
the emulation of dual 82C55A PPI chips. This driver provides GPIO
support for these 48 channels of digital I/O. The base port addresses
for the devices may be configured via the base array module parameter.
Signed-off-by: William Breathitt Gray <vilhelm.gray@gmail.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
-rw-r--r-- | MAINTAINERS | 6 | ||||
-rw-r--r-- | drivers/gpio/Kconfig | 13 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/gpio-gpio-mm.c | 267 |
4 files changed, 287 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 20bb1d00098c..a9ffa78c97cd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -3768,6 +3768,12 @@ F: include/linux/regulator/da9211.h | |||
3768 | F: include/sound/da[79]*.h | 3768 | F: include/sound/da[79]*.h |
3769 | F: sound/soc/codecs/da[79]*.[ch] | 3769 | F: sound/soc/codecs/da[79]*.[ch] |
3770 | 3770 | ||
3771 | DIAMOND SYSTEMS GPIO-MM GPIO DRIVER | ||
3772 | M: William Breathitt Gray <vilhelm.gray@gmail.com> | ||
3773 | L: linux-gpio@vger.kernel.org | ||
3774 | S: Maintained | ||
3775 | F: drivers/gpio/gpio-gpio-mm.c | ||
3776 | |||
3771 | DIGI NEO AND CLASSIC PCI PRODUCTS | 3777 | DIGI NEO AND CLASSIC PCI PRODUCTS |
3772 | M: Lidza Louina <lidza.louina@gmail.com> | 3778 | M: Lidza Louina <lidza.louina@gmail.com> |
3773 | M: Mark Hounschell <markh@compro.net> | 3779 | M: Mark Hounschell <markh@compro.net> |
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 591f692dae63..825cf3c3d579 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig | |||
@@ -558,6 +558,19 @@ config GPIO_F7188X | |||
558 | To compile this driver as a module, choose M here: the module will | 558 | To compile this driver as a module, choose M here: the module will |
559 | be called f7188x-gpio. | 559 | be called f7188x-gpio. |
560 | 560 | ||
561 | config GPIO_GPIO_MM | ||
562 | tristate "Diamond Systems GPIO-MM GPIO support" | ||
563 | depends on ISA_BUS_API | ||
564 | help | ||
565 | Enables GPIO support for the Diamond Systems GPIO-MM and GPIO-MM-12. | ||
566 | |||
567 | The Diamond Systems GPIO-MM device features 48 lines of digital I/O | ||
568 | via the emulation of dual 82C55A PPI chips. This driver provides GPIO | ||
569 | support for these 48 channels of digital I/O. | ||
570 | |||
571 | The base port addresses for the devices may be configured via the base | ||
572 | array module parameter. | ||
573 | |||
561 | config GPIO_IT87 | 574 | config GPIO_IT87 |
562 | tristate "IT87xx GPIO support" | 575 | tristate "IT87xx GPIO support" |
563 | help | 576 | help |
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 031195cdde7f..96650289888f 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile | |||
@@ -45,6 +45,7 @@ obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o | |||
45 | obj-$(CONFIG_GPIO_ETRAXFS) += gpio-etraxfs.o | 45 | obj-$(CONFIG_GPIO_ETRAXFS) += gpio-etraxfs.o |
46 | obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o | 46 | obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o |
47 | obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o | 47 | obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o |
48 | obj-$(CONFIG_GPIO_GPIO_MM) += gpio-gpio-mm.o | ||
48 | obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o | 49 | obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o |
49 | obj-$(CONFIG_GPIO_ICH) += gpio-ich.o | 50 | obj-$(CONFIG_GPIO_ICH) += gpio-ich.o |
50 | obj-$(CONFIG_GPIO_IOP) += gpio-iop.o | 51 | obj-$(CONFIG_GPIO_IOP) += gpio-iop.o |
diff --git a/drivers/gpio/gpio-gpio-mm.c b/drivers/gpio/gpio-gpio-mm.c new file mode 100644 index 000000000000..1e7def9449ce --- /dev/null +++ b/drivers/gpio/gpio-gpio-mm.c | |||
@@ -0,0 +1,267 @@ | |||
1 | /* | ||
2 | * GPIO driver for the Diamond Systems GPIO-MM | ||
3 | * Copyright (C) 2016 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 | * This driver supports the following Diamond Systems devices: GPIO-MM and | ||
15 | * GPIO-MM-12. | ||
16 | */ | ||
17 | #include <linux/bitops.h> | ||
18 | #include <linux/device.h> | ||
19 | #include <linux/errno.h> | ||
20 | #include <linux/gpio/driver.h> | ||
21 | #include <linux/io.h> | ||
22 | #include <linux/ioport.h> | ||
23 | #include <linux/isa.h> | ||
24 | #include <linux/kernel.h> | ||
25 | #include <linux/module.h> | ||
26 | #include <linux/moduleparam.h> | ||
27 | #include <linux/spinlock.h> | ||
28 | |||
29 | #define GPIOMM_EXTENT 8 | ||
30 | #define MAX_NUM_GPIOMM max_num_isa_dev(GPIOMM_EXTENT) | ||
31 | |||
32 | static unsigned int base[MAX_NUM_GPIOMM]; | ||
33 | static unsigned int num_gpiomm; | ||
34 | module_param_array(base, uint, &num_gpiomm, 0); | ||
35 | MODULE_PARM_DESC(base, "Diamond Systems GPIO-MM base addresses"); | ||
36 | |||
37 | /** | ||
38 | * struct gpiomm_gpio - GPIO device private data structure | ||
39 | * @chip: instance of the gpio_chip | ||
40 | * @io_state: bit I/O state (whether bit is set to input or output) | ||
41 | * @out_state: output bits state | ||
42 | * @control: Control registers state | ||
43 | * @lock: synchronization lock to prevent I/O race conditions | ||
44 | * @base: base port address of the GPIO device | ||
45 | */ | ||
46 | struct gpiomm_gpio { | ||
47 | struct gpio_chip chip; | ||
48 | unsigned char io_state[6]; | ||
49 | unsigned char out_state[6]; | ||
50 | unsigned char control[2]; | ||
51 | spinlock_t lock; | ||
52 | unsigned int base; | ||
53 | }; | ||
54 | |||
55 | static int gpiomm_gpio_get_direction(struct gpio_chip *chip, | ||
56 | unsigned int offset) | ||
57 | { | ||
58 | struct gpiomm_gpio *const gpiommgpio = gpiochip_get_data(chip); | ||
59 | const unsigned int port = offset / 8; | ||
60 | const unsigned int mask = BIT(offset % 8); | ||
61 | |||
62 | return !!(gpiommgpio->io_state[port] & mask); | ||
63 | } | ||
64 | |||
65 | static int gpiomm_gpio_direction_input(struct gpio_chip *chip, | ||
66 | unsigned int offset) | ||
67 | { | ||
68 | struct gpiomm_gpio *const gpiommgpio = gpiochip_get_data(chip); | ||
69 | const unsigned int io_port = offset / 8; | ||
70 | const unsigned int control_port = io_port / 3; | ||
71 | const unsigned int control_addr = gpiommgpio->base + 3 + control_port*4; | ||
72 | unsigned long flags; | ||
73 | unsigned int control; | ||
74 | |||
75 | spin_lock_irqsave(&gpiommgpio->lock, flags); | ||
76 | |||
77 | /* Check if configuring Port C */ | ||
78 | if (io_port == 2 || io_port == 5) { | ||
79 | /* Port C can be configured by nibble */ | ||
80 | if (offset % 8 > 3) { | ||
81 | gpiommgpio->io_state[io_port] |= 0xF0; | ||
82 | gpiommgpio->control[control_port] |= BIT(3); | ||
83 | } else { | ||
84 | gpiommgpio->io_state[io_port] |= 0x0F; | ||
85 | gpiommgpio->control[control_port] |= BIT(0); | ||
86 | } | ||
87 | } else { | ||
88 | gpiommgpio->io_state[io_port] |= 0xFF; | ||
89 | if (io_port == 0 || io_port == 3) | ||
90 | gpiommgpio->control[control_port] |= BIT(4); | ||
91 | else | ||
92 | gpiommgpio->control[control_port] |= BIT(1); | ||
93 | } | ||
94 | |||
95 | control = BIT(7) | gpiommgpio->control[control_port]; | ||
96 | outb(control, control_addr); | ||
97 | |||
98 | spin_unlock_irqrestore(&gpiommgpio->lock, flags); | ||
99 | |||
100 | return 0; | ||
101 | } | ||
102 | |||
103 | static int gpiomm_gpio_direction_output(struct gpio_chip *chip, | ||
104 | unsigned int offset, int value) | ||
105 | { | ||
106 | struct gpiomm_gpio *const gpiommgpio = gpiochip_get_data(chip); | ||
107 | const unsigned int io_port = offset / 8; | ||
108 | const unsigned int control_port = io_port / 3; | ||
109 | const unsigned int mask = BIT(offset % 8); | ||
110 | const unsigned int control_addr = gpiommgpio->base + 3 + control_port*4; | ||
111 | const unsigned int out_port = (io_port > 2) ? io_port + 1 : io_port; | ||
112 | unsigned long flags; | ||
113 | unsigned int control; | ||
114 | |||
115 | spin_lock_irqsave(&gpiommgpio->lock, flags); | ||
116 | |||
117 | /* Check if configuring Port C */ | ||
118 | if (io_port == 2 || io_port == 5) { | ||
119 | /* Port C can be configured by nibble */ | ||
120 | if (offset % 8 > 3) { | ||
121 | gpiommgpio->io_state[io_port] &= 0x0F; | ||
122 | gpiommgpio->control[control_port] &= ~BIT(3); | ||
123 | } else { | ||
124 | gpiommgpio->io_state[io_port] &= 0xF0; | ||
125 | gpiommgpio->control[control_port] &= ~BIT(0); | ||
126 | } | ||
127 | } else { | ||
128 | gpiommgpio->io_state[io_port] &= 0x00; | ||
129 | if (io_port == 0 || io_port == 3) | ||
130 | gpiommgpio->control[control_port] &= ~BIT(4); | ||
131 | else | ||
132 | gpiommgpio->control[control_port] &= ~BIT(1); | ||
133 | } | ||
134 | |||
135 | if (value) | ||
136 | gpiommgpio->out_state[io_port] |= mask; | ||
137 | else | ||
138 | gpiommgpio->out_state[io_port] &= ~mask; | ||
139 | |||
140 | control = BIT(7) | gpiommgpio->control[control_port]; | ||
141 | outb(control, control_addr); | ||
142 | |||
143 | outb(gpiommgpio->out_state[io_port], gpiommgpio->base + out_port); | ||
144 | |||
145 | spin_unlock_irqrestore(&gpiommgpio->lock, flags); | ||
146 | |||
147 | return 0; | ||
148 | } | ||
149 | |||
150 | static int gpiomm_gpio_get(struct gpio_chip *chip, unsigned int offset) | ||
151 | { | ||
152 | struct gpiomm_gpio *const gpiommgpio = gpiochip_get_data(chip); | ||
153 | const unsigned int port = offset / 8; | ||
154 | const unsigned int mask = BIT(offset % 8); | ||
155 | const unsigned int in_port = (port > 2) ? port + 1 : port; | ||
156 | unsigned long flags; | ||
157 | unsigned int port_state; | ||
158 | |||
159 | spin_lock_irqsave(&gpiommgpio->lock, flags); | ||
160 | |||
161 | /* ensure that GPIO is set for input */ | ||
162 | if (!(gpiommgpio->io_state[port] & mask)) { | ||
163 | spin_unlock_irqrestore(&gpiommgpio->lock, flags); | ||
164 | return -EINVAL; | ||
165 | } | ||
166 | |||
167 | port_state = inb(gpiommgpio->base + in_port); | ||
168 | |||
169 | spin_unlock_irqrestore(&gpiommgpio->lock, flags); | ||
170 | |||
171 | return !!(port_state & mask); | ||
172 | } | ||
173 | |||
174 | static void gpiomm_gpio_set(struct gpio_chip *chip, unsigned int offset, | ||
175 | int value) | ||
176 | { | ||
177 | struct gpiomm_gpio *const gpiommgpio = gpiochip_get_data(chip); | ||
178 | const unsigned int port = offset / 8; | ||
179 | const unsigned int mask = BIT(offset % 8); | ||
180 | const unsigned int out_port = (port > 2) ? port + 1 : port; | ||
181 | unsigned long flags; | ||
182 | |||
183 | spin_lock_irqsave(&gpiommgpio->lock, flags); | ||
184 | |||
185 | if (value) | ||
186 | gpiommgpio->out_state[port] |= mask; | ||
187 | else | ||
188 | gpiommgpio->out_state[port] &= ~mask; | ||
189 | |||
190 | outb(gpiommgpio->out_state[port], gpiommgpio->base + out_port); | ||
191 | |||
192 | spin_unlock_irqrestore(&gpiommgpio->lock, flags); | ||
193 | } | ||
194 | |||
195 | static int gpiomm_probe(struct device *dev, unsigned int id) | ||
196 | { | ||
197 | struct gpiomm_gpio *gpiommgpio; | ||
198 | const char *const name = dev_name(dev); | ||
199 | int err; | ||
200 | |||
201 | gpiommgpio = devm_kzalloc(dev, sizeof(*gpiommgpio), GFP_KERNEL); | ||
202 | if (!gpiommgpio) | ||
203 | return -ENOMEM; | ||
204 | |||
205 | if (!devm_request_region(dev, base[id], GPIOMM_EXTENT, name)) { | ||
206 | dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", | ||
207 | base[id], base[id] + GPIOMM_EXTENT); | ||
208 | return -EBUSY; | ||
209 | } | ||
210 | |||
211 | gpiommgpio->chip.label = name; | ||
212 | gpiommgpio->chip.parent = dev; | ||
213 | gpiommgpio->chip.owner = THIS_MODULE; | ||
214 | gpiommgpio->chip.base = -1; | ||
215 | gpiommgpio->chip.ngpio = 48; | ||
216 | gpiommgpio->chip.get_direction = gpiomm_gpio_get_direction; | ||
217 | gpiommgpio->chip.direction_input = gpiomm_gpio_direction_input; | ||
218 | gpiommgpio->chip.direction_output = gpiomm_gpio_direction_output; | ||
219 | gpiommgpio->chip.get = gpiomm_gpio_get; | ||
220 | gpiommgpio->chip.set = gpiomm_gpio_set; | ||
221 | gpiommgpio->base = base[id]; | ||
222 | |||
223 | spin_lock_init(&gpiommgpio->lock); | ||
224 | |||
225 | dev_set_drvdata(dev, gpiommgpio); | ||
226 | |||
227 | err = gpiochip_add_data(&gpiommgpio->chip, gpiommgpio); | ||
228 | if (err) { | ||
229 | dev_err(dev, "GPIO registering failed (%d)\n", err); | ||
230 | return err; | ||
231 | } | ||
232 | |||
233 | /* initialize all GPIO as output */ | ||
234 | outb(0x80, base[id] + 3); | ||
235 | outb(0x00, base[id]); | ||
236 | outb(0x00, base[id] + 1); | ||
237 | outb(0x00, base[id] + 2); | ||
238 | outb(0x80, base[id] + 7); | ||
239 | outb(0x00, base[id] + 4); | ||
240 | outb(0x00, base[id] + 5); | ||
241 | outb(0x00, base[id] + 6); | ||
242 | |||
243 | return 0; | ||
244 | } | ||
245 | |||
246 | static int gpiomm_remove(struct device *dev, unsigned int id) | ||
247 | { | ||
248 | struct gpiomm_gpio *const gpiommgpio = dev_get_drvdata(dev); | ||
249 | |||
250 | gpiochip_remove(&gpiommgpio->chip); | ||
251 | |||
252 | return 0; | ||
253 | } | ||
254 | |||
255 | static struct isa_driver gpiomm_driver = { | ||
256 | .probe = gpiomm_probe, | ||
257 | .driver = { | ||
258 | .name = "gpio-mm" | ||
259 | }, | ||
260 | .remove = gpiomm_remove | ||
261 | }; | ||
262 | |||
263 | module_isa_driver(gpiomm_driver, num_gpiomm); | ||
264 | |||
265 | MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>"); | ||
266 | MODULE_DESCRIPTION("Diamond Systems GPIO-MM GPIO driver"); | ||
267 | MODULE_LICENSE("GPL v2"); | ||