]>
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 <[email protected]> | |
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)) | |
58383c78 | 176 | dev_err(chip->parent, "usb error setting pin value\n"); |
9d5b72de LP |
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)) | |
58383c78 | 348 | dev_err(chip->parent, "usb error setting pin value\n"); |
9d5b72de LP |
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) | |
58383c78 | 369 | dev_err(chip->parent, "usb error setting pin to input\n"); |
9d5b72de LP |
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) | |
58383c78 | 388 | dev_err(chip->parent, "usb error setting pin to output\n"); |
9d5b72de LP |
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"; | |
58383c78 | 412 | vb_gpio->gpioa.parent = &pdev->dev; |
9d5b72de LP |
413 | vb_gpio->gpioa.owner = THIS_MODULE; |
414 | vb_gpio->gpioa.base = -1; | |
415 | vb_gpio->gpioa.ngpio = 16; | |
9fb1f39e | 416 | vb_gpio->gpioa.can_sleep = true; |
9d5b72de LP |
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) { | |
58383c78 | 423 | dev_err(vb_gpio->gpioa.parent, "could not add gpio a"); |
9d5b72de LP |
424 | goto err_gpioa; |
425 | } | |
426 | ||
427 | /* registering gpio b */ | |
428 | vb_gpio->gpiob.label = "viperboard gpio b"; | |
58383c78 | 429 | vb_gpio->gpiob.parent = &pdev->dev; |
9d5b72de LP |
430 | vb_gpio->gpiob.owner = THIS_MODULE; |
431 | vb_gpio->gpiob.base = -1; | |
432 | vb_gpio->gpiob.ngpio = 16; | |
9fb1f39e | 433 | vb_gpio->gpiob.can_sleep = true; |
9d5b72de LP |
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) { | |
58383c78 | 440 | dev_err(vb_gpio->gpiob.parent, "could not add gpio b"); |
9d5b72de LP |
441 | goto err_gpiob; |
442 | } | |
443 | ||
444 | platform_set_drvdata(pdev, vb_gpio); | |
445 | ||
446 | return ret; | |
447 | ||
448 | err_gpiob: | |
9f5132ae | 449 | gpiochip_remove(&vb_gpio->gpioa); |
9d5b72de LP |
450 | |
451 | err_gpioa: | |
452 | return ret; | |
453 | } | |
454 | ||
0fe763c5 | 455 | static int vprbrd_gpio_remove(struct platform_device *pdev) |
9d5b72de LP |
456 | { |
457 | struct vprbrd_gpio *vb_gpio = platform_get_drvdata(pdev); | |
9d5b72de | 458 | |
9f5132ae | 459 | gpiochip_remove(&vb_gpio->gpiob); |
9d5b72de | 460 | |
9f5132ae | 461 | return 0; |
9d5b72de LP |
462 | } |
463 | ||
464 | static struct platform_driver vprbrd_gpio_driver = { | |
465 | .driver.name = "viperboard-gpio", | |
466 | .driver.owner = THIS_MODULE, | |
467 | .probe = vprbrd_gpio_probe, | |
0fe763c5 | 468 | .remove = vprbrd_gpio_remove, |
9d5b72de LP |
469 | }; |
470 | ||
471 | static int __init vprbrd_gpio_init(void) | |
472 | { | |
473 | switch (gpioa_freq) { | |
474 | case 1000000: | |
475 | gpioa_clk = VPRBRD_GPIOA_CLK_1MHZ; | |
476 | break; | |
477 | case 100000: | |
478 | gpioa_clk = VPRBRD_GPIOA_CLK_100KHZ; | |
479 | break; | |
480 | case 10000: | |
481 | gpioa_clk = VPRBRD_GPIOA_CLK_10KHZ; | |
482 | break; | |
483 | case 1000: | |
484 | gpioa_clk = VPRBRD_GPIOA_CLK_1KHZ; | |
485 | break; | |
486 | case 100: | |
487 | gpioa_clk = VPRBRD_GPIOA_CLK_100HZ; | |
488 | break; | |
489 | case 10: | |
490 | gpioa_clk = VPRBRD_GPIOA_CLK_10HZ; | |
491 | break; | |
492 | default: | |
493 | pr_warn("invalid gpioa_freq (%d)\n", gpioa_freq); | |
494 | gpioa_clk = VPRBRD_GPIOA_CLK_1KHZ; | |
495 | } | |
496 | ||
497 | return platform_driver_register(&vprbrd_gpio_driver); | |
498 | } | |
499 | subsys_initcall(vprbrd_gpio_init); | |
500 | ||
501 | static void __exit vprbrd_gpio_exit(void) | |
502 | { | |
503 | platform_driver_unregister(&vprbrd_gpio_driver); | |
504 | } | |
505 | module_exit(vprbrd_gpio_exit); | |
506 | ||
507 | MODULE_AUTHOR("Lars Poeschel <[email protected]>"); | |
508 | MODULE_DESCRIPTION("GPIO driver for Nano River Techs Viperboard"); | |
509 | MODULE_LICENSE("GPL"); | |
510 | MODULE_ALIAS("platform:viperboard-gpio"); |