xref: /linux/drivers/mfd/timberdale.c (revision c145211d1f9e2ef19e7b4c2b943f68366daa97af)
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 #include <linux/slab.h>
29 
30 #include <linux/timb_gpio.h>
31 
32 #include <linux/i2c.h>
33 #include <linux/i2c-ocores.h>
34 #include <linux/i2c/tsc2007.h>
35 
36 #include <linux/spi/spi.h>
37 #include <linux/spi/xilinx_spi.h>
38 #include <linux/spi/max7301.h>
39 #include <linux/spi/mc33880.h>
40 
41 #include <media/timb_radio.h>
42 
43 #include "timberdale.h"
44 
45 #define DRIVER_NAME "timberdale"
46 
47 struct timberdale_device {
48 	resource_size_t		ctl_mapbase;
49 	unsigned char __iomem   *ctl_membase;
50 	struct {
51 		u32 major;
52 		u32 minor;
53 		u32 config;
54 	} fw;
55 };
56 
57 /*--------------------------------------------------------------------------*/
58 
59 static struct tsc2007_platform_data timberdale_tsc2007_platform_data = {
60 	.model = 2003,
61 	.x_plate_ohms = 100
62 };
63 
64 static struct i2c_board_info timberdale_i2c_board_info[] = {
65 	{
66 		I2C_BOARD_INFO("tsc2007", 0x48),
67 		.platform_data = &timberdale_tsc2007_platform_data,
68 		.irq = IRQ_TIMBERDALE_TSC_INT
69 	},
70 };
71 
72 static __devinitdata struct ocores_i2c_platform_data
73 timberdale_ocores_platform_data = {
74 	.regstep = 4,
75 	.clock_khz = 62500,
76 	.devices = timberdale_i2c_board_info,
77 	.num_devices = ARRAY_SIZE(timberdale_i2c_board_info)
78 };
79 
80 const static __devinitconst struct resource timberdale_ocores_resources[] = {
81 	{
82 		.start	= OCORESOFFSET,
83 		.end	= OCORESEND,
84 		.flags	= IORESOURCE_MEM,
85 	},
86 	{
87 		.start 	= IRQ_TIMBERDALE_I2C,
88 		.end	= IRQ_TIMBERDALE_I2C,
89 		.flags	= IORESOURCE_IRQ,
90 	},
91 };
92 
93 const struct max7301_platform_data timberdale_max7301_platform_data = {
94 	.base = 200
95 };
96 
97 const struct mc33880_platform_data timberdale_mc33880_platform_data = {
98 	.base = 100
99 };
100 
101 static struct spi_board_info timberdale_spi_16bit_board_info[] = {
102 	{
103 		.modalias = "max7301",
104 		.max_speed_hz = 26000,
105 		.chip_select = 2,
106 		.mode = SPI_MODE_0,
107 		.platform_data = &timberdale_max7301_platform_data
108 	},
109 };
110 
111 static struct spi_board_info timberdale_spi_8bit_board_info[] = {
112 	{
113 		.modalias = "mc33880",
114 		.max_speed_hz = 4000,
115 		.chip_select = 1,
116 		.mode = SPI_MODE_1,
117 		.platform_data = &timberdale_mc33880_platform_data
118 	},
119 };
120 
121 static __devinitdata struct xspi_platform_data timberdale_xspi_platform_data = {
122 	.num_chipselect = 3,
123 	.little_endian = true,
124 	/* bits per word and devices will be filled in runtime depending
125 	 * on the HW config
126 	 */
127 };
128 
129 const static __devinitconst struct resource timberdale_spi_resources[] = {
130 	{
131 		.start 	= SPIOFFSET,
132 		.end	= SPIEND,
133 		.flags	= IORESOURCE_MEM,
134 	},
135 	{
136 		.start	= IRQ_TIMBERDALE_SPI,
137 		.end	= IRQ_TIMBERDALE_SPI,
138 		.flags	= IORESOURCE_IRQ,
139 	},
140 };
141 
142 const static __devinitconst struct resource timberdale_eth_resources[] = {
143 	{
144 		.start	= ETHOFFSET,
145 		.end	= ETHEND,
146 		.flags	= IORESOURCE_MEM,
147 	},
148 	{
149 		.start	= IRQ_TIMBERDALE_ETHSW_IF,
150 		.end	= IRQ_TIMBERDALE_ETHSW_IF,
151 		.flags	= IORESOURCE_IRQ,
152 	},
153 };
154 
155 static __devinitdata struct timbgpio_platform_data
156 	timberdale_gpio_platform_data = {
157 	.gpio_base = 0,
158 	.nr_pins = GPIO_NR_PINS,
159 	.irq_base = 200,
160 };
161 
162 const static __devinitconst struct resource timberdale_gpio_resources[] = {
163 	{
164 		.start	= GPIOOFFSET,
165 		.end	= GPIOEND,
166 		.flags	= IORESOURCE_MEM,
167 	},
168 	{
169 		.start	= IRQ_TIMBERDALE_GPIO,
170 		.end	= IRQ_TIMBERDALE_GPIO,
171 		.flags	= IORESOURCE_IRQ,
172 	},
173 };
174 
175 const static __devinitconst struct resource timberdale_mlogicore_resources[] = {
176 	{
177 		.start	= MLCOREOFFSET,
178 		.end	= MLCOREEND,
179 		.flags	= IORESOURCE_MEM,
180 	},
181 	{
182 		.start	= IRQ_TIMBERDALE_MLCORE,
183 		.end	= IRQ_TIMBERDALE_MLCORE,
184 		.flags	= IORESOURCE_IRQ,
185 	},
186 	{
187 		.start	= IRQ_TIMBERDALE_MLCORE_BUF,
188 		.end	= IRQ_TIMBERDALE_MLCORE_BUF,
189 		.flags	= IORESOURCE_IRQ,
190 	},
191 };
192 
193 const static __devinitconst struct resource timberdale_uart_resources[] = {
194 	{
195 		.start	= UARTOFFSET,
196 		.end	= UARTEND,
197 		.flags	= IORESOURCE_MEM,
198 	},
199 	{
200 		.start	= IRQ_TIMBERDALE_UART,
201 		.end	= IRQ_TIMBERDALE_UART,
202 		.flags	= IORESOURCE_IRQ,
203 	},
204 };
205 
206 const static __devinitconst struct resource timberdale_uartlite_resources[] = {
207 	{
208 		.start	= UARTLITEOFFSET,
209 		.end	= UARTLITEEND,
210 		.flags	= IORESOURCE_MEM,
211 	},
212 	{
213 		.start	= IRQ_TIMBERDALE_UARTLITE,
214 		.end	= IRQ_TIMBERDALE_UARTLITE,
215 		.flags	= IORESOURCE_IRQ,
216 	},
217 };
218 
219 const static __devinitconst struct resource timberdale_radio_resources[] = {
220 	{
221 		.start	= RDSOFFSET,
222 		.end	= RDSEND,
223 		.flags	= IORESOURCE_MEM,
224 	},
225 	{
226 		.start	= IRQ_TIMBERDALE_RDS,
227 		.end	= IRQ_TIMBERDALE_RDS,
228 		.flags	= IORESOURCE_IRQ,
229 	},
230 };
231 
232 static __devinitdata struct i2c_board_info timberdale_tef6868_i2c_board_info = {
233 	I2C_BOARD_INFO("tef6862", 0x60)
234 };
235 
236 static __devinitdata struct i2c_board_info timberdale_saa7706_i2c_board_info = {
237 	I2C_BOARD_INFO("saa7706h", 0x1C)
238 };
239 
240 static __devinitdata struct timb_radio_platform_data
241 	timberdale_radio_platform_data = {
242 	.i2c_adapter = 0,
243 	.tuner = {
244 		.module_name = "tef6862",
245 		.info = &timberdale_tef6868_i2c_board_info
246 	},
247 	.dsp = {
248 		.module_name = "saa7706h",
249 		.info = &timberdale_saa7706_i2c_board_info
250 	}
251 };
252 
253 const static __devinitconst struct resource timberdale_dma_resources[] = {
254 	{
255 		.start	= DMAOFFSET,
256 		.end	= DMAEND,
257 		.flags	= IORESOURCE_MEM,
258 	},
259 	{
260 		.start	= IRQ_TIMBERDALE_DMA,
261 		.end	= IRQ_TIMBERDALE_DMA,
262 		.flags	= IORESOURCE_IRQ,
263 	},
264 };
265 
266 static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg0[] = {
267 	{
268 		.name = "timb-uart",
269 		.num_resources = ARRAY_SIZE(timberdale_uart_resources),
270 		.resources = timberdale_uart_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-radio",
281 		.num_resources = ARRAY_SIZE(timberdale_radio_resources),
282 		.resources = timberdale_radio_resources,
283 		.platform_data = &timberdale_radio_platform_data,
284 		.data_size = sizeof(timberdale_radio_platform_data),
285 	},
286 	{
287 		.name = "xilinx_spi",
288 		.num_resources = ARRAY_SIZE(timberdale_spi_resources),
289 		.resources = timberdale_spi_resources,
290 		.platform_data = &timberdale_xspi_platform_data,
291 		.data_size = sizeof(timberdale_xspi_platform_data),
292 	},
293 	{
294 		.name = "ks8842",
295 		.num_resources = ARRAY_SIZE(timberdale_eth_resources),
296 		.resources = timberdale_eth_resources,
297 	},
298 	{
299 		.name = "timb-dma",
300 		.num_resources = ARRAY_SIZE(timberdale_dma_resources),
301 		.resources = timberdale_dma_resources,
302 	},
303 };
304 
305 static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = {
306 	{
307 		.name = "timb-uart",
308 		.num_resources = ARRAY_SIZE(timberdale_uart_resources),
309 		.resources = timberdale_uart_resources,
310 	},
311 	{
312 		.name = "uartlite",
313 		.num_resources = ARRAY_SIZE(timberdale_uartlite_resources),
314 		.resources = timberdale_uartlite_resources,
315 	},
316 	{
317 		.name = "timb-gpio",
318 		.num_resources = ARRAY_SIZE(timberdale_gpio_resources),
319 		.resources = timberdale_gpio_resources,
320 		.platform_data = &timberdale_gpio_platform_data,
321 		.data_size = sizeof(timberdale_gpio_platform_data),
322 	},
323 	{
324 		.name = "timb-mlogicore",
325 		.num_resources = ARRAY_SIZE(timberdale_mlogicore_resources),
326 		.resources = timberdale_mlogicore_resources,
327 	},
328 	{
329 		.name = "timb-radio",
330 		.num_resources = ARRAY_SIZE(timberdale_radio_resources),
331 		.resources = timberdale_radio_resources,
332 		.platform_data = &timberdale_radio_platform_data,
333 		.data_size = sizeof(timberdale_radio_platform_data),
334 	},
335 	{
336 		.name = "xilinx_spi",
337 		.num_resources = ARRAY_SIZE(timberdale_spi_resources),
338 		.resources = timberdale_spi_resources,
339 		.platform_data = &timberdale_xspi_platform_data,
340 		.data_size = sizeof(timberdale_xspi_platform_data),
341 	},
342 	{
343 		.name = "ks8842",
344 		.num_resources = ARRAY_SIZE(timberdale_eth_resources),
345 		.resources = timberdale_eth_resources,
346 	},
347 	{
348 		.name = "timb-dma",
349 		.num_resources = ARRAY_SIZE(timberdale_dma_resources),
350 		.resources = timberdale_dma_resources,
351 	},
352 };
353 
354 static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = {
355 	{
356 		.name = "timb-uart",
357 		.num_resources = ARRAY_SIZE(timberdale_uart_resources),
358 		.resources = timberdale_uart_resources,
359 	},
360 	{
361 		.name = "timb-gpio",
362 		.num_resources = ARRAY_SIZE(timberdale_gpio_resources),
363 		.resources = timberdale_gpio_resources,
364 		.platform_data = &timberdale_gpio_platform_data,
365 		.data_size = sizeof(timberdale_gpio_platform_data),
366 	},
367 	{
368 		.name = "timb-radio",
369 		.num_resources = ARRAY_SIZE(timberdale_radio_resources),
370 		.resources = timberdale_radio_resources,
371 		.platform_data = &timberdale_radio_platform_data,
372 		.data_size = sizeof(timberdale_radio_platform_data),
373 	},
374 	{
375 		.name = "xilinx_spi",
376 		.num_resources = ARRAY_SIZE(timberdale_spi_resources),
377 		.resources = timberdale_spi_resources,
378 		.platform_data = &timberdale_xspi_platform_data,
379 		.data_size = sizeof(timberdale_xspi_platform_data),
380 	},
381 	{
382 		.name = "timb-dma",
383 		.num_resources = ARRAY_SIZE(timberdale_dma_resources),
384 		.resources = timberdale_dma_resources,
385 	},
386 };
387 
388 static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg3[] = {
389 	{
390 		.name = "timb-uart",
391 		.num_resources = ARRAY_SIZE(timberdale_uart_resources),
392 		.resources = timberdale_uart_resources,
393 	},
394 	{
395 		.name = "ocores-i2c",
396 		.num_resources = ARRAY_SIZE(timberdale_ocores_resources),
397 		.resources = timberdale_ocores_resources,
398 		.platform_data = &timberdale_ocores_platform_data,
399 		.data_size = sizeof(timberdale_ocores_platform_data),
400 	},
401 	{
402 		.name = "timb-gpio",
403 		.num_resources = ARRAY_SIZE(timberdale_gpio_resources),
404 		.resources = timberdale_gpio_resources,
405 		.platform_data = &timberdale_gpio_platform_data,
406 		.data_size = sizeof(timberdale_gpio_platform_data),
407 	},
408 	{
409 		.name = "timb-radio",
410 		.num_resources = ARRAY_SIZE(timberdale_radio_resources),
411 		.resources = timberdale_radio_resources,
412 		.platform_data = &timberdale_radio_platform_data,
413 		.data_size = sizeof(timberdale_radio_platform_data),
414 	},
415 	{
416 		.name = "xilinx_spi",
417 		.num_resources = ARRAY_SIZE(timberdale_spi_resources),
418 		.resources = timberdale_spi_resources,
419 		.platform_data = &timberdale_xspi_platform_data,
420 		.data_size = sizeof(timberdale_xspi_platform_data),
421 	},
422 	{
423 		.name = "ks8842",
424 		.num_resources = ARRAY_SIZE(timberdale_eth_resources),
425 		.resources = timberdale_eth_resources,
426 	},
427 	{
428 		.name = "timb-dma",
429 		.num_resources = ARRAY_SIZE(timberdale_dma_resources),
430 		.resources = timberdale_dma_resources,
431 	},
432 };
433 
434 static const __devinitconst struct resource timberdale_sdhc_resources[] = {
435 	/* located in bar 1 and bar 2 */
436 	{
437 		.start	= SDHC0OFFSET,
438 		.end	= SDHC0END,
439 		.flags	= IORESOURCE_MEM,
440 	},
441 	{
442 		.start	= IRQ_TIMBERDALE_SDHC,
443 		.end	= IRQ_TIMBERDALE_SDHC,
444 		.flags	= IORESOURCE_IRQ,
445 	},
446 };
447 
448 static __devinitdata struct mfd_cell timberdale_cells_bar1[] = {
449 	{
450 		.name = "sdhci",
451 		.num_resources = ARRAY_SIZE(timberdale_sdhc_resources),
452 		.resources = timberdale_sdhc_resources,
453 	},
454 };
455 
456 static __devinitdata struct mfd_cell timberdale_cells_bar2[] = {
457 	{
458 		.name = "sdhci",
459 		.num_resources = ARRAY_SIZE(timberdale_sdhc_resources),
460 		.resources = timberdale_sdhc_resources,
461 	},
462 };
463 
464 static ssize_t show_fw_ver(struct device *dev, struct device_attribute *attr,
465 	char *buf)
466 {
467 	struct pci_dev *pdev = to_pci_dev(dev);
468 	struct timberdale_device *priv = pci_get_drvdata(pdev);
469 
470 	return sprintf(buf, "%d.%d.%d\n", priv->fw.major, priv->fw.minor,
471 		priv->fw.config);
472 }
473 
474 static DEVICE_ATTR(fw_ver, S_IRUGO, show_fw_ver, NULL);
475 
476 /*--------------------------------------------------------------------------*/
477 
478 static int __devinit timb_probe(struct pci_dev *dev,
479 	const struct pci_device_id *id)
480 {
481 	struct timberdale_device *priv;
482 	int err, i;
483 	resource_size_t mapbase;
484 	struct msix_entry *msix_entries = NULL;
485 	u8 ip_setup;
486 
487 	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
488 	if (!priv)
489 		return -ENOMEM;
490 
491 	pci_set_drvdata(dev, priv);
492 
493 	err = pci_enable_device(dev);
494 	if (err)
495 		goto err_enable;
496 
497 	mapbase = pci_resource_start(dev, 0);
498 	if (!mapbase) {
499 		dev_err(&dev->dev, "No resource\n");
500 		goto err_start;
501 	}
502 
503 	/* create a resource for the PCI master register */
504 	priv->ctl_mapbase = mapbase + CHIPCTLOFFSET;
505 	if (!request_mem_region(priv->ctl_mapbase, CHIPCTLSIZE, "timb-ctl")) {
506 		dev_err(&dev->dev, "Failed to request ctl mem\n");
507 		goto err_request;
508 	}
509 
510 	priv->ctl_membase = ioremap(priv->ctl_mapbase, CHIPCTLSIZE);
511 	if (!priv->ctl_membase) {
512 		dev_err(&dev->dev, "ioremap failed for ctl mem\n");
513 		goto err_ioremap;
514 	}
515 
516 	/* read the HW config */
517 	priv->fw.major = ioread32(priv->ctl_membase + TIMB_REV_MAJOR);
518 	priv->fw.minor = ioread32(priv->ctl_membase + TIMB_REV_MINOR);
519 	priv->fw.config = ioread32(priv->ctl_membase + TIMB_HW_CONFIG);
520 
521 	if (priv->fw.major > TIMB_SUPPORTED_MAJOR) {
522 		dev_err(&dev->dev, "The driver supports an older "
523 			"version of the FPGA, please update the driver to "
524 			"support %d.%d\n", priv->fw.major, priv->fw.minor);
525 		goto err_ioremap;
526 	}
527 	if (priv->fw.major < TIMB_SUPPORTED_MAJOR ||
528 		priv->fw.minor < TIMB_REQUIRED_MINOR) {
529 		dev_err(&dev->dev, "The FPGA image is too old (%d.%d), "
530 			"please upgrade the FPGA to at least: %d.%d\n",
531 			priv->fw.major, priv->fw.minor,
532 			TIMB_SUPPORTED_MAJOR, TIMB_REQUIRED_MINOR);
533 		goto err_ioremap;
534 	}
535 
536 	msix_entries = kzalloc(TIMBERDALE_NR_IRQS * sizeof(*msix_entries),
537 		GFP_KERNEL);
538 	if (!msix_entries)
539 		goto err_ioremap;
540 
541 	for (i = 0; i < TIMBERDALE_NR_IRQS; i++)
542 		msix_entries[i].entry = i;
543 
544 	err = pci_enable_msix(dev, msix_entries, TIMBERDALE_NR_IRQS);
545 	if (err) {
546 		dev_err(&dev->dev,
547 			"MSI-X init failed: %d, expected entries: %d\n",
548 			err, TIMBERDALE_NR_IRQS);
549 		goto err_msix;
550 	}
551 
552 	err = device_create_file(&dev->dev, &dev_attr_fw_ver);
553 	if (err)
554 		goto err_create_file;
555 
556 	/* Reset all FPGA PLB peripherals */
557 	iowrite32(0x1, priv->ctl_membase + TIMB_SW_RST);
558 
559 	/* update IRQ offsets in I2C board info */
560 	for (i = 0; i < ARRAY_SIZE(timberdale_i2c_board_info); i++)
561 		timberdale_i2c_board_info[i].irq =
562 			msix_entries[timberdale_i2c_board_info[i].irq].vector;
563 
564 	/* Update the SPI configuration depending on the HW (8 or 16 bit) */
565 	if (priv->fw.config & TIMB_HW_CONFIG_SPI_8BIT) {
566 		timberdale_xspi_platform_data.bits_per_word = 8;
567 		timberdale_xspi_platform_data.devices =
568 			timberdale_spi_8bit_board_info;
569 		timberdale_xspi_platform_data.num_devices =
570 			ARRAY_SIZE(timberdale_spi_8bit_board_info);
571 	} else {
572 		timberdale_xspi_platform_data.bits_per_word = 16;
573 		timberdale_xspi_platform_data.devices =
574 			timberdale_spi_16bit_board_info;
575 		timberdale_xspi_platform_data.num_devices =
576 			ARRAY_SIZE(timberdale_spi_16bit_board_info);
577 	}
578 
579 	ip_setup = priv->fw.config & TIMB_HW_VER_MASK;
580 	switch (ip_setup) {
581 	case TIMB_HW_VER0:
582 		err = mfd_add_devices(&dev->dev, -1,
583 			timberdale_cells_bar0_cfg0,
584 			ARRAY_SIZE(timberdale_cells_bar0_cfg0),
585 			&dev->resource[0], msix_entries[0].vector);
586 		break;
587 	case TIMB_HW_VER1:
588 		err = mfd_add_devices(&dev->dev, -1,
589 			timberdale_cells_bar0_cfg1,
590 			ARRAY_SIZE(timberdale_cells_bar0_cfg1),
591 			&dev->resource[0], msix_entries[0].vector);
592 		break;
593 	case TIMB_HW_VER2:
594 		err = mfd_add_devices(&dev->dev, -1,
595 			timberdale_cells_bar0_cfg2,
596 			ARRAY_SIZE(timberdale_cells_bar0_cfg2),
597 			&dev->resource[0], msix_entries[0].vector);
598 		break;
599 	case TIMB_HW_VER3:
600 		err = mfd_add_devices(&dev->dev, -1,
601 			timberdale_cells_bar0_cfg3,
602 			ARRAY_SIZE(timberdale_cells_bar0_cfg3),
603 			&dev->resource[0], msix_entries[0].vector);
604 		break;
605 	default:
606 		dev_err(&dev->dev, "Uknown IP setup: %d.%d.%d\n",
607 			priv->fw.major, priv->fw.minor, ip_setup);
608 		err = -ENODEV;
609 		goto err_mfd;
610 		break;
611 	}
612 
613 	if (err) {
614 		dev_err(&dev->dev, "mfd_add_devices failed: %d\n", err);
615 		goto err_mfd;
616 	}
617 
618 	err = mfd_add_devices(&dev->dev, 0,
619 		timberdale_cells_bar1, ARRAY_SIZE(timberdale_cells_bar1),
620 		&dev->resource[1], msix_entries[0].vector);
621 	if (err) {
622 		dev_err(&dev->dev, "mfd_add_devices failed: %d\n", err);
623 		goto err_mfd2;
624 	}
625 
626 	/* only version 0 and 3 have the iNand routed to SDHCI */
627 	if (((priv->fw.config & TIMB_HW_VER_MASK) == TIMB_HW_VER0) ||
628 		((priv->fw.config & TIMB_HW_VER_MASK) == TIMB_HW_VER3)) {
629 		err = mfd_add_devices(&dev->dev, 1, timberdale_cells_bar2,
630 			ARRAY_SIZE(timberdale_cells_bar2),
631 			&dev->resource[2], msix_entries[0].vector);
632 		if (err) {
633 			dev_err(&dev->dev, "mfd_add_devices failed: %d\n", err);
634 			goto err_mfd2;
635 		}
636 	}
637 
638 	kfree(msix_entries);
639 
640 	dev_info(&dev->dev,
641 		"Found Timberdale Card. Rev: %d.%d, HW config: 0x%02x\n",
642 		priv->fw.major, priv->fw.minor, priv->fw.config);
643 
644 	return 0;
645 
646 err_mfd2:
647 	mfd_remove_devices(&dev->dev);
648 err_mfd:
649 	device_remove_file(&dev->dev, &dev_attr_fw_ver);
650 err_create_file:
651 	pci_disable_msix(dev);
652 err_msix:
653 	iounmap(priv->ctl_membase);
654 err_ioremap:
655 	release_mem_region(priv->ctl_mapbase, CHIPCTLSIZE);
656 err_request:
657 	pci_set_drvdata(dev, NULL);
658 err_start:
659 	pci_disable_device(dev);
660 err_enable:
661 	kfree(msix_entries);
662 	kfree(priv);
663 	pci_set_drvdata(dev, NULL);
664 	return -ENODEV;
665 }
666 
667 static void __devexit timb_remove(struct pci_dev *dev)
668 {
669 	struct timberdale_device *priv = pci_get_drvdata(dev);
670 
671 	mfd_remove_devices(&dev->dev);
672 
673 	device_remove_file(&dev->dev, &dev_attr_fw_ver);
674 
675 	iounmap(priv->ctl_membase);
676 	release_mem_region(priv->ctl_mapbase, CHIPCTLSIZE);
677 
678 	pci_disable_msix(dev);
679 	pci_disable_device(dev);
680 	pci_set_drvdata(dev, NULL);
681 	kfree(priv);
682 }
683 
684 static struct pci_device_id timberdale_pci_tbl[] = {
685 	{ PCI_DEVICE(PCI_VENDOR_ID_TIMB, PCI_DEVICE_ID_TIMB) },
686 	{ 0 }
687 };
688 MODULE_DEVICE_TABLE(pci, timberdale_pci_tbl);
689 
690 static struct pci_driver timberdale_pci_driver = {
691 	.name = DRIVER_NAME,
692 	.id_table = timberdale_pci_tbl,
693 	.probe = timb_probe,
694 	.remove = __devexit_p(timb_remove),
695 };
696 
697 static int __init timberdale_init(void)
698 {
699 	int err;
700 
701 	err = pci_register_driver(&timberdale_pci_driver);
702 	if (err < 0) {
703 		printk(KERN_ERR
704 			"Failed to register PCI driver for %s device.\n",
705 			timberdale_pci_driver.name);
706 		return -ENODEV;
707 	}
708 
709 	printk(KERN_INFO "Driver for %s has been successfully registered.\n",
710 		timberdale_pci_driver.name);
711 
712 	return 0;
713 }
714 
715 static void __exit timberdale_exit(void)
716 {
717 	pci_unregister_driver(&timberdale_pci_driver);
718 
719 	printk(KERN_INFO "Driver for %s has been successfully unregistered.\n",
720 		timberdale_pci_driver.name);
721 }
722 
723 module_init(timberdale_init);
724 module_exit(timberdale_exit);
725 
726 MODULE_AUTHOR("Mocean Laboratories <info@mocean-labs.com>");
727 MODULE_VERSION(DRV_VERSION);
728 MODULE_LICENSE("GPL v2");
729