diff options
author | Simon Guinot <simon.guinot@sequanux.org> | 2013-08-29 16:56:56 -0400 |
---|---|---|
committer | Linus Walleij <linus.walleij@linaro.org> | 2013-08-30 03:25:52 -0400 |
commit | 6c17aa0138a6c55364936bbaa35846e09a4db53b (patch) | |
tree | ddbe02fbee48e5cc0f058e9cda30a1c659e1df59 /drivers/gpio | |
parent | c6641da12e0f5516b75386dccedf163bbfeaabe0 (diff) |
gpio: add GPIO support for F71882FG and F71889F
This patch adds support for the GPIOs found on the Fintek super-I/O
chips F71882FG and F71889F.
A super-I/O is a legacy I/O controller embedded on x86 motherboards. It
is used to connect the low-bandwidth devices. Among others functions the
F71882FG/F71889F provides: a parallel port, two serial ports, a keyboard
controller, an hardware monitoring controller and some GPIO pins.
Note that this super-I/Os are embedded on some Atom-based LaCie NASes.
The GPIOs are used to control the LEDs and the hard drive power.
Changes since v3:
- Use request_muxed_region to protect the I/O ports against concurrent
accesses.
Changes since v2:
- Remove useless NULL setters for driver data.
Changes since v1:
- Enhance the commit message by describing what is a Super-I/O.
- Use self-explanatory names for the GPIO register macros.
- Add a comment to explain the platform device and driver registration.
- Fix gpio_get when GPIO is configured in input mode. I only had
the hardware to check this mode recently...
Signed-off-by: Simon Guinot <simon.guinot@sequanux.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Diffstat (limited to 'drivers/gpio')
-rw-r--r-- | drivers/gpio/Kconfig | 10 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/gpio-f7188x.c | 469 |
3 files changed, 480 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 4b7ba53e96db..349b16160ac9 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig | |||
@@ -146,6 +146,16 @@ config GPIO_MM_LANTIQ | |||
146 | (EBU) found on Lantiq SoCs. The gpios are output only as they are | 146 | (EBU) found on Lantiq SoCs. The gpios are output only as they are |
147 | created by attaching a 16bit latch to the bus. | 147 | created by attaching a 16bit latch to the bus. |
148 | 148 | ||
149 | config GPIO_F7188X | ||
150 | tristate "F71882FG and F71889F GPIO support" | ||
151 | depends on X86 | ||
152 | help | ||
153 | This option enables support for GPIOs found on Fintek Super-I/O | ||
154 | chips F71882FG and F71889F. | ||
155 | |||
156 | To compile this driver as a module, choose M here: the module will | ||
157 | be called f7188x-gpio. | ||
158 | |||
149 | config GPIO_MPC5200 | 159 | config GPIO_MPC5200 |
150 | def_bool y | 160 | def_bool y |
151 | depends on PPC_MPC52xx | 161 | depends on PPC_MPC52xx |
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 156fd283945c..97438bf8434a 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile | |||
@@ -24,6 +24,7 @@ obj-$(CONFIG_GPIO_DA9055) += gpio-da9055.o | |||
24 | obj-$(CONFIG_ARCH_DAVINCI) += gpio-davinci.o | 24 | obj-$(CONFIG_ARCH_DAVINCI) += gpio-davinci.o |
25 | obj-$(CONFIG_GPIO_EM) += gpio-em.o | 25 | obj-$(CONFIG_GPIO_EM) += gpio-em.o |
26 | obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o | 26 | obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o |
27 | obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o | ||
27 | obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o | 28 | obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o |
28 | obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o | 29 | obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o |
29 | obj-$(CONFIG_GPIO_ICH) += gpio-ich.o | 30 | obj-$(CONFIG_GPIO_ICH) += gpio-ich.o |
diff --git a/drivers/gpio/gpio-f7188x.c b/drivers/gpio/gpio-f7188x.c new file mode 100644 index 000000000000..9cb8320e1181 --- /dev/null +++ b/drivers/gpio/gpio-f7188x.c | |||
@@ -0,0 +1,469 @@ | |||
1 | /* | ||
2 | * GPIO driver for Fintek Super-I/O F71882 and F71889 | ||
3 | * | ||
4 | * Copyright (C) 2010-2013 LaCie | ||
5 | * | ||
6 | * Author: Simon Guinot <simon.guinot@sequanux.org> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | */ | ||
13 | |||
14 | #include <linux/module.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/platform_device.h> | ||
17 | #include <linux/io.h> | ||
18 | #include <linux/gpio.h> | ||
19 | |||
20 | #define DRVNAME "gpio-f7188x" | ||
21 | |||
22 | /* | ||
23 | * Super-I/O registers | ||
24 | */ | ||
25 | #define SIO_LDSEL 0x07 /* Logical device select */ | ||
26 | #define SIO_DEVID 0x20 /* Device ID (2 bytes) */ | ||
27 | #define SIO_DEVREV 0x22 /* Device revision */ | ||
28 | #define SIO_MANID 0x23 /* Fintek ID (2 bytes) */ | ||
29 | |||
30 | #define SIO_LD_GPIO 0x06 /* GPIO logical device */ | ||
31 | #define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */ | ||
32 | #define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */ | ||
33 | |||
34 | #define SIO_FINTEK_ID 0x1934 /* Manufacturer ID */ | ||
35 | #define SIO_F71882_ID 0x0541 /* F71882 chipset ID */ | ||
36 | #define SIO_F71889_ID 0x0909 /* F71889 chipset ID */ | ||
37 | |||
38 | enum chips { f71882fg, f71889f }; | ||
39 | |||
40 | static const char * const f7188x_names[] = { | ||
41 | "f71882fg", | ||
42 | "f71889f", | ||
43 | }; | ||
44 | |||
45 | struct f7188x_sio { | ||
46 | int addr; | ||
47 | enum chips type; | ||
48 | }; | ||
49 | |||
50 | struct f7188x_gpio_bank { | ||
51 | struct gpio_chip chip; | ||
52 | unsigned int regbase; | ||
53 | struct f7188x_gpio_data *data; | ||
54 | }; | ||
55 | |||
56 | struct f7188x_gpio_data { | ||
57 | struct f7188x_sio *sio; | ||
58 | int nr_bank; | ||
59 | struct f7188x_gpio_bank *bank; | ||
60 | }; | ||
61 | |||
62 | /* | ||
63 | * Super-I/O functions. | ||
64 | */ | ||
65 | |||
66 | static inline int superio_inb(int base, int reg) | ||
67 | { | ||
68 | outb(reg, base); | ||
69 | return inb(base + 1); | ||
70 | } | ||
71 | |||
72 | static int superio_inw(int base, int reg) | ||
73 | { | ||
74 | int val; | ||
75 | |||
76 | outb(reg++, base); | ||
77 | val = inb(base + 1) << 8; | ||
78 | outb(reg, base); | ||
79 | val |= inb(base + 1); | ||
80 | |||
81 | return val; | ||
82 | } | ||
83 | |||
84 | static inline void superio_outb(int base, int reg, int val) | ||
85 | { | ||
86 | outb(reg, base); | ||
87 | outb(val, base + 1); | ||
88 | } | ||
89 | |||
90 | static inline int superio_enter(int base) | ||
91 | { | ||
92 | /* Don't step on other drivers' I/O space by accident. */ | ||
93 | if (!request_muxed_region(base, 2, DRVNAME)) { | ||
94 | pr_err(DRVNAME "I/O address 0x%04x already in use\n", base); | ||
95 | return -EBUSY; | ||
96 | } | ||
97 | |||
98 | /* According to the datasheet the key must be send twice. */ | ||
99 | outb(SIO_UNLOCK_KEY, base); | ||
100 | outb(SIO_UNLOCK_KEY, base); | ||
101 | |||
102 | return 0; | ||
103 | } | ||
104 | |||
105 | static inline void superio_select(int base, int ld) | ||
106 | { | ||
107 | outb(SIO_LDSEL, base); | ||
108 | outb(ld, base + 1); | ||
109 | } | ||
110 | |||
111 | static inline void superio_exit(int base) | ||
112 | { | ||
113 | outb(SIO_LOCK_KEY, base); | ||
114 | release_region(base, 2); | ||
115 | } | ||
116 | |||
117 | /* | ||
118 | * GPIO chip. | ||
119 | */ | ||
120 | |||
121 | static int f7188x_gpio_direction_in(struct gpio_chip *chip, unsigned offset); | ||
122 | static int f7188x_gpio_get(struct gpio_chip *chip, unsigned offset); | ||
123 | static int f7188x_gpio_direction_out(struct gpio_chip *chip, | ||
124 | unsigned offset, int value); | ||
125 | static void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value); | ||
126 | |||
127 | #define F7188X_GPIO_BANK(_base, _ngpio, _regbase) \ | ||
128 | { \ | ||
129 | .chip = { \ | ||
130 | .label = DRVNAME, \ | ||
131 | .owner = THIS_MODULE, \ | ||
132 | .direction_input = f7188x_gpio_direction_in, \ | ||
133 | .get = f7188x_gpio_get, \ | ||
134 | .direction_output = f7188x_gpio_direction_out, \ | ||
135 | .set = f7188x_gpio_set, \ | ||
136 | .base = _base, \ | ||
137 | .ngpio = _ngpio, \ | ||
138 | }, \ | ||
139 | .regbase = _regbase, \ | ||
140 | } | ||
141 | |||
142 | #define gpio_dir(base) (base + 0) | ||
143 | #define gpio_data_out(base) (base + 1) | ||
144 | #define gpio_data_in(base) (base + 2) | ||
145 | /* Output mode register (0:open drain 1:push-pull). */ | ||
146 | #define gpio_out_mode(base) (base + 3) | ||
147 | |||
148 | static struct f7188x_gpio_bank f71882_gpio_bank[] = { | ||
149 | F7188X_GPIO_BANK(0 , 8, 0xF0), | ||
150 | F7188X_GPIO_BANK(10, 8, 0xE0), | ||
151 | F7188X_GPIO_BANK(20, 8, 0xD0), | ||
152 | F7188X_GPIO_BANK(30, 4, 0xC0), | ||
153 | F7188X_GPIO_BANK(40, 4, 0xB0), | ||
154 | }; | ||
155 | |||
156 | static struct f7188x_gpio_bank f71889_gpio_bank[] = { | ||
157 | F7188X_GPIO_BANK(0 , 7, 0xF0), | ||
158 | F7188X_GPIO_BANK(10, 7, 0xE0), | ||
159 | F7188X_GPIO_BANK(20, 8, 0xD0), | ||
160 | F7188X_GPIO_BANK(30, 8, 0xC0), | ||
161 | F7188X_GPIO_BANK(40, 8, 0xB0), | ||
162 | F7188X_GPIO_BANK(50, 5, 0xA0), | ||
163 | F7188X_GPIO_BANK(60, 8, 0x90), | ||
164 | F7188X_GPIO_BANK(70, 8, 0x80), | ||
165 | }; | ||
166 | |||
167 | static int f7188x_gpio_direction_in(struct gpio_chip *chip, unsigned offset) | ||
168 | { | ||
169 | int err; | ||
170 | struct f7188x_gpio_bank *bank = | ||
171 | container_of(chip, struct f7188x_gpio_bank, chip); | ||
172 | struct f7188x_sio *sio = bank->data->sio; | ||
173 | u8 dir; | ||
174 | |||
175 | err = superio_enter(sio->addr); | ||
176 | if (err) | ||
177 | return err; | ||
178 | superio_select(sio->addr, SIO_LD_GPIO); | ||
179 | |||
180 | dir = superio_inb(sio->addr, gpio_dir(bank->regbase)); | ||
181 | dir &= ~(1 << offset); | ||
182 | superio_outb(sio->addr, gpio_dir(bank->regbase), dir); | ||
183 | |||
184 | superio_exit(sio->addr); | ||
185 | |||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | static int f7188x_gpio_get(struct gpio_chip *chip, unsigned offset) | ||
190 | { | ||
191 | int err; | ||
192 | struct f7188x_gpio_bank *bank = | ||
193 | container_of(chip, struct f7188x_gpio_bank, chip); | ||
194 | struct f7188x_sio *sio = bank->data->sio; | ||
195 | u8 dir, data; | ||
196 | |||
197 | err = superio_enter(sio->addr); | ||
198 | if (err) | ||
199 | return err; | ||
200 | superio_select(sio->addr, SIO_LD_GPIO); | ||
201 | |||
202 | dir = superio_inb(sio->addr, gpio_dir(bank->regbase)); | ||
203 | dir = !!(dir & (1 << offset)); | ||
204 | if (dir) | ||
205 | data = superio_inb(sio->addr, gpio_data_out(bank->regbase)); | ||
206 | else | ||
207 | data = superio_inb(sio->addr, gpio_data_in(bank->regbase)); | ||
208 | |||
209 | superio_exit(sio->addr); | ||
210 | |||
211 | return !!(data & 1 << offset); | ||
212 | } | ||
213 | |||
214 | static int f7188x_gpio_direction_out(struct gpio_chip *chip, | ||
215 | unsigned offset, int value) | ||
216 | { | ||
217 | int err; | ||
218 | struct f7188x_gpio_bank *bank = | ||
219 | container_of(chip, struct f7188x_gpio_bank, chip); | ||
220 | struct f7188x_sio *sio = bank->data->sio; | ||
221 | u8 dir, data_out; | ||
222 | |||
223 | err = superio_enter(sio->addr); | ||
224 | if (err) | ||
225 | return err; | ||
226 | superio_select(sio->addr, SIO_LD_GPIO); | ||
227 | |||
228 | data_out = superio_inb(sio->addr, gpio_data_out(bank->regbase)); | ||
229 | if (value) | ||
230 | data_out |= (1 << offset); | ||
231 | else | ||
232 | data_out &= ~(1 << offset); | ||
233 | superio_outb(sio->addr, gpio_data_out(bank->regbase), data_out); | ||
234 | |||
235 | dir = superio_inb(sio->addr, gpio_dir(bank->regbase)); | ||
236 | dir |= (1 << offset); | ||
237 | superio_outb(sio->addr, gpio_dir(bank->regbase), dir); | ||
238 | |||
239 | superio_exit(sio->addr); | ||
240 | |||
241 | return 0; | ||
242 | } | ||
243 | |||
244 | static void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) | ||
245 | { | ||
246 | int err; | ||
247 | struct f7188x_gpio_bank *bank = | ||
248 | container_of(chip, struct f7188x_gpio_bank, chip); | ||
249 | struct f7188x_sio *sio = bank->data->sio; | ||
250 | u8 data_out; | ||
251 | |||
252 | err = superio_enter(sio->addr); | ||
253 | if (err) | ||
254 | return; | ||
255 | superio_select(sio->addr, SIO_LD_GPIO); | ||
256 | |||
257 | data_out = superio_inb(sio->addr, gpio_data_out(bank->regbase)); | ||
258 | if (value) | ||
259 | data_out |= (1 << offset); | ||
260 | else | ||
261 | data_out &= ~(1 << offset); | ||
262 | superio_outb(sio->addr, gpio_data_out(bank->regbase), data_out); | ||
263 | |||
264 | superio_exit(sio->addr); | ||
265 | } | ||
266 | |||
267 | /* | ||
268 | * Platform device and driver. | ||
269 | */ | ||
270 | |||
271 | static int f7188x_gpio_probe(struct platform_device *pdev) | ||
272 | { | ||
273 | int err; | ||
274 | int i; | ||
275 | struct f7188x_sio *sio = pdev->dev.platform_data; | ||
276 | struct f7188x_gpio_data *data; | ||
277 | |||
278 | data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); | ||
279 | if (!data) | ||
280 | return -ENOMEM; | ||
281 | |||
282 | switch (sio->type) { | ||
283 | case f71882fg: | ||
284 | data->nr_bank = ARRAY_SIZE(f71882_gpio_bank); | ||
285 | data->bank = f71882_gpio_bank; | ||
286 | break; | ||
287 | case f71889f: | ||
288 | data->nr_bank = ARRAY_SIZE(f71889_gpio_bank); | ||
289 | data->bank = f71889_gpio_bank; | ||
290 | break; | ||
291 | default: | ||
292 | return -ENODEV; | ||
293 | } | ||
294 | data->sio = sio; | ||
295 | |||
296 | platform_set_drvdata(pdev, data); | ||
297 | |||
298 | /* For each GPIO bank, register a GPIO chip. */ | ||
299 | for (i = 0; i < data->nr_bank; i++) { | ||
300 | struct f7188x_gpio_bank *bank = &data->bank[i]; | ||
301 | |||
302 | bank->chip.dev = &pdev->dev; | ||
303 | bank->data = data; | ||
304 | |||
305 | err = gpiochip_add(&bank->chip); | ||
306 | if (err) { | ||
307 | dev_err(&pdev->dev, | ||
308 | "Failed to register gpiochip %d: %d\n", | ||
309 | i, err); | ||
310 | goto err_gpiochip; | ||
311 | } | ||
312 | } | ||
313 | |||
314 | return 0; | ||
315 | |||
316 | err_gpiochip: | ||
317 | for (i = i - 1; i >= 0; i--) { | ||
318 | struct f7188x_gpio_bank *bank = &data->bank[i]; | ||
319 | int tmp; | ||
320 | |||
321 | tmp = gpiochip_remove(&bank->chip); | ||
322 | if (tmp < 0) | ||
323 | dev_err(&pdev->dev, | ||
324 | "Failed to remove gpiochip %d: %d\n", | ||
325 | i, tmp); | ||
326 | } | ||
327 | |||
328 | return err; | ||
329 | } | ||
330 | |||
331 | static int f7188x_gpio_remove(struct platform_device *pdev) | ||
332 | { | ||
333 | int err; | ||
334 | int i; | ||
335 | struct f7188x_gpio_data *data = platform_get_drvdata(pdev); | ||
336 | |||
337 | for (i = 0; i < data->nr_bank; i++) { | ||
338 | struct f7188x_gpio_bank *bank = &data->bank[i]; | ||
339 | |||
340 | err = gpiochip_remove(&bank->chip); | ||
341 | if (err) { | ||
342 | dev_err(&pdev->dev, | ||
343 | "Failed to remove GPIO gpiochip %d: %d\n", | ||
344 | i, err); | ||
345 | return err; | ||
346 | } | ||
347 | } | ||
348 | |||
349 | return 0; | ||
350 | } | ||
351 | |||
352 | static int __init f7188x_find(int addr, struct f7188x_sio *sio) | ||
353 | { | ||
354 | int err; | ||
355 | u16 devid; | ||
356 | |||
357 | err = superio_enter(addr); | ||
358 | if (err) | ||
359 | return err; | ||
360 | |||
361 | err = -ENODEV; | ||
362 | devid = superio_inw(addr, SIO_MANID); | ||
363 | if (devid != SIO_FINTEK_ID) { | ||
364 | pr_debug(DRVNAME ": Not a Fintek device at 0x%08x\n", addr); | ||
365 | goto err; | ||
366 | } | ||
367 | |||
368 | devid = superio_inw(addr, SIO_DEVID); | ||
369 | switch (devid) { | ||
370 | case SIO_F71882_ID: | ||
371 | sio->type = f71882fg; | ||
372 | break; | ||
373 | case SIO_F71889_ID: | ||
374 | sio->type = f71889f; | ||
375 | break; | ||
376 | default: | ||
377 | pr_info(DRVNAME ": Unsupported Fintek device 0x%04x\n", devid); | ||
378 | goto err; | ||
379 | } | ||
380 | sio->addr = addr; | ||
381 | err = 0; | ||
382 | |||
383 | pr_info(DRVNAME ": Found %s at %#x, revision %d\n", | ||
384 | f7188x_names[sio->type], | ||
385 | (unsigned int) addr, | ||
386 | (int) superio_inb(addr, SIO_DEVREV)); | ||
387 | |||
388 | err: | ||
389 | superio_exit(addr); | ||
390 | return err; | ||
391 | } | ||
392 | |||
393 | static struct platform_device *f7188x_gpio_pdev; | ||
394 | |||
395 | static int __init | ||
396 | f7188x_gpio_device_add(const struct f7188x_sio *sio) | ||
397 | { | ||
398 | int err; | ||
399 | |||
400 | f7188x_gpio_pdev = platform_device_alloc(DRVNAME, -1); | ||
401 | if (!f7188x_gpio_pdev) | ||
402 | return -ENOMEM; | ||
403 | |||
404 | err = platform_device_add_data(f7188x_gpio_pdev, | ||
405 | sio, sizeof(*sio)); | ||
406 | if (err) { | ||
407 | pr_err(DRVNAME "Platform data allocation failed\n"); | ||
408 | goto err; | ||
409 | } | ||
410 | |||
411 | err = platform_device_add(f7188x_gpio_pdev); | ||
412 | if (err) { | ||
413 | pr_err(DRVNAME "Device addition failed\n"); | ||
414 | goto err; | ||
415 | } | ||
416 | |||
417 | return 0; | ||
418 | |||
419 | err: | ||
420 | platform_device_put(f7188x_gpio_pdev); | ||
421 | |||
422 | return err; | ||
423 | } | ||
424 | |||
425 | /* | ||
426 | * Try to match a supported Fintech device by reading the (hard-wired) | ||
427 | * configuration I/O ports. If available, then register both the platform | ||
428 | * device and driver to support the GPIOs. | ||
429 | */ | ||
430 | |||
431 | static struct platform_driver f7188x_gpio_driver = { | ||
432 | .driver = { | ||
433 | .owner = THIS_MODULE, | ||
434 | .name = DRVNAME, | ||
435 | }, | ||
436 | .probe = f7188x_gpio_probe, | ||
437 | .remove = f7188x_gpio_remove, | ||
438 | }; | ||
439 | |||
440 | static int __init f7188x_gpio_init(void) | ||
441 | { | ||
442 | int err; | ||
443 | struct f7188x_sio sio; | ||
444 | |||
445 | if (f7188x_find(0x2e, &sio) && | ||
446 | f7188x_find(0x4e, &sio)) | ||
447 | return -ENODEV; | ||
448 | |||
449 | err = platform_driver_register(&f7188x_gpio_driver); | ||
450 | if (!err) { | ||
451 | err = f7188x_gpio_device_add(&sio); | ||
452 | if (err) | ||
453 | platform_driver_unregister(&f7188x_gpio_driver); | ||
454 | } | ||
455 | |||
456 | return err; | ||
457 | } | ||
458 | subsys_initcall(f7188x_gpio_init); | ||
459 | |||
460 | static void __exit f7188x_gpio_exit(void) | ||
461 | { | ||
462 | platform_device_unregister(f7188x_gpio_pdev); | ||
463 | platform_driver_unregister(&f7188x_gpio_driver); | ||
464 | } | ||
465 | module_exit(f7188x_gpio_exit); | ||
466 | |||
467 | MODULE_DESCRIPTION("GPIO driver for Super-I/O chips F71882FG and F71889F"); | ||
468 | MODULE_AUTHOR("Simon Guinot <simon.guinot@sequanux.org>"); | ||
469 | MODULE_LICENSE("GPL"); | ||