aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mfd/timberdale.c
diff options
context:
space:
mode:
authorRichard Röjfors <richard.rojfors@pelagicore.com>2010-02-04 06:18:52 -0500
committerMauro Carvalho Chehab <mchehab@redhat.com>2010-02-26 13:10:56 -0500
commit8edbede9ebf5959ec9951175a239925225440f5f (patch)
tree8389065970e63b4ab4ed1530126c3c252c731535 /drivers/mfd/timberdale.c
parentd44d1f3bfaef71ce27b4fd2284ec528b52617977 (diff)
V4L/DVB: mfd: Add support for the timberdale FPGA
The timberdale FPGA is found on the Intel in-Vehicle Infotainment reference board russelville. The driver is a PCI driver which chunks up the I/O memory and distributes interrupts to a number of platform devices for each IP inside the FPGA. Signed-off-by: Richard Röjfors <richard.rojfors@pelagicore.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/mfd/timberdale.c')
-rw-r--r--drivers/mfd/timberdale.c663
1 files changed, 663 insertions, 0 deletions
diff --git a/drivers/mfd/timberdale.c b/drivers/mfd/timberdale.c
new file mode 100644
index 000000000000..603cf069ad24
--- /dev/null
+++ b/drivers/mfd/timberdale.c
@@ -0,0 +1,663 @@
1/*
2 * timberdale.c timberdale FPGA MFD driver
3 * Copyright (c) 2009 Intel Corporation
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19/* Supports:
20 * Timberdale FPGA
21 */
22
23#include <linux/kernel.h>
24#include <linux/module.h>
25#include <linux/pci.h>
26#include <linux/msi.h>
27#include <linux/mfd/core.h>
28
29#include <linux/timb_gpio.h>
30
31#include <linux/i2c.h>
32#include <linux/i2c-ocores.h>
33#include <linux/i2c/tsc2007.h>
34
35#include <linux/spi/spi.h>
36#include <linux/spi/xilinx_spi.h>
37#include <linux/spi/max7301.h>
38#include <linux/spi/mc33880.h>
39
40#include "timberdale.h"
41
42#define DRIVER_NAME "timberdale"
43
44struct timberdale_device {
45 resource_size_t ctl_mapbase;
46 unsigned char __iomem *ctl_membase;
47 struct {
48 u32 major;
49 u32 minor;
50 u32 config;
51 } fw;
52};
53
54/*--------------------------------------------------------------------------*/
55
56static struct tsc2007_platform_data timberdale_tsc2007_platform_data = {
57 .model = 2003,
58 .x_plate_ohms = 100
59};
60
61static struct i2c_board_info timberdale_i2c_board_info[] = {
62 {
63 I2C_BOARD_INFO("tsc2007", 0x48),
64 .platform_data = &timberdale_tsc2007_platform_data,
65 .irq = IRQ_TIMBERDALE_TSC_INT
66 },
67};
68
69static __devinitdata struct ocores_i2c_platform_data
70timberdale_ocores_platform_data = {
71 .regstep = 4,
72 .clock_khz = 62500,
73 .devices = timberdale_i2c_board_info,
74 .num_devices = ARRAY_SIZE(timberdale_i2c_board_info)
75};
76
77const static __devinitconst struct resource timberdale_ocores_resources[] = {
78 {
79 .start = OCORESOFFSET,
80 .end = OCORESEND,
81 .flags = IORESOURCE_MEM,
82 },
83 {
84 .start = IRQ_TIMBERDALE_I2C,
85 .end = IRQ_TIMBERDALE_I2C,
86 .flags = IORESOURCE_IRQ,
87 },
88};
89
90const struct max7301_platform_data timberdale_max7301_platform_data = {
91 .base = 200
92};
93
94const struct mc33880_platform_data timberdale_mc33880_platform_data = {
95 .base = 100
96};
97
98static struct spi_board_info timberdale_spi_16bit_board_info[] = {
99 {
100 .modalias = "max7301",
101 .max_speed_hz = 26000,
102 .chip_select = 2,
103 .mode = SPI_MODE_0,
104 .platform_data = &timberdale_max7301_platform_data
105 },
106};
107
108static struct spi_board_info timberdale_spi_8bit_board_info[] = {
109 {
110 .modalias = "mc33880",
111 .max_speed_hz = 4000,
112 .chip_select = 1,
113 .mode = SPI_MODE_1,
114 .platform_data = &timberdale_mc33880_platform_data
115 },
116};
117
118static __devinitdata struct xspi_platform_data timberdale_xspi_platform_data = {
119 .num_chipselect = 3,
120 .little_endian = true,
121 /* bits per word and devices will be filled in runtime depending
122 * on the HW config
123 */
124};
125
126const static __devinitconst struct resource timberdale_spi_resources[] = {
127 {
128 .start = SPIOFFSET,
129 .end = SPIEND,
130 .flags = IORESOURCE_MEM,
131 },
132 {
133 .start = IRQ_TIMBERDALE_SPI,
134 .end = IRQ_TIMBERDALE_SPI,
135 .flags = IORESOURCE_IRQ,
136 },
137};
138
139const static __devinitconst struct resource timberdale_eth_resources[] = {
140 {
141 .start = ETHOFFSET,
142 .end = ETHEND,
143 .flags = IORESOURCE_MEM,
144 },
145 {
146 .start = IRQ_TIMBERDALE_ETHSW_IF,
147 .end = IRQ_TIMBERDALE_ETHSW_IF,
148 .flags = IORESOURCE_IRQ,
149 },
150};
151
152static __devinitdata struct timbgpio_platform_data
153 timberdale_gpio_platform_data = {
154 .gpio_base = 0,
155 .nr_pins = GPIO_NR_PINS,
156 .irq_base = 200,
157};
158
159const static __devinitconst struct resource timberdale_gpio_resources[] = {
160 {
161 .start = GPIOOFFSET,
162 .end = GPIOEND,
163 .flags = IORESOURCE_MEM,
164 },
165 {
166 .start = IRQ_TIMBERDALE_GPIO,
167 .end = IRQ_TIMBERDALE_GPIO,
168 .flags = IORESOURCE_IRQ,
169 },
170};
171
172const static __devinitconst struct resource timberdale_mlogicore_resources[] = {
173 {
174 .start = MLCOREOFFSET,
175 .end = MLCOREEND,
176 .flags = IORESOURCE_MEM,
177 },
178 {
179 .start = IRQ_TIMBERDALE_MLCORE,
180 .end = IRQ_TIMBERDALE_MLCORE,
181 .flags = IORESOURCE_IRQ,
182 },
183 {
184 .start = IRQ_TIMBERDALE_MLCORE_BUF,
185 .end = IRQ_TIMBERDALE_MLCORE_BUF,
186 .flags = IORESOURCE_IRQ,
187 },
188};
189
190const static __devinitconst struct resource timberdale_uart_resources[] = {
191 {
192 .start = UARTOFFSET,
193 .end = UARTEND,
194 .flags = IORESOURCE_MEM,
195 },
196 {
197 .start = IRQ_TIMBERDALE_UART,
198 .end = IRQ_TIMBERDALE_UART,
199 .flags = IORESOURCE_IRQ,
200 },
201};
202
203const static __devinitconst struct resource timberdale_uartlite_resources[] = {
204 {
205 .start = UARTLITEOFFSET,
206 .end = UARTLITEEND,
207 .flags = IORESOURCE_MEM,
208 },
209 {
210 .start = IRQ_TIMBERDALE_UARTLITE,
211 .end = IRQ_TIMBERDALE_UARTLITE,
212 .flags = IORESOURCE_IRQ,
213 },
214};
215
216const static __devinitconst struct resource timberdale_dma_resources[] = {
217 {
218 .start = DMAOFFSET,
219 .end = DMAEND,
220 .flags = IORESOURCE_MEM,
221 },
222 {
223 .start = IRQ_TIMBERDALE_DMA,
224 .end = IRQ_TIMBERDALE_DMA,
225 .flags = IORESOURCE_IRQ,
226 },
227};
228
229static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg0[] = {
230 {
231 .name = "timb-uart",
232 .num_resources = ARRAY_SIZE(timberdale_uart_resources),
233 .resources = timberdale_uart_resources,
234 },
235 {
236 .name = "timb-gpio",
237 .num_resources = ARRAY_SIZE(timberdale_gpio_resources),
238 .resources = timberdale_gpio_resources,
239 .platform_data = &timberdale_gpio_platform_data,
240 .data_size = sizeof(timberdale_gpio_platform_data),
241 },
242 {
243 .name = "xilinx_spi",
244 .num_resources = ARRAY_SIZE(timberdale_spi_resources),
245 .resources = timberdale_spi_resources,
246 .platform_data = &timberdale_xspi_platform_data,
247 .data_size = sizeof(timberdale_xspi_platform_data),
248 },
249 {
250 .name = "ks8842",
251 .num_resources = ARRAY_SIZE(timberdale_eth_resources),
252 .resources = timberdale_eth_resources,
253 },
254 {
255 .name = "timb-dma",
256 .num_resources = ARRAY_SIZE(timberdale_dma_resources),
257 .resources = timberdale_dma_resources,
258 },
259};
260
261static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = {
262 {
263 .name = "timb-uart",
264 .num_resources = ARRAY_SIZE(timberdale_uart_resources),
265 .resources = timberdale_uart_resources,
266 },
267 {
268 .name = "uartlite",
269 .num_resources = ARRAY_SIZE(timberdale_uartlite_resources),
270 .resources = timberdale_uartlite_resources,
271 },
272 {
273 .name = "timb-gpio",
274 .num_resources = ARRAY_SIZE(timberdale_gpio_resources),
275 .resources = timberdale_gpio_resources,
276 .platform_data = &timberdale_gpio_platform_data,
277 .data_size = sizeof(timberdale_gpio_platform_data),
278 },
279 {
280 .name = "timb-mlogicore",
281 .num_resources = ARRAY_SIZE(timberdale_mlogicore_resources),
282 .resources = timberdale_mlogicore_resources,
283 },
284 {
285 .name = "xilinx_spi",
286 .num_resources = ARRAY_SIZE(timberdale_spi_resources),
287 .resources = timberdale_spi_resources,
288 .platform_data = &timberdale_xspi_platform_data,
289 .data_size = sizeof(timberdale_xspi_platform_data),
290 },
291 {
292 .name = "ks8842",
293 .num_resources = ARRAY_SIZE(timberdale_eth_resources),
294 .resources = timberdale_eth_resources,
295 },
296 {
297 .name = "timb-dma",
298 .num_resources = ARRAY_SIZE(timberdale_dma_resources),
299 .resources = timberdale_dma_resources,
300 },
301};
302
303static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = {
304 {
305 .name = "timb-uart",
306 .num_resources = ARRAY_SIZE(timberdale_uart_resources),
307 .resources = timberdale_uart_resources,
308 },
309 {
310 .name = "timb-gpio",
311 .num_resources = ARRAY_SIZE(timberdale_gpio_resources),
312 .resources = timberdale_gpio_resources,
313 .platform_data = &timberdale_gpio_platform_data,
314 .data_size = sizeof(timberdale_gpio_platform_data),
315 },
316 {
317 .name = "xilinx_spi",
318 .num_resources = ARRAY_SIZE(timberdale_spi_resources),
319 .resources = timberdale_spi_resources,
320 .platform_data = &timberdale_xspi_platform_data,
321 .data_size = sizeof(timberdale_xspi_platform_data),
322 },
323 {
324 .name = "timb-dma",
325 .num_resources = ARRAY_SIZE(timberdale_dma_resources),
326 .resources = timberdale_dma_resources,
327 },
328};
329
330static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg3[] = {
331 {
332 .name = "timb-uart",
333 .num_resources = ARRAY_SIZE(timberdale_uart_resources),
334 .resources = timberdale_uart_resources,
335 },
336 {
337 .name = "ocores-i2c",
338 .num_resources = ARRAY_SIZE(timberdale_ocores_resources),
339 .resources = timberdale_ocores_resources,
340 .platform_data = &timberdale_ocores_platform_data,
341 .data_size = sizeof(timberdale_ocores_platform_data),
342 },
343 {
344 .name = "timb-gpio",
345 .num_resources = ARRAY_SIZE(timberdale_gpio_resources),
346 .resources = timberdale_gpio_resources,
347 .platform_data = &timberdale_gpio_platform_data,
348 .data_size = sizeof(timberdale_gpio_platform_data),
349 },
350 {
351 .name = "xilinx_spi",
352 .num_resources = ARRAY_SIZE(timberdale_spi_resources),
353 .resources = timberdale_spi_resources,
354 .platform_data = &timberdale_xspi_platform_data,
355 .data_size = sizeof(timberdale_xspi_platform_data),
356 },
357 {
358 .name = "ks8842",
359 .num_resources = ARRAY_SIZE(timberdale_eth_resources),
360 .resources = timberdale_eth_resources,
361 },
362 {
363 .name = "timb-dma",
364 .num_resources = ARRAY_SIZE(timberdale_dma_resources),
365 .resources = timberdale_dma_resources,
366 },
367};
368
369static const __devinitconst struct resource timberdale_sdhc_resources[] = {
370 /* located in bar 1 and bar 2 */
371 {
372 .start = SDHC0OFFSET,
373 .end = SDHC0END,
374 .flags = IORESOURCE_MEM,
375 },
376 {
377 .start = IRQ_TIMBERDALE_SDHC,
378 .end = IRQ_TIMBERDALE_SDHC,
379 .flags = IORESOURCE_IRQ,
380 },
381};
382
383static __devinitdata struct mfd_cell timberdale_cells_bar1[] = {
384 {
385 .name = "sdhci",
386 .num_resources = ARRAY_SIZE(timberdale_sdhc_resources),
387 .resources = timberdale_sdhc_resources,
388 },
389};
390
391static __devinitdata struct mfd_cell timberdale_cells_bar2[] = {
392 {
393 .name = "sdhci",
394 .num_resources = ARRAY_SIZE(timberdale_sdhc_resources),
395 .resources = timberdale_sdhc_resources,
396 },
397};
398
399static ssize_t show_fw_ver(struct device *dev, struct device_attribute *attr,
400 char *buf)
401{
402 struct pci_dev *pdev = to_pci_dev(dev);
403 struct timberdale_device *priv = pci_get_drvdata(pdev);
404
405 return sprintf(buf, "%d.%d.%d\n", priv->fw.major, priv->fw.minor,
406 priv->fw.config);
407}
408
409static DEVICE_ATTR(fw_ver, S_IRUGO, show_fw_ver, NULL);
410
411/*--------------------------------------------------------------------------*/
412
413static int __devinit timb_probe(struct pci_dev *dev,
414 const struct pci_device_id *id)
415{
416 struct timberdale_device *priv;
417 int err, i;
418 resource_size_t mapbase;
419 struct msix_entry *msix_entries = NULL;
420 u8 ip_setup;
421
422 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
423 if (!priv)
424 return -ENOMEM;
425
426 pci_set_drvdata(dev, priv);
427
428 err = pci_enable_device(dev);
429 if (err)
430 goto err_enable;
431
432 mapbase = pci_resource_start(dev, 0);
433 if (!mapbase) {
434 dev_err(&dev->dev, "No resource\n");
435 goto err_start;
436 }
437
438 /* create a resource for the PCI master register */
439 priv->ctl_mapbase = mapbase + CHIPCTLOFFSET;
440 if (!request_mem_region(priv->ctl_mapbase, CHIPCTLSIZE, "timb-ctl")) {
441 dev_err(&dev->dev, "Failed to request ctl mem\n");
442 goto err_request;
443 }
444
445 priv->ctl_membase = ioremap(priv->ctl_mapbase, CHIPCTLSIZE);
446 if (!priv->ctl_membase) {
447 dev_err(&dev->dev, "ioremap failed for ctl mem\n");
448 goto err_ioremap;
449 }
450
451 /* read the HW config */
452 priv->fw.major = ioread32(priv->ctl_membase + TIMB_REV_MAJOR);
453 priv->fw.minor = ioread32(priv->ctl_membase + TIMB_REV_MINOR);
454 priv->fw.config = ioread32(priv->ctl_membase + TIMB_HW_CONFIG);
455
456 if (priv->fw.major > TIMB_SUPPORTED_MAJOR) {
457 dev_err(&dev->dev, "The driver supports an older "
458 "version of the FPGA, please update the driver to "
459 "support %d.%d\n", priv->fw.major, priv->fw.minor);
460 goto err_ioremap;
461 }
462 if (priv->fw.major < TIMB_SUPPORTED_MAJOR ||
463 priv->fw.minor < TIMB_REQUIRED_MINOR) {
464 dev_err(&dev->dev, "The FPGA image is too old (%d.%d), "
465 "please upgrade the FPGA to at least: %d.%d\n",
466 priv->fw.major, priv->fw.minor,
467 TIMB_SUPPORTED_MAJOR, TIMB_REQUIRED_MINOR);
468 goto err_ioremap;
469 }
470
471 msix_entries = kzalloc(TIMBERDALE_NR_IRQS * sizeof(*msix_entries),
472 GFP_KERNEL);
473 if (!msix_entries)
474 goto err_ioremap;
475
476 for (i = 0; i < TIMBERDALE_NR_IRQS; i++)
477 msix_entries[i].entry = i;
478
479 err = pci_enable_msix(dev, msix_entries, TIMBERDALE_NR_IRQS);
480 if (err) {
481 dev_err(&dev->dev,
482 "MSI-X init failed: %d, expected entries: %d\n",
483 err, TIMBERDALE_NR_IRQS);
484 goto err_msix;
485 }
486
487 err = device_create_file(&dev->dev, &dev_attr_fw_ver);
488 if (err)
489 goto err_create_file;
490
491 /* Reset all FPGA PLB peripherals */
492 iowrite32(0x1, priv->ctl_membase + TIMB_SW_RST);
493
494 /* update IRQ offsets in I2C board info */
495 for (i = 0; i < ARRAY_SIZE(timberdale_i2c_board_info); i++)
496 timberdale_i2c_board_info[i].irq =
497 msix_entries[timberdale_i2c_board_info[i].irq].vector;
498
499 /* Update the SPI configuration depending on the HW (8 or 16 bit) */
500 if (priv->fw.config & TIMB_HW_CONFIG_SPI_8BIT) {
501 timberdale_xspi_platform_data.bits_per_word = 8;
502 timberdale_xspi_platform_data.devices =
503 timberdale_spi_8bit_board_info;
504 timberdale_xspi_platform_data.num_devices =
505 ARRAY_SIZE(timberdale_spi_8bit_board_info);
506 } else {
507 timberdale_xspi_platform_data.bits_per_word = 16;
508 timberdale_xspi_platform_data.devices =
509 timberdale_spi_16bit_board_info;
510 timberdale_xspi_platform_data.num_devices =
511 ARRAY_SIZE(timberdale_spi_16bit_board_info);
512 }
513
514 ip_setup = priv->fw.config & TIMB_HW_VER_MASK;
515 switch (ip_setup) {
516 case TIMB_HW_VER0:
517 err = mfd_add_devices(&dev->dev, -1,
518 timberdale_cells_bar0_cfg0,
519 ARRAY_SIZE(timberdale_cells_bar0_cfg0),
520 &dev->resource[0], msix_entries[0].vector);
521 break;
522 case TIMB_HW_VER1:
523 err = mfd_add_devices(&dev->dev, -1,
524 timberdale_cells_bar0_cfg1,
525 ARRAY_SIZE(timberdale_cells_bar0_cfg1),
526 &dev->resource[0], msix_entries[0].vector);
527 break;
528 case TIMB_HW_VER2:
529 err = mfd_add_devices(&dev->dev, -1,
530 timberdale_cells_bar0_cfg2,
531 ARRAY_SIZE(timberdale_cells_bar0_cfg2),
532 &dev->resource[0], msix_entries[0].vector);
533 break;
534 case TIMB_HW_VER3:
535 err = mfd_add_devices(&dev->dev, -1,
536 timberdale_cells_bar0_cfg3,
537 ARRAY_SIZE(timberdale_cells_bar0_cfg3),
538 &dev->resource[0], msix_entries[0].vector);
539 break;
540 default:
541 dev_err(&dev->dev, "Uknown IP setup: %d.%d.%d\n",
542 priv->fw.major, priv->fw.minor, ip_setup);
543 err = -ENODEV;
544 goto err_mfd;
545 break;
546 }
547
548 if (err) {
549 dev_err(&dev->dev, "mfd_add_devices failed: %d\n", err);
550 goto err_mfd;
551 }
552
553 err = mfd_add_devices(&dev->dev, 0,
554 timberdale_cells_bar1, ARRAY_SIZE(timberdale_cells_bar1),
555 &dev->resource[1], msix_entries[0].vector);
556 if (err) {
557 dev_err(&dev->dev, "mfd_add_devices failed: %d\n", err);
558 goto err_mfd2;
559 }
560
561 /* only version 0 and 3 have the iNand routed to SDHCI */
562 if (((priv->fw.config & TIMB_HW_VER_MASK) == TIMB_HW_VER0) ||
563 ((priv->fw.config & TIMB_HW_VER_MASK) == TIMB_HW_VER3)) {
564 err = mfd_add_devices(&dev->dev, 1, timberdale_cells_bar2,
565 ARRAY_SIZE(timberdale_cells_bar2),
566 &dev->resource[2], msix_entries[0].vector);
567 if (err) {
568 dev_err(&dev->dev, "mfd_add_devices failed: %d\n", err);
569 goto err_mfd2;
570 }
571 }
572
573 kfree(msix_entries);
574
575 dev_info(&dev->dev,
576 "Found Timberdale Card. Rev: %d.%d, HW config: 0x%02x\n",
577 priv->fw.major, priv->fw.minor, priv->fw.config);
578
579 return 0;
580
581err_mfd2:
582 mfd_remove_devices(&dev->dev);
583err_mfd:
584 device_remove_file(&dev->dev, &dev_attr_fw_ver);
585err_create_file:
586 pci_disable_msix(dev);
587err_msix:
588 iounmap(priv->ctl_membase);
589err_ioremap:
590 release_mem_region(priv->ctl_mapbase, CHIPCTLSIZE);
591err_request:
592 pci_set_drvdata(dev, NULL);
593err_start:
594 pci_disable_device(dev);
595err_enable:
596 kfree(msix_entries);
597 kfree(priv);
598 pci_set_drvdata(dev, NULL);
599 return -ENODEV;
600}
601
602static void __devexit timb_remove(struct pci_dev *dev)
603{
604 struct timberdale_device *priv = pci_get_drvdata(dev);
605
606 mfd_remove_devices(&dev->dev);
607
608 device_remove_file(&dev->dev, &dev_attr_fw_ver);
609
610 iounmap(priv->ctl_membase);
611 release_mem_region(priv->ctl_mapbase, CHIPCTLSIZE);
612
613 pci_disable_msix(dev);
614 pci_disable_device(dev);
615 pci_set_drvdata(dev, NULL);
616 kfree(priv);
617}
618
619static struct pci_device_id timberdale_pci_tbl[] = {
620 { PCI_DEVICE(PCI_VENDOR_ID_TIMB, PCI_DEVICE_ID_TIMB) },
621 { 0 }
622};
623MODULE_DEVICE_TABLE(pci, timberdale_pci_tbl);
624
625static struct pci_driver timberdale_pci_driver = {
626 .name = DRIVER_NAME,
627 .id_table = timberdale_pci_tbl,
628 .probe = timb_probe,
629 .remove = __devexit_p(timb_remove),
630};
631
632static int __init timberdale_init(void)
633{
634 int err;
635
636 err = pci_register_driver(&timberdale_pci_driver);
637 if (err < 0) {
638 printk(KERN_ERR
639 "Failed to register PCI driver for %s device.\n",
640 timberdale_pci_driver.name);
641 return -ENODEV;
642 }
643
644 printk(KERN_INFO "Driver for %s has been successfully registered.\n",
645 timberdale_pci_driver.name);
646
647 return 0;
648}
649
650static void __exit timberdale_exit(void)
651{
652 pci_unregister_driver(&timberdale_pci_driver);
653
654 printk(KERN_INFO "Driver for %s has been successfully unregistered.\n",
655 timberdale_pci_driver.name);
656}
657
658module_init(timberdale_init);
659module_exit(timberdale_exit);
660
661MODULE_AUTHOR("Mocean Laboratories <info@mocean-labs.com>");
662MODULE_VERSION(DRV_VERSION);
663MODULE_LICENSE("GPL v2");