2 * GPIO driver for NCT5104D
4 * Author: Tasanakorn Phaipool <tasanakorn@gmail.com>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
12 #include <linux/module.h>
13 #include <linux/init.h>
14 #include <linux/platform_device.h>
16 #include <linux/gpio.h>
17 #include <linux/version.h>
18 #include <linux/dmi.h>
19 #include <linux/string.h>
21 #define DRVNAME "gpio-nct5104d"
26 #define SIO_LDSEL 0x07 /* Logical device select */
27 #define SIO_CHIPID 0x20 /* Chaip ID (2 bytes) */
28 #define SIO_GPIO_ENABLE 0x30 /* GPIO enable */
29 #define SIO_GPIO1_MODE 0xE0 /* GPIO1 Mode OpenDrain/Push-Pull */
30 #define SIO_GPIO2_MODE 0xE1 /* GPIO2 Mode OpenDrain/Push-Pull */
32 #define SIO_LD_GPIO 0x07 /* GPIO logical device */
33 #define SIO_LD_GPIO_MODE 0x0F /* GPIO mode control device */
34 #define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */
35 #define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */
37 #define SIO_NCT5104D_ID 0x1061 /* Chip ID */
38 #define SIO_PCENGINES_APU_NCT5104D_ID 0xc452 /* Chip ID */
40 enum chips
{ nct5104d
};
42 static const char * const nct5104d_names
[] = {
51 struct nct5104d_gpio_bank
{
52 struct gpio_chip chip
;
54 struct nct5104d_gpio_data
*data
;
57 struct nct5104d_gpio_data
{
58 struct nct5104d_sio
*sio
;
60 struct nct5104d_gpio_bank
*bank
;
64 * Super-I/O functions.
67 static inline int superio_inb(int base
, int reg
)
73 static int superio_inw(int base
, int reg
)
78 val
= inb(base
+ 1) << 8;
85 static inline void superio_outb(int base
, int reg
, int val
)
91 static inline int superio_enter(int base
)
93 /* Don't step on other drivers' I/O space by accident. */
94 if (!request_muxed_region(base
, 2, DRVNAME
)) {
95 pr_err(DRVNAME
"I/O address 0x%04x already in use\n", base
);
99 /* According to the datasheet the key must be send twice. */
100 outb(SIO_UNLOCK_KEY
, base
);
101 outb(SIO_UNLOCK_KEY
, base
);
106 static inline void superio_select(int base
, int ld
)
108 outb(SIO_LDSEL
, base
);
112 static inline void superio_exit(int base
)
114 outb(SIO_LOCK_KEY
, base
);
115 release_region(base
, 2);
122 static int nct5104d_gpio_direction_in(struct gpio_chip
*chip
, unsigned offset
);
123 static int nct5104d_gpio_get(struct gpio_chip
*chip
, unsigned offset
);
124 static int nct5104d_gpio_direction_out(struct gpio_chip
*chip
,
125 unsigned offset
, int value
);
126 static void nct5104d_gpio_set(struct gpio_chip
*chip
, unsigned offset
, int value
);
128 #define NCT5104D_GPIO_BANK(_base, _ngpio, _regbase) \
132 .owner = THIS_MODULE, \
133 .direction_input = nct5104d_gpio_direction_in, \
134 .get = nct5104d_gpio_get, \
135 .direction_output = nct5104d_gpio_direction_out, \
136 .set = nct5104d_gpio_set, \
141 .regbase = _regbase, \
144 #define gpio_dir(base) (base + 0)
145 #define gpio_data(base) (base + 1)
147 static struct nct5104d_gpio_bank nct5104d_gpio_bank
[] = {
148 NCT5104D_GPIO_BANK(0 , 8, 0xE0),
149 NCT5104D_GPIO_BANK(10, 8, 0xE4)
152 static int nct5104d_gpio_direction_in(struct gpio_chip
*chip
, unsigned offset
)
155 struct nct5104d_gpio_bank
*bank
=
156 container_of(chip
, struct nct5104d_gpio_bank
, chip
);
157 struct nct5104d_sio
*sio
= bank
->data
->sio
;
160 err
= superio_enter(sio
->addr
);
163 superio_select(sio
->addr
, SIO_LD_GPIO
);
165 dir
= superio_inb(sio
->addr
, gpio_dir(bank
->regbase
));
166 dir
|= (1 << offset
);
167 superio_outb(sio
->addr
, gpio_dir(bank
->regbase
), dir
);
169 superio_exit(sio
->addr
);
174 static int nct5104d_gpio_get(struct gpio_chip
*chip
, unsigned offset
)
177 struct nct5104d_gpio_bank
*bank
=
178 container_of(chip
, struct nct5104d_gpio_bank
, chip
);
179 struct nct5104d_sio
*sio
= bank
->data
->sio
;
182 err
= superio_enter(sio
->addr
);
185 superio_select(sio
->addr
, SIO_LD_GPIO
);
187 data
= superio_inb(sio
->addr
, gpio_data(bank
->regbase
));
189 superio_exit(sio
->addr
);
191 return !!(data
& 1 << offset
);
194 static int nct5104d_gpio_direction_out(struct gpio_chip
*chip
,
195 unsigned offset
, int value
)
198 struct nct5104d_gpio_bank
*bank
=
199 container_of(chip
, struct nct5104d_gpio_bank
, chip
);
200 struct nct5104d_sio
*sio
= bank
->data
->sio
;
203 err
= superio_enter(sio
->addr
);
206 superio_select(sio
->addr
, SIO_LD_GPIO
);
208 data_out
= superio_inb(sio
->addr
, gpio_data(bank
->regbase
));
210 data_out
|= (1 << offset
);
212 data_out
&= ~(1 << offset
);
213 superio_outb(sio
->addr
, gpio_data(bank
->regbase
), data_out
);
215 dir
= superio_inb(sio
->addr
, gpio_dir(bank
->regbase
));
216 dir
&= ~(1 << offset
);
217 superio_outb(sio
->addr
, gpio_dir(bank
->regbase
), dir
);
219 superio_exit(sio
->addr
);
224 static void nct5104d_gpio_set(struct gpio_chip
*chip
, unsigned offset
, int value
)
227 struct nct5104d_gpio_bank
*bank
=
228 container_of(chip
, struct nct5104d_gpio_bank
, chip
);
229 struct nct5104d_sio
*sio
= bank
->data
->sio
;
232 err
= superio_enter(sio
->addr
);
235 superio_select(sio
->addr
, SIO_LD_GPIO
);
237 data_out
= superio_inb(sio
->addr
, gpio_data(bank
->regbase
));
239 data_out
|= (1 << offset
);
241 data_out
&= ~(1 << offset
);
242 superio_outb(sio
->addr
, gpio_data(bank
->regbase
), data_out
);
244 superio_exit(sio
->addr
);
248 * Platform device and driver.
251 static int nct5104d_gpio_probe(struct platform_device
*pdev
)
255 struct nct5104d_sio
*sio
= pdev
->dev
.platform_data
;
256 struct nct5104d_gpio_data
*data
;
258 data
= devm_kzalloc(&pdev
->dev
, sizeof(*data
), GFP_KERNEL
);
264 data
->nr_bank
= ARRAY_SIZE(nct5104d_gpio_bank
);
265 data
->bank
= nct5104d_gpio_bank
;
272 platform_set_drvdata(pdev
, data
);
274 /* For each GPIO bank, register a GPIO chip. */
275 for (i
= 0; i
< data
->nr_bank
; i
++) {
276 struct nct5104d_gpio_bank
*bank
= &data
->bank
[i
];
278 #if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0)
279 bank
->chip
.dev
= &pdev
->dev
;
281 bank
->chip
.parent
= &pdev
->dev
;
285 err
= gpiochip_add(&bank
->chip
);
288 "Failed to register gpiochip %d: %d\n",
297 for (i
= i
- 1; i
>= 0; i
--) {
298 struct nct5104d_gpio_bank
*bank
= &data
->bank
[i
];
300 #if LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0)
301 int rm_err
= gpiochip_remove(&bank
->chip
);
304 "Failed to remove gpiochip %d: %d\n",
306 #else /* LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0) */
307 gpiochip_remove (&bank
->chip
);
308 #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0) */
314 static int nct5104d_gpio_remove(struct platform_device
*pdev
)
317 struct nct5104d_gpio_data
*data
= platform_get_drvdata(pdev
);
319 for (i
= 0; i
< data
->nr_bank
; i
++) {
320 struct nct5104d_gpio_bank
*bank
= &data
->bank
[i
];
322 #if LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0)
323 int err
= gpiochip_remove(&bank
->chip
);
326 "Failed to remove GPIO gpiochip %d: %d\n",
330 #else /* LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0) */
331 gpiochip_remove (&bank
->chip
);
332 #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0) */
338 static int __init
nct5104d_find(int addr
, struct nct5104d_sio
*sio
)
344 err
= superio_enter(addr
);
350 devid
= superio_inw(addr
, SIO_CHIPID
);
352 case SIO_NCT5104D_ID
:
353 case SIO_PCENGINES_APU_NCT5104D_ID
:
354 sio
->type
= nct5104d
;
355 /* enable GPIO0 and GPIO1 */
356 superio_select(addr
, SIO_LD_GPIO
);
357 gpio_cfg
= superio_inb(addr
, SIO_GPIO_ENABLE
);
359 superio_outb(addr
, SIO_GPIO_ENABLE
, gpio_cfg
);
362 pr_info(DRVNAME
": Unsupported device 0x%04x\n", devid
);
368 pr_info(DRVNAME
": Found %s at %#x chip id 0x%04x\n",
369 nct5104d_names
[sio
->type
],
371 (int) superio_inw(addr
, SIO_CHIPID
));
373 superio_select(sio
->addr
, SIO_LD_GPIO_MODE
);
374 superio_outb(sio
->addr
, SIO_GPIO1_MODE
, 0x0);
375 superio_outb(sio
->addr
, SIO_GPIO2_MODE
, 0x0);
382 static struct platform_device
*nct5104d_gpio_pdev
;
385 nct5104d_gpio_device_add(const struct nct5104d_sio
*sio
)
389 nct5104d_gpio_pdev
= platform_device_alloc(DRVNAME
, -1);
390 if (!nct5104d_gpio_pdev
)
391 pr_err(DRVNAME
": Error platform_device_alloc\n");
392 if (!nct5104d_gpio_pdev
)
395 err
= platform_device_add_data(nct5104d_gpio_pdev
,
398 pr_err(DRVNAME
"Platform data allocation failed\n");
402 err
= platform_device_add(nct5104d_gpio_pdev
);
404 pr_err(DRVNAME
"Device addition failed\n");
407 pr_info(DRVNAME
": Device added\n");
411 platform_device_put(nct5104d_gpio_pdev
);
419 static struct platform_driver nct5104d_gpio_driver
= {
421 .owner
= THIS_MODULE
,
424 .probe
= nct5104d_gpio_probe
,
425 .remove
= nct5104d_gpio_remove
,
428 static int __init
nct5104d_gpio_init(void)
431 struct nct5104d_sio sio
;
432 const char *board_vendor
= dmi_get_system_info(DMI_BOARD_VENDOR
);
433 const char *board_name
= dmi_get_system_info(DMI_BOARD_NAME
);
435 /* Make sure we only run on PC Engine APU boards */
436 if (!board_name
|| !board_vendor
|| strcasecmp(board_vendor
, "PC Engines") || strncasecmp(board_name
, "apu", 3)) {
440 if (nct5104d_find(0x2e, &sio
) &&
441 nct5104d_find(0x4e, &sio
))
444 err
= platform_driver_register(&nct5104d_gpio_driver
);
446 pr_info(DRVNAME
": platform_driver_register\n");
447 err
= nct5104d_gpio_device_add(&sio
);
449 platform_driver_unregister(&nct5104d_gpio_driver
);
454 subsys_initcall(nct5104d_gpio_init
);
456 static void __exit
nct5104d_gpio_exit(void)
458 platform_device_unregister(nct5104d_gpio_pdev
);
459 platform_driver_unregister(&nct5104d_gpio_driver
);
461 module_exit(nct5104d_gpio_exit
);
463 MODULE_DESCRIPTION("GPIO driver for Super-I/O chips NCT5104D");
464 MODULE_AUTHOR("Tasanakorn Phaipool <tasanakorn@gmail.com>");
465 MODULE_LICENSE("GPL");