OpenWrt – Blame information for rev 2
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /* |
2 | * GPIO driver for NCT5104D |
||
3 | * |
||
4 | * Author: Tasanakorn Phaipool <tasanakorn@gmail.com> |
||
5 | * |
||
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. |
||
10 | */ |
||
11 | |||
12 | #include <linux/module.h> |
||
13 | #include <linux/init.h> |
||
14 | #include <linux/platform_device.h> |
||
15 | #include <linux/io.h> |
||
16 | #include <linux/gpio.h> |
||
17 | #include <linux/version.h> |
||
18 | #include <linux/dmi.h> |
||
19 | #include <linux/string.h> |
||
20 | |||
21 | #define DRVNAME "gpio-nct5104d" |
||
22 | |||
23 | /* |
||
24 | * Super-I/O registers |
||
25 | */ |
||
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 */ |
||
31 | |||
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 */ |
||
36 | |||
37 | #define SIO_NCT5104D_ID 0x1061 /* Chip ID */ |
||
38 | #define SIO_PCENGINES_APU_NCT5104D_ID1 0xc452 /* Chip ID */ |
||
39 | #define SIO_PCENGINES_APU_NCT5104D_ID2 0xc453 /* Chip ID */ |
||
40 | |||
41 | enum chips { nct5104d }; |
||
42 | |||
43 | static const char * const nct5104d_names[] = { |
||
44 | "nct5104d" |
||
45 | }; |
||
46 | |||
47 | struct nct5104d_sio { |
||
48 | int addr; |
||
49 | enum chips type; |
||
50 | }; |
||
51 | |||
52 | struct nct5104d_gpio_bank { |
||
53 | struct gpio_chip chip; |
||
54 | unsigned int regbase; |
||
55 | struct nct5104d_gpio_data *data; |
||
56 | }; |
||
57 | |||
58 | struct nct5104d_gpio_data { |
||
59 | struct nct5104d_sio *sio; |
||
60 | int nr_bank; |
||
61 | struct nct5104d_gpio_bank *bank; |
||
62 | }; |
||
63 | |||
64 | /* |
||
65 | * Super-I/O functions. |
||
66 | */ |
||
67 | |||
68 | static inline int superio_inb(int base, int reg) |
||
69 | { |
||
70 | outb(reg, base); |
||
71 | return inb(base + 1); |
||
72 | } |
||
73 | |||
74 | static int superio_inw(int base, int reg) |
||
75 | { |
||
76 | int val; |
||
77 | |||
78 | outb(reg++, base); |
||
79 | val = inb(base + 1) << 8; |
||
80 | outb(reg, base); |
||
81 | val |= inb(base + 1); |
||
82 | |||
83 | return val; |
||
84 | } |
||
85 | |||
86 | static inline void superio_outb(int base, int reg, int val) |
||
87 | { |
||
88 | outb(reg, base); |
||
89 | outb(val, base + 1); |
||
90 | } |
||
91 | |||
92 | static inline int superio_enter(int base) |
||
93 | { |
||
94 | /* Don't step on other drivers' I/O space by accident. */ |
||
95 | if (!request_muxed_region(base, 2, DRVNAME)) { |
||
96 | pr_err(DRVNAME "I/O address 0x%04x already in use\n", base); |
||
97 | return -EBUSY; |
||
98 | } |
||
99 | |||
100 | /* According to the datasheet the key must be send twice. */ |
||
101 | outb(SIO_UNLOCK_KEY, base); |
||
102 | outb(SIO_UNLOCK_KEY, base); |
||
103 | |||
104 | return 0; |
||
105 | } |
||
106 | |||
107 | static inline void superio_select(int base, int ld) |
||
108 | { |
||
109 | outb(SIO_LDSEL, base); |
||
110 | outb(ld, base + 1); |
||
111 | } |
||
112 | |||
113 | static inline void superio_exit(int base) |
||
114 | { |
||
115 | outb(SIO_LOCK_KEY, base); |
||
116 | release_region(base, 2); |
||
117 | } |
||
118 | |||
119 | /* |
||
120 | * GPIO chip. |
||
121 | */ |
||
122 | |||
123 | static int nct5104d_gpio_direction_in(struct gpio_chip *chip, unsigned offset); |
||
124 | static int nct5104d_gpio_get(struct gpio_chip *chip, unsigned offset); |
||
125 | static int nct5104d_gpio_direction_out(struct gpio_chip *chip, |
||
126 | unsigned offset, int value); |
||
127 | static void nct5104d_gpio_set(struct gpio_chip *chip, unsigned offset, int value); |
||
128 | |||
129 | #define NCT5104D_GPIO_BANK(_base, _ngpio, _regbase) \ |
||
130 | { \ |
||
131 | .chip = { \ |
||
132 | .label = DRVNAME, \ |
||
133 | .owner = THIS_MODULE, \ |
||
134 | .direction_input = nct5104d_gpio_direction_in, \ |
||
135 | .get = nct5104d_gpio_get, \ |
||
136 | .direction_output = nct5104d_gpio_direction_out, \ |
||
137 | .set = nct5104d_gpio_set, \ |
||
138 | .base = _base, \ |
||
139 | .ngpio = _ngpio, \ |
||
140 | .can_sleep = true, \ |
||
141 | }, \ |
||
142 | .regbase = _regbase, \ |
||
143 | } |
||
144 | |||
145 | #define gpio_dir(base) (base + 0) |
||
146 | #define gpio_data(base) (base + 1) |
||
147 | |||
148 | static struct nct5104d_gpio_bank nct5104d_gpio_bank[] = { |
||
149 | NCT5104D_GPIO_BANK(0 , 8, 0xE0), |
||
150 | NCT5104D_GPIO_BANK(10, 8, 0xE4) |
||
151 | }; |
||
152 | |||
153 | static int nct5104d_gpio_direction_in(struct gpio_chip *chip, unsigned offset) |
||
154 | { |
||
155 | int err; |
||
156 | struct nct5104d_gpio_bank *bank = |
||
157 | container_of(chip, struct nct5104d_gpio_bank, chip); |
||
158 | struct nct5104d_sio *sio = bank->data->sio; |
||
159 | u8 dir; |
||
160 | |||
161 | err = superio_enter(sio->addr); |
||
162 | if (err) |
||
163 | return err; |
||
164 | superio_select(sio->addr, SIO_LD_GPIO); |
||
165 | |||
166 | dir = superio_inb(sio->addr, gpio_dir(bank->regbase)); |
||
167 | dir |= (1 << offset); |
||
168 | superio_outb(sio->addr, gpio_dir(bank->regbase), dir); |
||
169 | |||
170 | superio_exit(sio->addr); |
||
171 | |||
172 | return 0; |
||
173 | } |
||
174 | |||
175 | static int nct5104d_gpio_get(struct gpio_chip *chip, unsigned offset) |
||
176 | { |
||
177 | int err; |
||
178 | struct nct5104d_gpio_bank *bank = |
||
179 | container_of(chip, struct nct5104d_gpio_bank, chip); |
||
180 | struct nct5104d_sio *sio = bank->data->sio; |
||
181 | u8 data; |
||
182 | |||
183 | err = superio_enter(sio->addr); |
||
184 | if (err) |
||
185 | return err; |
||
186 | superio_select(sio->addr, SIO_LD_GPIO); |
||
187 | |||
188 | data = superio_inb(sio->addr, gpio_data(bank->regbase)); |
||
189 | |||
190 | superio_exit(sio->addr); |
||
191 | |||
192 | return !!(data & 1 << offset); |
||
193 | } |
||
194 | |||
195 | static int nct5104d_gpio_direction_out(struct gpio_chip *chip, |
||
196 | unsigned offset, int value) |
||
197 | { |
||
198 | int err; |
||
199 | struct nct5104d_gpio_bank *bank = |
||
200 | container_of(chip, struct nct5104d_gpio_bank, chip); |
||
201 | struct nct5104d_sio *sio = bank->data->sio; |
||
202 | u8 dir, data_out; |
||
203 | |||
204 | err = superio_enter(sio->addr); |
||
205 | if (err) |
||
206 | return err; |
||
207 | superio_select(sio->addr, SIO_LD_GPIO); |
||
208 | |||
209 | data_out = superio_inb(sio->addr, gpio_data(bank->regbase)); |
||
210 | if (value) |
||
211 | data_out |= (1 << offset); |
||
212 | else |
||
213 | data_out &= ~(1 << offset); |
||
214 | superio_outb(sio->addr, gpio_data(bank->regbase), data_out); |
||
215 | |||
216 | dir = superio_inb(sio->addr, gpio_dir(bank->regbase)); |
||
217 | dir &= ~(1 << offset); |
||
218 | superio_outb(sio->addr, gpio_dir(bank->regbase), dir); |
||
219 | |||
220 | superio_exit(sio->addr); |
||
221 | |||
222 | return 0; |
||
223 | } |
||
224 | |||
225 | static void nct5104d_gpio_set(struct gpio_chip *chip, unsigned offset, int value) |
||
226 | { |
||
227 | int err; |
||
228 | struct nct5104d_gpio_bank *bank = |
||
229 | container_of(chip, struct nct5104d_gpio_bank, chip); |
||
230 | struct nct5104d_sio *sio = bank->data->sio; |
||
231 | u8 data_out; |
||
232 | |||
233 | err = superio_enter(sio->addr); |
||
234 | if (err) |
||
235 | return; |
||
236 | superio_select(sio->addr, SIO_LD_GPIO); |
||
237 | |||
238 | data_out = superio_inb(sio->addr, gpio_data(bank->regbase)); |
||
239 | if (value) |
||
240 | data_out |= (1 << offset); |
||
241 | else |
||
242 | data_out &= ~(1 << offset); |
||
243 | superio_outb(sio->addr, gpio_data(bank->regbase), data_out); |
||
244 | |||
245 | superio_exit(sio->addr); |
||
246 | } |
||
247 | |||
248 | /* |
||
249 | * Platform device and driver. |
||
250 | */ |
||
251 | |||
252 | static int nct5104d_gpio_probe(struct platform_device *pdev) |
||
253 | { |
||
254 | int err; |
||
255 | int i; |
||
256 | struct nct5104d_sio *sio = pdev->dev.platform_data; |
||
257 | struct nct5104d_gpio_data *data; |
||
258 | |||
259 | data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); |
||
260 | if (!data) |
||
261 | return -ENOMEM; |
||
262 | |||
263 | switch (sio->type) { |
||
264 | case nct5104d: |
||
265 | data->nr_bank = ARRAY_SIZE(nct5104d_gpio_bank); |
||
266 | data->bank = nct5104d_gpio_bank; |
||
267 | break; |
||
268 | default: |
||
269 | return -ENODEV; |
||
270 | } |
||
271 | data->sio = sio; |
||
272 | |||
273 | platform_set_drvdata(pdev, data); |
||
274 | |||
275 | /* For each GPIO bank, register a GPIO chip. */ |
||
276 | for (i = 0; i < data->nr_bank; i++) { |
||
277 | struct nct5104d_gpio_bank *bank = &data->bank[i]; |
||
278 | |||
279 | #if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0) |
||
280 | bank->chip.dev = &pdev->dev; |
||
281 | #else |
||
282 | bank->chip.parent = &pdev->dev; |
||
283 | #endif |
||
284 | bank->data = data; |
||
285 | |||
286 | err = gpiochip_add(&bank->chip); |
||
287 | if (err) { |
||
288 | dev_err(&pdev->dev, |
||
289 | "Failed to register gpiochip %d: %d\n", |
||
290 | i, err); |
||
291 | goto err_gpiochip; |
||
292 | } |
||
293 | } |
||
294 | |||
295 | return 0; |
||
296 | |||
297 | err_gpiochip: |
||
298 | for (i = i - 1; i >= 0; i--) { |
||
299 | struct nct5104d_gpio_bank *bank = &data->bank[i]; |
||
300 | |||
301 | #if LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0) |
||
302 | int rm_err = gpiochip_remove(&bank->chip); |
||
303 | if (rm_err < 0) |
||
304 | dev_err(&pdev->dev, |
||
305 | "Failed to remove gpiochip %d: %d\n", |
||
306 | i, rm_err); |
||
307 | #else /* LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0) */ |
||
308 | gpiochip_remove (&bank->chip); |
||
309 | #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0) */ |
||
310 | } |
||
311 | |||
312 | return err; |
||
313 | } |
||
314 | |||
315 | static int nct5104d_gpio_remove(struct platform_device *pdev) |
||
316 | { |
||
317 | int i; |
||
318 | struct nct5104d_gpio_data *data = platform_get_drvdata(pdev); |
||
319 | |||
320 | for (i = 0; i < data->nr_bank; i++) { |
||
321 | struct nct5104d_gpio_bank *bank = &data->bank[i]; |
||
322 | |||
323 | #if LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0) |
||
324 | int err = gpiochip_remove(&bank->chip); |
||
325 | if (err) { |
||
326 | dev_err(&pdev->dev, |
||
327 | "Failed to remove GPIO gpiochip %d: %d\n", |
||
328 | i, err); |
||
329 | return err; |
||
330 | } |
||
331 | #else /* LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0) */ |
||
332 | gpiochip_remove (&bank->chip); |
||
333 | #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0) */ |
||
334 | } |
||
335 | |||
336 | return 0; |
||
337 | } |
||
338 | |||
339 | static int __init nct5104d_find(int addr, struct nct5104d_sio *sio) |
||
340 | { |
||
341 | int err; |
||
342 | u16 devid; |
||
343 | u8 gpio_cfg; |
||
344 | |||
345 | err = superio_enter(addr); |
||
346 | if (err) |
||
347 | return err; |
||
348 | |||
349 | err = -ENODEV; |
||
350 | |||
351 | devid = superio_inw(addr, SIO_CHIPID); |
||
352 | switch (devid) { |
||
353 | case SIO_NCT5104D_ID: |
||
354 | case SIO_PCENGINES_APU_NCT5104D_ID1: |
||
355 | case SIO_PCENGINES_APU_NCT5104D_ID2: |
||
356 | sio->type = nct5104d; |
||
357 | /* enable GPIO0 and GPIO1 */ |
||
358 | superio_select(addr, SIO_LD_GPIO); |
||
359 | gpio_cfg = superio_inb(addr, SIO_GPIO_ENABLE); |
||
360 | gpio_cfg |= 0x03; |
||
361 | superio_outb(addr, SIO_GPIO_ENABLE, gpio_cfg); |
||
362 | break; |
||
363 | default: |
||
364 | pr_info(DRVNAME ": Unsupported device 0x%04x\n", devid); |
||
365 | goto err; |
||
366 | } |
||
367 | sio->addr = addr; |
||
368 | err = 0; |
||
369 | |||
370 | pr_info(DRVNAME ": Found %s at %#x chip id 0x%04x\n", |
||
371 | nct5104d_names[sio->type], |
||
372 | (unsigned int) addr, |
||
373 | (int) superio_inw(addr, SIO_CHIPID)); |
||
374 | |||
375 | superio_select(sio->addr, SIO_LD_GPIO_MODE); |
||
376 | superio_outb(sio->addr, SIO_GPIO1_MODE, 0x0); |
||
377 | superio_outb(sio->addr, SIO_GPIO2_MODE, 0x0); |
||
378 | |||
379 | err: |
||
380 | superio_exit(addr); |
||
381 | return err; |
||
382 | } |
||
383 | |||
384 | static struct platform_device *nct5104d_gpio_pdev; |
||
385 | |||
386 | static int __init |
||
387 | nct5104d_gpio_device_add(const struct nct5104d_sio *sio) |
||
388 | { |
||
389 | int err; |
||
390 | |||
391 | nct5104d_gpio_pdev = platform_device_alloc(DRVNAME, -1); |
||
392 | if (!nct5104d_gpio_pdev) |
||
393 | pr_err(DRVNAME ": Error platform_device_alloc\n"); |
||
394 | if (!nct5104d_gpio_pdev) |
||
395 | return -ENOMEM; |
||
396 | |||
397 | err = platform_device_add_data(nct5104d_gpio_pdev, |
||
398 | sio, sizeof(*sio)); |
||
399 | if (err) { |
||
400 | pr_err(DRVNAME "Platform data allocation failed\n"); |
||
401 | goto err; |
||
402 | } |
||
403 | |||
404 | err = platform_device_add(nct5104d_gpio_pdev); |
||
405 | if (err) { |
||
406 | pr_err(DRVNAME "Device addition failed\n"); |
||
407 | goto err; |
||
408 | } |
||
409 | pr_info(DRVNAME ": Device added\n"); |
||
410 | return 0; |
||
411 | |||
412 | err: |
||
413 | platform_device_put(nct5104d_gpio_pdev); |
||
414 | |||
415 | return err; |
||
416 | } |
||
417 | |||
418 | /* |
||
419 | */ |
||
420 | |||
421 | static struct platform_driver nct5104d_gpio_driver = { |
||
422 | .driver = { |
||
423 | .owner = THIS_MODULE, |
||
424 | .name = DRVNAME, |
||
425 | }, |
||
426 | .probe = nct5104d_gpio_probe, |
||
427 | .remove = nct5104d_gpio_remove, |
||
428 | }; |
||
429 | |||
430 | static int __init nct5104d_gpio_init(void) |
||
431 | { |
||
432 | int err; |
||
433 | struct nct5104d_sio sio; |
||
434 | const char *board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); |
||
435 | const char *board_name = dmi_get_system_info(DMI_BOARD_NAME); |
||
436 | |||
437 | if (nct5104d_find(0x2e, &sio) && |
||
438 | nct5104d_find(0x4e, &sio)) |
||
439 | return -ENODEV; |
||
440 | |||
441 | err = platform_driver_register(&nct5104d_gpio_driver); |
||
442 | if (!err) { |
||
443 | pr_info(DRVNAME ": platform_driver_register\n"); |
||
444 | err = nct5104d_gpio_device_add(&sio); |
||
445 | if (err) |
||
446 | platform_driver_unregister(&nct5104d_gpio_driver); |
||
447 | } |
||
448 | |||
449 | return err; |
||
450 | } |
||
451 | subsys_initcall(nct5104d_gpio_init); |
||
452 | |||
453 | static void __exit nct5104d_gpio_exit(void) |
||
454 | { |
||
455 | platform_device_unregister(nct5104d_gpio_pdev); |
||
456 | platform_driver_unregister(&nct5104d_gpio_driver); |
||
457 | } |
||
458 | module_exit(nct5104d_gpio_exit); |
||
459 | |||
460 | MODULE_DESCRIPTION("GPIO driver for Super-I/O chips NCT5104D"); |
||
461 | MODULE_AUTHOR("Tasanakorn Phaipool <tasanakorn@gmail.com>"); |
||
462 | MODULE_LICENSE("GPL"); |