Commit | Line | Data |
---|---|---|
9d5b72de LP |
1 | /* |
2 | * Nano River Technologies viperboard GPIO lib driver | |
3 | * | |
4 | * (C) 2012 by Lemonage GmbH | |
5 | * Author: Lars Poeschel <poeschel@lemonage.de> | |
6 | * All rights reserved. | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms of the GNU General Public License as published by the | |
10 | * Free Software Foundation; either version 2 of the License, or (at your | |
11 | * option) any later version. | |
12 | * | |
13 | */ | |
14 | ||
15 | #include <linux/kernel.h> | |
16 | #include <linux/errno.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/slab.h> | |
19 | #include <linux/types.h> | |
20 | #include <linux/mutex.h> | |
21 | #include <linux/platform_device.h> | |
22 | ||
23 | #include <linux/usb.h> | |
24 | #include <linux/gpio.h> | |
25 | ||
26 | #include <linux/mfd/viperboard.h> | |
27 | ||
28 | #define VPRBRD_GPIOA_CLK_1MHZ 0 | |
29 | #define VPRBRD_GPIOA_CLK_100KHZ 1 | |
30 | #define VPRBRD_GPIOA_CLK_10KHZ 2 | |
31 | #define VPRBRD_GPIOA_CLK_1KHZ 3 | |
32 | #define VPRBRD_GPIOA_CLK_100HZ 4 | |
33 | #define VPRBRD_GPIOA_CLK_10HZ 5 | |
34 | ||
35 | #define VPRBRD_GPIOA_FREQ_DEFAULT 1000 | |
36 | ||
37 | #define VPRBRD_GPIOA_CMD_CONT 0x00 | |
38 | #define VPRBRD_GPIOA_CMD_PULSE 0x01 | |
39 | #define VPRBRD_GPIOA_CMD_PWM 0x02 | |
40 | #define VPRBRD_GPIOA_CMD_SETOUT 0x03 | |
41 | #define VPRBRD_GPIOA_CMD_SETIN 0x04 | |
42 | #define VPRBRD_GPIOA_CMD_SETINT 0x05 | |
43 | #define VPRBRD_GPIOA_CMD_GETIN 0x06 | |
44 | ||
45 | #define VPRBRD_GPIOB_CMD_SETDIR 0x00 | |
46 | #define VPRBRD_GPIOB_CMD_SETVAL 0x01 | |
47 | ||
48 | struct vprbrd_gpioa_msg { | |
49 | u8 cmd; | |
50 | u8 clk; | |
51 | u8 offset; | |
52 | u8 t1; | |
53 | u8 t2; | |
54 | u8 invert; | |
55 | u8 pwmlevel; | |
56 | u8 outval; | |
57 | u8 risefall; | |
58 | u8 answer; | |
59 | u8 __fill; | |
60 | } __packed; | |
61 | ||
62 | struct vprbrd_gpiob_msg { | |
63 | u8 cmd; | |
64 | u16 val; | |
65 | u16 mask; | |
66 | } __packed; | |
67 | ||
68 | struct vprbrd_gpio { | |
69 | struct gpio_chip gpioa; /* gpio a related things */ | |
70 | u32 gpioa_out; | |
71 | u32 gpioa_val; | |
72 | struct gpio_chip gpiob; /* gpio b related things */ | |
73 | u32 gpiob_out; | |
74 | u32 gpiob_val; | |
75 | struct vprbrd *vb; | |
76 | }; | |
77 | ||
78 | /* gpioa sampling clock module parameter */ | |
79 | static unsigned char gpioa_clk; | |
80 | static unsigned int gpioa_freq = VPRBRD_GPIOA_FREQ_DEFAULT; | |
81 | module_param(gpioa_freq, uint, 0); | |
82 | MODULE_PARM_DESC(gpioa_freq, | |
83 | "gpio-a sampling freq in Hz (default is 1000Hz) valid values: 10, 100, 1000, 10000, 100000, 1000000"); | |
84 | ||
85 | /* ----- begin of gipo a chip -------------------------------------------- */ | |
86 | ||
87 | static int vprbrd_gpioa_get(struct gpio_chip *chip, | |
88 | unsigned offset) | |
89 | { | |
90 | int ret, answer, error = 0; | |
91 | struct vprbrd_gpio *gpio = | |
92 | container_of(chip, struct vprbrd_gpio, gpioa); | |
93 | struct vprbrd *vb = gpio->vb; | |
94 | struct vprbrd_gpioa_msg *gamsg = (struct vprbrd_gpioa_msg *)vb->buf; | |
95 | ||
96 | /* if io is set to output, just return the saved value */ | |
97 | if (gpio->gpioa_out & (1 << offset)) | |
98 | return gpio->gpioa_val & (1 << offset); | |
99 | ||
100 | mutex_lock(&vb->lock); | |
101 | ||
102 | gamsg->cmd = VPRBRD_GPIOA_CMD_GETIN; | |
103 | gamsg->clk = 0x00; | |
104 | gamsg->offset = offset; | |
105 | gamsg->t1 = 0x00; | |
106 | gamsg->t2 = 0x00; | |
107 | gamsg->invert = 0x00; | |
108 | gamsg->pwmlevel = 0x00; | |
109 | gamsg->outval = 0x00; | |
110 | gamsg->risefall = 0x00; | |
111 | gamsg->answer = 0x00; | |
112 | gamsg->__fill = 0x00; | |
113 | ||
114 | ret = usb_control_msg(vb->usb_dev, usb_sndctrlpipe(vb->usb_dev, 0), | |
115 | VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_OUT, 0x0000, | |
116 | 0x0000, gamsg, sizeof(struct vprbrd_gpioa_msg), | |
117 | VPRBRD_USB_TIMEOUT_MS); | |
118 | if (ret != sizeof(struct vprbrd_gpioa_msg)) | |
119 | error = -EREMOTEIO; | |
120 | ||
121 | ret = usb_control_msg(vb->usb_dev, usb_rcvctrlpipe(vb->usb_dev, 0), | |
122 | VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_IN, 0x0000, | |
123 | 0x0000, gamsg, sizeof(struct vprbrd_gpioa_msg), | |
124 | VPRBRD_USB_TIMEOUT_MS); | |
125 | answer = gamsg->answer & 0x01; | |
126 | ||
127 | mutex_unlock(&vb->lock); | |
128 | ||
129 | if (ret != sizeof(struct vprbrd_gpioa_msg)) | |
130 | error = -EREMOTEIO; | |
131 | ||
132 | if (error) | |
133 | return error; | |
134 | ||
135 | return answer; | |
136 | } | |
137 | ||
138 | static void vprbrd_gpioa_set(struct gpio_chip *chip, | |
139 | unsigned offset, int value) | |
140 | { | |
141 | int ret; | |
142 | struct vprbrd_gpio *gpio = | |
143 | container_of(chip, struct vprbrd_gpio, gpioa); | |
144 | struct vprbrd *vb = gpio->vb; | |
145 | struct vprbrd_gpioa_msg *gamsg = (struct vprbrd_gpioa_msg *)vb->buf; | |
146 | ||
147 | if (gpio->gpioa_out & (1 << offset)) { | |
148 | if (value) | |
149 | gpio->gpioa_val |= (1 << offset); | |
150 | else | |
151 | gpio->gpioa_val &= ~(1 << offset); | |
152 | ||
153 | mutex_lock(&vb->lock); | |
154 | ||
155 | gamsg->cmd = VPRBRD_GPIOA_CMD_SETOUT; | |
156 | gamsg->clk = 0x00; | |
157 | gamsg->offset = offset; | |
158 | gamsg->t1 = 0x00; | |
159 | gamsg->t2 = 0x00; | |
160 | gamsg->invert = 0x00; | |
161 | gamsg->pwmlevel = 0x00; | |
162 | gamsg->outval = value; | |
163 | gamsg->risefall = 0x00; | |
164 | gamsg->answer = 0x00; | |
165 | gamsg->__fill = 0x00; | |
166 | ||
167 | ret = usb_control_msg(vb->usb_dev, | |
168 | usb_sndctrlpipe(vb->usb_dev, 0), | |
169 | VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_OUT, | |
170 | 0x0000, 0x0000, gamsg, | |
171 | sizeof(struct vprbrd_gpioa_msg), VPRBRD_USB_TIMEOUT_MS); | |
172 | ||
173 | mutex_unlock(&vb->lock); | |
174 | ||
175 | if (ret != sizeof(struct vprbrd_gpioa_msg)) | |
176 | dev_err(chip->dev, "usb error setting pin value\n"); | |
177 | } | |
178 | } | |
179 | ||
180 | static int vprbrd_gpioa_direction_input(struct gpio_chip *chip, | |
181 | unsigned offset) | |
182 | { | |
183 | int ret; | |
184 | struct vprbrd_gpio *gpio = | |
185 | container_of(chip, struct vprbrd_gpio, gpioa); | |
186 | struct vprbrd *vb = gpio->vb; | |
187 | struct vprbrd_gpioa_msg *gamsg = (struct vprbrd_gpioa_msg *)vb->buf; | |
188 | ||
189 | gpio->gpioa_out &= ~(1 << offset); | |
190 | ||
191 | mutex_lock(&vb->lock); | |
192 | ||
193 | gamsg->cmd = VPRBRD_GPIOA_CMD_SETIN; | |
194 | gamsg->clk = gpioa_clk; | |
195 | gamsg->offset = offset; | |
196 | gamsg->t1 = 0x00; | |
197 | gamsg->t2 = 0x00; | |
198 | gamsg->invert = 0x00; | |
199 | gamsg->pwmlevel = 0x00; | |
200 | gamsg->outval = 0x00; | |
201 | gamsg->risefall = 0x00; | |
202 | gamsg->answer = 0x00; | |
203 | gamsg->__fill = 0x00; | |
204 | ||
205 | ret = usb_control_msg(vb->usb_dev, usb_sndctrlpipe(vb->usb_dev, 0), | |
206 | VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_OUT, 0x0000, | |
207 | 0x0000, gamsg, sizeof(struct vprbrd_gpioa_msg), | |
208 | VPRBRD_USB_TIMEOUT_MS); | |
209 | ||
210 | mutex_unlock(&vb->lock); | |
211 | ||
212 | if (ret != sizeof(struct vprbrd_gpioa_msg)) | |
213 | return -EREMOTEIO; | |
214 | ||
215 | return 0; | |
216 | } | |
217 | ||
218 | static int vprbrd_gpioa_direction_output(struct gpio_chip *chip, | |
219 | unsigned offset, int value) | |
220 | { | |
221 | int ret; | |
222 | struct vprbrd_gpio *gpio = | |
223 | container_of(chip, struct vprbrd_gpio, gpioa); | |
224 | struct vprbrd *vb = gpio->vb; | |
225 | struct vprbrd_gpioa_msg *gamsg = (struct vprbrd_gpioa_msg *)vb->buf; | |
226 | ||
227 | gpio->gpioa_out |= (1 << offset); | |
228 | if (value) | |
229 | gpio->gpioa_val |= (1 << offset); | |
230 | else | |
231 | gpio->gpioa_val &= ~(1 << offset); | |
232 | ||
233 | mutex_lock(&vb->lock); | |
234 | ||
235 | gamsg->cmd = VPRBRD_GPIOA_CMD_SETOUT; | |
236 | gamsg->clk = 0x00; | |
237 | gamsg->offset = offset; | |
238 | gamsg->t1 = 0x00; | |
239 | gamsg->t2 = 0x00; | |
240 | gamsg->invert = 0x00; | |
241 | gamsg->pwmlevel = 0x00; | |
242 | gamsg->outval = value; | |
243 | gamsg->risefall = 0x00; | |
244 | gamsg->answer = 0x00; | |
245 | gamsg->__fill = 0x00; | |
246 | ||
247 | ret = usb_control_msg(vb->usb_dev, usb_sndctrlpipe(vb->usb_dev, 0), | |
248 | VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_OUT, 0x0000, | |
249 | 0x0000, gamsg, sizeof(struct vprbrd_gpioa_msg), | |
250 | VPRBRD_USB_TIMEOUT_MS); | |
251 | ||
252 | mutex_unlock(&vb->lock); | |
253 | ||
254 | if (ret != sizeof(struct vprbrd_gpioa_msg)) | |
255 | return -EREMOTEIO; | |
256 | ||
257 | return 0; | |
258 | } | |
259 | ||
260 | /* ----- end of gpio a chip ---------------------------------------------- */ | |
261 | ||
262 | /* ----- begin of gipo b chip -------------------------------------------- */ | |
263 | ||
264 | static int vprbrd_gpiob_setdir(struct vprbrd *vb, unsigned offset, | |
265 | unsigned dir) | |
266 | { | |
267 | struct vprbrd_gpiob_msg *gbmsg = (struct vprbrd_gpiob_msg *)vb->buf; | |
268 | int ret; | |
269 | ||
270 | gbmsg->cmd = VPRBRD_GPIOB_CMD_SETDIR; | |
271 | gbmsg->val = cpu_to_be16(dir << offset); | |
272 | gbmsg->mask = cpu_to_be16(0x0001 << offset); | |
273 | ||
274 | ret = usb_control_msg(vb->usb_dev, usb_sndctrlpipe(vb->usb_dev, 0), | |
275 | VPRBRD_USB_REQUEST_GPIOB, VPRBRD_USB_TYPE_OUT, 0x0000, | |
276 | 0x0000, gbmsg, sizeof(struct vprbrd_gpiob_msg), | |
277 | VPRBRD_USB_TIMEOUT_MS); | |
278 | ||
279 | if (ret != sizeof(struct vprbrd_gpiob_msg)) | |
280 | return -EREMOTEIO; | |
281 | ||
282 | return 0; | |
283 | } | |
284 | ||
285 | static int vprbrd_gpiob_get(struct gpio_chip *chip, | |
286 | unsigned offset) | |
287 | { | |
288 | int ret; | |
289 | u16 val; | |
290 | struct vprbrd_gpio *gpio = | |
291 | container_of(chip, struct vprbrd_gpio, gpiob); | |
292 | struct vprbrd *vb = gpio->vb; | |
293 | struct vprbrd_gpiob_msg *gbmsg = (struct vprbrd_gpiob_msg *)vb->buf; | |
294 | ||
295 | /* if io is set to output, just return the saved value */ | |
296 | if (gpio->gpiob_out & (1 << offset)) | |
297 | return gpio->gpiob_val & (1 << offset); | |
298 | ||
299 | mutex_lock(&vb->lock); | |
300 | ||
301 | ret = usb_control_msg(vb->usb_dev, usb_rcvctrlpipe(vb->usb_dev, 0), | |
302 | VPRBRD_USB_REQUEST_GPIOB, VPRBRD_USB_TYPE_IN, 0x0000, | |
303 | 0x0000, gbmsg, sizeof(struct vprbrd_gpiob_msg), | |
304 | VPRBRD_USB_TIMEOUT_MS); | |
305 | val = gbmsg->val; | |
306 | ||
307 | mutex_unlock(&vb->lock); | |
308 | ||
309 | if (ret != sizeof(struct vprbrd_gpiob_msg)) | |
310 | return ret; | |
311 | ||
312 | /* cache the read values */ | |
313 | gpio->gpiob_val = be16_to_cpu(val); | |
314 | ||
315 | return (gpio->gpiob_val >> offset) & 0x1; | |
316 | } | |
317 | ||
318 | static void vprbrd_gpiob_set(struct gpio_chip *chip, | |
319 | unsigned offset, int value) | |
320 | { | |
321 | int ret; | |
322 | struct vprbrd_gpio *gpio = | |
323 | container_of(chip, struct vprbrd_gpio, gpiob); | |
324 | struct vprbrd *vb = gpio->vb; | |
325 | struct vprbrd_gpiob_msg *gbmsg = (struct vprbrd_gpiob_msg *)vb->buf; | |
326 | ||
327 | if (gpio->gpiob_out & (1 << offset)) { | |
328 | if (value) | |
329 | gpio->gpiob_val |= (1 << offset); | |
330 | else | |
331 | gpio->gpiob_val &= ~(1 << offset); | |
332 | ||
333 | mutex_lock(&vb->lock); | |
334 | ||
335 | gbmsg->cmd = VPRBRD_GPIOB_CMD_SETVAL; | |
336 | gbmsg->val = cpu_to_be16(value << offset); | |
337 | gbmsg->mask = cpu_to_be16(0x0001 << offset); | |
338 | ||
339 | ret = usb_control_msg(vb->usb_dev, | |
340 | usb_sndctrlpipe(vb->usb_dev, 0), | |
341 | VPRBRD_USB_REQUEST_GPIOB, VPRBRD_USB_TYPE_OUT, | |
342 | 0x0000, 0x0000, gbmsg, | |
343 | sizeof(struct vprbrd_gpiob_msg), VPRBRD_USB_TIMEOUT_MS); | |
344 | ||
345 | mutex_unlock(&vb->lock); | |
346 | ||
347 | if (ret != sizeof(struct vprbrd_gpiob_msg)) | |
348 | dev_err(chip->dev, "usb error setting pin value\n"); | |
349 | } | |
350 | } | |
351 | ||
352 | static int vprbrd_gpiob_direction_input(struct gpio_chip *chip, | |
353 | unsigned offset) | |
354 | { | |
355 | int ret; | |
356 | struct vprbrd_gpio *gpio = | |
357 | container_of(chip, struct vprbrd_gpio, gpiob); | |
358 | struct vprbrd *vb = gpio->vb; | |
359 | ||
360 | gpio->gpiob_out &= ~(1 << offset); | |
361 | ||
362 | mutex_lock(&vb->lock); | |
363 | ||
364 | ret = vprbrd_gpiob_setdir(vb, offset, 0); | |
365 | ||
366 | mutex_unlock(&vb->lock); | |
367 | ||
368 | if (ret) | |
369 | dev_err(chip->dev, "usb error setting pin to input\n"); | |
370 | ||
371 | return ret; | |
372 | } | |
373 | ||
374 | static int vprbrd_gpiob_direction_output(struct gpio_chip *chip, | |
375 | unsigned offset, int value) | |
376 | { | |
377 | int ret; | |
378 | struct vprbrd_gpio *gpio = | |
379 | container_of(chip, struct vprbrd_gpio, gpiob); | |
380 | struct vprbrd *vb = gpio->vb; | |
381 | ||
382 | gpio->gpiob_out |= (1 << offset); | |
9d5b72de LP |
383 | |
384 | mutex_lock(&vb->lock); | |
385 | ||
386 | ret = vprbrd_gpiob_setdir(vb, offset, 1); | |
387 | if (ret) | |
388 | dev_err(chip->dev, "usb error setting pin to output\n"); | |
389 | ||
390 | mutex_unlock(&vb->lock); | |
391 | ||
392 | vprbrd_gpiob_set(chip, offset, value); | |
393 | ||
394 | return ret; | |
395 | } | |
396 | ||
397 | /* ----- end of gpio b chip ---------------------------------------------- */ | |
398 | ||
0fe763c5 | 399 | static int vprbrd_gpio_probe(struct platform_device *pdev) |
9d5b72de LP |
400 | { |
401 | struct vprbrd *vb = dev_get_drvdata(pdev->dev.parent); | |
402 | struct vprbrd_gpio *vb_gpio; | |
403 | int ret; | |
404 | ||
405 | vb_gpio = devm_kzalloc(&pdev->dev, sizeof(*vb_gpio), GFP_KERNEL); | |
406 | if (vb_gpio == NULL) | |
407 | return -ENOMEM; | |
408 | ||
409 | vb_gpio->vb = vb; | |
410 | /* registering gpio a */ | |
411 | vb_gpio->gpioa.label = "viperboard gpio a"; | |
412 | vb_gpio->gpioa.dev = &pdev->dev; | |
413 | vb_gpio->gpioa.owner = THIS_MODULE; | |
414 | vb_gpio->gpioa.base = -1; | |
415 | vb_gpio->gpioa.ngpio = 16; | |
416 | vb_gpio->gpioa.can_sleep = 1; | |
417 | vb_gpio->gpioa.set = vprbrd_gpioa_set; | |
418 | vb_gpio->gpioa.get = vprbrd_gpioa_get; | |
419 | vb_gpio->gpioa.direction_input = vprbrd_gpioa_direction_input; | |
420 | vb_gpio->gpioa.direction_output = vprbrd_gpioa_direction_output; | |
421 | ret = gpiochip_add(&vb_gpio->gpioa); | |
422 | if (ret < 0) { | |
423 | dev_err(vb_gpio->gpioa.dev, "could not add gpio a"); | |
424 | goto err_gpioa; | |
425 | } | |
426 | ||
427 | /* registering gpio b */ | |
428 | vb_gpio->gpiob.label = "viperboard gpio b"; | |
429 | vb_gpio->gpiob.dev = &pdev->dev; | |
430 | vb_gpio->gpiob.owner = THIS_MODULE; | |
431 | vb_gpio->gpiob.base = -1; | |
432 | vb_gpio->gpiob.ngpio = 16; | |
433 | vb_gpio->gpiob.can_sleep = 1; | |
434 | vb_gpio->gpiob.set = vprbrd_gpiob_set; | |
435 | vb_gpio->gpiob.get = vprbrd_gpiob_get; | |
436 | vb_gpio->gpiob.direction_input = vprbrd_gpiob_direction_input; | |
437 | vb_gpio->gpiob.direction_output = vprbrd_gpiob_direction_output; | |
438 | ret = gpiochip_add(&vb_gpio->gpiob); | |
439 | if (ret < 0) { | |
440 | dev_err(vb_gpio->gpiob.dev, "could not add gpio b"); | |
441 | goto err_gpiob; | |
442 | } | |
443 | ||
444 | platform_set_drvdata(pdev, vb_gpio); | |
445 | ||
446 | return ret; | |
447 | ||
448 | err_gpiob: | |
cfb10898 AL |
449 | if (gpiochip_remove(&vb_gpio->gpioa)) |
450 | dev_err(&pdev->dev, "%s gpiochip_remove failed\n", __func__); | |
9d5b72de LP |
451 | |
452 | err_gpioa: | |
453 | return ret; | |
454 | } | |
455 | ||
0fe763c5 | 456 | static int vprbrd_gpio_remove(struct platform_device *pdev) |
9d5b72de LP |
457 | { |
458 | struct vprbrd_gpio *vb_gpio = platform_get_drvdata(pdev); | |
459 | int ret; | |
460 | ||
461 | ret = gpiochip_remove(&vb_gpio->gpiob); | |
462 | if (ret == 0) | |
463 | ret = gpiochip_remove(&vb_gpio->gpioa); | |
464 | ||
465 | return ret; | |
466 | } | |
467 | ||
468 | static struct platform_driver vprbrd_gpio_driver = { | |
469 | .driver.name = "viperboard-gpio", | |
470 | .driver.owner = THIS_MODULE, | |
471 | .probe = vprbrd_gpio_probe, | |
0fe763c5 | 472 | .remove = vprbrd_gpio_remove, |
9d5b72de LP |
473 | }; |
474 | ||
475 | static int __init vprbrd_gpio_init(void) | |
476 | { | |
477 | switch (gpioa_freq) { | |
478 | case 1000000: | |
479 | gpioa_clk = VPRBRD_GPIOA_CLK_1MHZ; | |
480 | break; | |
481 | case 100000: | |
482 | gpioa_clk = VPRBRD_GPIOA_CLK_100KHZ; | |
483 | break; | |
484 | case 10000: | |
485 | gpioa_clk = VPRBRD_GPIOA_CLK_10KHZ; | |
486 | break; | |
487 | case 1000: | |
488 | gpioa_clk = VPRBRD_GPIOA_CLK_1KHZ; | |
489 | break; | |
490 | case 100: | |
491 | gpioa_clk = VPRBRD_GPIOA_CLK_100HZ; | |
492 | break; | |
493 | case 10: | |
494 | gpioa_clk = VPRBRD_GPIOA_CLK_10HZ; | |
495 | break; | |
496 | default: | |
497 | pr_warn("invalid gpioa_freq (%d)\n", gpioa_freq); | |
498 | gpioa_clk = VPRBRD_GPIOA_CLK_1KHZ; | |
499 | } | |
500 | ||
501 | return platform_driver_register(&vprbrd_gpio_driver); | |
502 | } | |
503 | subsys_initcall(vprbrd_gpio_init); | |
504 | ||
505 | static void __exit vprbrd_gpio_exit(void) | |
506 | { | |
507 | platform_driver_unregister(&vprbrd_gpio_driver); | |
508 | } | |
509 | module_exit(vprbrd_gpio_exit); | |
510 | ||
511 | MODULE_AUTHOR("Lars Poeschel <poeschel@lemonage.de>"); | |
512 | MODULE_DESCRIPTION("GPIO driver for Nano River Techs Viperboard"); | |
513 | MODULE_LICENSE("GPL"); | |
514 | MODULE_ALIAS("platform:viperboard-gpio"); |