xref: /openbmc/linux/drivers/mfd/kempld-core.c (revision 089a49b6)
1 /*
2  * Kontron PLD MFD core driver
3  *
4  * Copyright (c) 2010-2013 Kontron Europe GmbH
5  * Author: Michael Brunner <michael.brunner@kontron.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License 2 as published
9  * by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  */
16 
17 #include <linux/platform_device.h>
18 #include <linux/mfd/core.h>
19 #include <linux/mfd/kempld.h>
20 #include <linux/module.h>
21 #include <linux/dmi.h>
22 #include <linux/io.h>
23 #include <linux/delay.h>
24 
25 #define MAX_ID_LEN 4
26 static char force_device_id[MAX_ID_LEN + 1] = "";
27 module_param_string(force_device_id, force_device_id, sizeof(force_device_id), 0);
28 MODULE_PARM_DESC(force_device_id, "Override detected product");
29 
30 /*
31  * Get hardware mutex to block firmware from accessing the pld.
32  * It is possible for the firmware may hold the mutex for an extended length of
33  * time. This function will block until access has been granted.
34  */
35 static void kempld_get_hardware_mutex(struct kempld_device_data *pld)
36 {
37 	/* The mutex bit will read 1 until access has been granted */
38 	while (ioread8(pld->io_index) & KEMPLD_MUTEX_KEY)
39 		msleep(1);
40 }
41 
42 static void kempld_release_hardware_mutex(struct kempld_device_data *pld)
43 {
44 	/* The harware mutex is released when 1 is written to the mutex bit. */
45 	iowrite8(KEMPLD_MUTEX_KEY, pld->io_index);
46 }
47 
48 static int kempld_get_info_generic(struct kempld_device_data *pld)
49 {
50 	u16 version;
51 	u8 spec;
52 
53 	kempld_get_mutex(pld);
54 
55 	version = kempld_read16(pld, KEMPLD_VERSION);
56 	spec = kempld_read8(pld, KEMPLD_SPEC);
57 	pld->info.buildnr = kempld_read16(pld, KEMPLD_BUILDNR);
58 
59 	pld->info.minor = KEMPLD_VERSION_GET_MINOR(version);
60 	pld->info.major = KEMPLD_VERSION_GET_MAJOR(version);
61 	pld->info.number = KEMPLD_VERSION_GET_NUMBER(version);
62 	pld->info.type = KEMPLD_VERSION_GET_TYPE(version);
63 
64 	if (spec == 0xff) {
65 		pld->info.spec_minor = 0;
66 		pld->info.spec_major = 1;
67 	} else {
68 		pld->info.spec_minor = KEMPLD_SPEC_GET_MINOR(spec);
69 		pld->info.spec_major = KEMPLD_SPEC_GET_MAJOR(spec);
70 	}
71 
72 	if (pld->info.spec_major > 0)
73 		pld->feature_mask = kempld_read16(pld, KEMPLD_FEATURE);
74 	else
75 		pld->feature_mask = 0;
76 
77 	kempld_release_mutex(pld);
78 
79 	return 0;
80 }
81 
82 enum kempld_cells {
83 	KEMPLD_I2C = 0,
84 	KEMPLD_WDT,
85 	KEMPLD_GPIO,
86 	KEMPLD_UART,
87 };
88 
89 static struct mfd_cell kempld_devs[] = {
90 	[KEMPLD_I2C] = {
91 		.name = "kempld-i2c",
92 	},
93 	[KEMPLD_WDT] = {
94 		.name = "kempld-wdt",
95 	},
96 	[KEMPLD_GPIO] = {
97 		.name = "kempld-gpio",
98 	},
99 	[KEMPLD_UART] = {
100 		.name = "kempld-uart",
101 	},
102 };
103 
104 #define KEMPLD_MAX_DEVS	ARRAY_SIZE(kempld_devs)
105 
106 static int kempld_register_cells_generic(struct kempld_device_data *pld)
107 {
108 	struct mfd_cell devs[KEMPLD_MAX_DEVS];
109 	int i = 0;
110 
111 	if (pld->feature_mask & KEMPLD_FEATURE_BIT_I2C)
112 		devs[i++] = kempld_devs[KEMPLD_I2C];
113 
114 	if (pld->feature_mask & KEMPLD_FEATURE_BIT_WATCHDOG)
115 		devs[i++] = kempld_devs[KEMPLD_WDT];
116 
117 	if (pld->feature_mask & KEMPLD_FEATURE_BIT_GPIO)
118 		devs[i++] = kempld_devs[KEMPLD_GPIO];
119 
120 	if (pld->feature_mask & KEMPLD_FEATURE_MASK_UART)
121 		devs[i++] = kempld_devs[KEMPLD_UART];
122 
123 	return mfd_add_devices(pld->dev, -1, devs, i, NULL, 0, NULL);
124 }
125 
126 static struct resource kempld_ioresource = {
127 	.start	= KEMPLD_IOINDEX,
128 	.end	= KEMPLD_IODATA,
129 	.flags	= IORESOURCE_IO,
130 };
131 
132 static const struct kempld_platform_data kempld_platform_data_generic = {
133 	.pld_clock		= KEMPLD_CLK,
134 	.ioresource		= &kempld_ioresource,
135 	.get_hardware_mutex	= kempld_get_hardware_mutex,
136 	.release_hardware_mutex	= kempld_release_hardware_mutex,
137 	.get_info		= kempld_get_info_generic,
138 	.register_cells		= kempld_register_cells_generic,
139 };
140 
141 static struct platform_device *kempld_pdev;
142 
143 static int kempld_create_platform_device(const struct dmi_system_id *id)
144 {
145 	struct kempld_platform_data *pdata = id->driver_data;
146 	int ret;
147 
148 	kempld_pdev = platform_device_alloc("kempld", -1);
149 	if (!kempld_pdev)
150 		return -ENOMEM;
151 
152 	ret = platform_device_add_data(kempld_pdev, pdata, sizeof(*pdata));
153 	if (ret)
154 		goto err;
155 
156 	ret = platform_device_add_resources(kempld_pdev, pdata->ioresource, 1);
157 	if (ret)
158 		goto err;
159 
160 	ret = platform_device_add(kempld_pdev);
161 	if (ret)
162 		goto err;
163 
164 	return 0;
165 err:
166 	platform_device_put(kempld_pdev);
167 	return ret;
168 }
169 
170 /**
171  * kempld_read8 - read 8 bit register
172  * @pld: kempld_device_data structure describing the PLD
173  * @index: register index on the chip
174  *
175  * kempld_get_mutex must be called prior to calling this function.
176  */
177 u8 kempld_read8(struct kempld_device_data *pld, u8 index)
178 {
179 	iowrite8(index, pld->io_index);
180 	return ioread8(pld->io_data);
181 }
182 EXPORT_SYMBOL_GPL(kempld_read8);
183 
184 /**
185  * kempld_write8 - write 8 bit register
186  * @pld: kempld_device_data structure describing the PLD
187  * @index: register index on the chip
188  * @data: new register value
189  *
190  * kempld_get_mutex must be called prior to calling this function.
191  */
192 void kempld_write8(struct kempld_device_data *pld, u8 index, u8 data)
193 {
194 	iowrite8(index, pld->io_index);
195 	iowrite8(data, pld->io_data);
196 }
197 EXPORT_SYMBOL_GPL(kempld_write8);
198 
199 /**
200  * kempld_read16 - read 16 bit register
201  * @pld: kempld_device_data structure describing the PLD
202  * @index: register index on the chip
203  *
204  * kempld_get_mutex must be called prior to calling this function.
205  */
206 u16 kempld_read16(struct kempld_device_data *pld, u8 index)
207 {
208 	return kempld_read8(pld, index) | kempld_read8(pld, index + 1) << 8;
209 }
210 EXPORT_SYMBOL_GPL(kempld_read16);
211 
212 /**
213  * kempld_write16 - write 16 bit register
214  * @pld: kempld_device_data structure describing the PLD
215  * @index: register index on the chip
216  * @data: new register value
217  *
218  * kempld_get_mutex must be called prior to calling this function.
219  */
220 void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data)
221 {
222 	kempld_write8(pld, index, (u8)data);
223 	kempld_write8(pld, index + 1, (u8)(data >> 8));
224 }
225 EXPORT_SYMBOL_GPL(kempld_write16);
226 
227 /**
228  * kempld_read32 - read 32 bit register
229  * @pld: kempld_device_data structure describing the PLD
230  * @index: register index on the chip
231  *
232  * kempld_get_mutex must be called prior to calling this function.
233  */
234 u32 kempld_read32(struct kempld_device_data *pld, u8 index)
235 {
236 	return kempld_read16(pld, index) | kempld_read16(pld, index + 2) << 16;
237 }
238 EXPORT_SYMBOL_GPL(kempld_read32);
239 
240 /**
241  * kempld_write32 - write 32 bit register
242  * @pld: kempld_device_data structure describing the PLD
243  * @index: register index on the chip
244  * @data: new register value
245  *
246  * kempld_get_mutex must be called prior to calling this function.
247  */
248 void kempld_write32(struct kempld_device_data *pld, u8 index, u32 data)
249 {
250 	kempld_write16(pld, index, (u16)data);
251 	kempld_write16(pld, index + 2, (u16)(data >> 16));
252 }
253 EXPORT_SYMBOL_GPL(kempld_write32);
254 
255 /**
256  * kempld_get_mutex - acquire PLD mutex
257  * @pld: kempld_device_data structure describing the PLD
258  */
259 void kempld_get_mutex(struct kempld_device_data *pld)
260 {
261 	struct kempld_platform_data *pdata = dev_get_platdata(pld->dev);
262 
263 	mutex_lock(&pld->lock);
264 	pdata->get_hardware_mutex(pld);
265 }
266 EXPORT_SYMBOL_GPL(kempld_get_mutex);
267 
268 /**
269  * kempld_release_mutex - release PLD mutex
270  * @pld: kempld_device_data structure describing the PLD
271  */
272 void kempld_release_mutex(struct kempld_device_data *pld)
273 {
274 	struct kempld_platform_data *pdata = dev_get_platdata(pld->dev);
275 
276 	pdata->release_hardware_mutex(pld);
277 	mutex_unlock(&pld->lock);
278 }
279 EXPORT_SYMBOL_GPL(kempld_release_mutex);
280 
281 /**
282  * kempld_get_info - update device specific information
283  * @pld: kempld_device_data structure describing the PLD
284  *
285  * This function calls the configured board specific kempld_get_info_XXXX
286  * function which is responsible for gathering information about the specific
287  * hardware. The information is then stored within the pld structure.
288  */
289 static int kempld_get_info(struct kempld_device_data *pld)
290 {
291 	struct kempld_platform_data *pdata = dev_get_platdata(pld->dev);
292 
293 	return pdata->get_info(pld);
294 }
295 
296 /*
297  * kempld_register_cells - register cell drivers
298  *
299  * This function registers cell drivers for the detected hardware by calling
300  * the configured kempld_register_cells_XXXX function which is responsible
301  * to detect and register the needed cell drivers.
302  */
303 static int kempld_register_cells(struct kempld_device_data *pld)
304 {
305 	struct kempld_platform_data *pdata = dev_get_platdata(pld->dev);
306 
307 	return pdata->register_cells(pld);
308 }
309 
310 static int kempld_detect_device(struct kempld_device_data *pld)
311 {
312 	char *version_type;
313 	u8 index_reg;
314 	int ret;
315 
316 	mutex_lock(&pld->lock);
317 
318 	/* Check for empty IO space */
319 	index_reg = ioread8(pld->io_index);
320 	if (index_reg == 0xff && ioread8(pld->io_data) == 0xff) {
321 		mutex_unlock(&pld->lock);
322 		return -ENODEV;
323 	}
324 
325 	/* Release hardware mutex if aquired */
326 	if (!(index_reg & KEMPLD_MUTEX_KEY))
327 		iowrite8(KEMPLD_MUTEX_KEY, pld->io_index);
328 
329 	mutex_unlock(&pld->lock);
330 
331 	ret = kempld_get_info(pld);
332 	if (ret)
333 		return ret;
334 
335 	switch (pld->info.type) {
336 	case 0:
337 		version_type = "release";
338 		break;
339 	case 1:
340 		version_type = "debug";
341 		break;
342 	case 2:
343 		version_type = "custom";
344 		break;
345 	default:
346 		version_type = "unspecified";
347 	}
348 
349 	dev_info(pld->dev, "Found Kontron PLD %d\n", pld->info.number);
350 	dev_info(pld->dev, "%s version %d.%d build %d, specification %d.%d\n",
351 		 version_type, pld->info.major, pld->info.minor,
352 		 pld->info.buildnr, pld->info.spec_major,
353 		 pld->info.spec_minor);
354 
355 	return kempld_register_cells(pld);
356 }
357 
358 static int kempld_probe(struct platform_device *pdev)
359 {
360 	struct kempld_platform_data *pdata = dev_get_platdata(&pdev->dev);
361 	struct device *dev = &pdev->dev;
362 	struct kempld_device_data *pld;
363 	struct resource *ioport;
364 	int ret;
365 
366 	pld = devm_kzalloc(dev, sizeof(*pld), GFP_KERNEL);
367 	if (!pld)
368 		return -ENOMEM;
369 
370 	ioport = platform_get_resource(pdev, IORESOURCE_IO, 0);
371 	if (!ioport)
372 		return -EINVAL;
373 
374 	pld->io_base = devm_ioport_map(dev, ioport->start,
375 					ioport->end - ioport->start);
376 	if (!pld->io_base)
377 		return -ENOMEM;
378 
379 	pld->io_index = pld->io_base;
380 	pld->io_data = pld->io_base + 1;
381 	pld->pld_clock = pdata->pld_clock;
382 	pld->dev = dev;
383 
384 	mutex_init(&pld->lock);
385 	platform_set_drvdata(pdev, pld);
386 
387 	ret = kempld_detect_device(pld);
388 	if (ret)
389 		return ret;
390 
391 	return 0;
392 }
393 
394 static int kempld_remove(struct platform_device *pdev)
395 {
396 	struct kempld_device_data *pld = platform_get_drvdata(pdev);
397 	struct kempld_platform_data *pdata = dev_get_platdata(pld->dev);
398 
399 	mfd_remove_devices(&pdev->dev);
400 	pdata->release_hardware_mutex(pld);
401 
402 	return 0;
403 }
404 
405 static struct platform_driver kempld_driver = {
406 	.driver		= {
407 		.name	= "kempld",
408 		.owner	= THIS_MODULE,
409 	},
410 	.probe		= kempld_probe,
411 	.remove		= kempld_remove,
412 };
413 
414 static struct dmi_system_id __initdata kempld_dmi_table[] = {
415 	{
416 		.ident = "BHL6",
417 		.matches = {
418 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
419 			DMI_MATCH(DMI_BOARD_NAME, "COMe-bHL6"),
420 		},
421 		.driver_data = (void *)&kempld_platform_data_generic,
422 		.callback = kempld_create_platform_device,
423 	},
424 	{
425 		.ident = "CCR2",
426 		.matches = {
427 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
428 			DMI_MATCH(DMI_BOARD_NAME, "COMe-bIP2"),
429 		},
430 		.driver_data = (void *)&kempld_platform_data_generic,
431 		.callback = kempld_create_platform_device,
432 	}, {
433 		.ident = "CCR6",
434 		.matches = {
435 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
436 			DMI_MATCH(DMI_BOARD_NAME, "COMe-bIP6"),
437 		},
438 		.driver_data = (void *)&kempld_platform_data_generic,
439 		.callback = kempld_create_platform_device,
440 	}, {
441 		.ident = "CHR2",
442 		.matches = {
443 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
444 			DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-SC T2"),
445 		},
446 		.driver_data = (void *)&kempld_platform_data_generic,
447 		.callback = kempld_create_platform_device,
448 	}, {
449 		.ident = "CHR2",
450 		.matches = {
451 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
452 			DMI_MATCH(DMI_BOARD_NAME, "ETXe-SC T2"),
453 		},
454 		.driver_data = (void *)&kempld_platform_data_generic,
455 		.callback = kempld_create_platform_device,
456 	}, {
457 		.ident = "CHR2",
458 		.matches = {
459 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
460 			DMI_MATCH(DMI_BOARD_NAME, "COMe-bSC2"),
461 		},
462 		.driver_data = (void *)&kempld_platform_data_generic,
463 		.callback = kempld_create_platform_device,
464 	}, {
465 		.ident = "CHR6",
466 		.matches = {
467 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
468 			DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-SC T6"),
469 		},
470 		.driver_data = (void *)&kempld_platform_data_generic,
471 		.callback = kempld_create_platform_device,
472 	}, {
473 		.ident = "CHR6",
474 		.matches = {
475 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
476 			DMI_MATCH(DMI_BOARD_NAME, "ETXe-SC T6"),
477 		},
478 		.driver_data = (void *)&kempld_platform_data_generic,
479 		.callback = kempld_create_platform_device,
480 	}, {
481 		.ident = "CHR6",
482 		.matches = {
483 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
484 			DMI_MATCH(DMI_BOARD_NAME, "COMe-bSC6"),
485 		},
486 		.driver_data = (void *)&kempld_platform_data_generic,
487 		.callback = kempld_create_platform_device,
488 	}, {
489 		.ident = "CNTG",
490 		.matches = {
491 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
492 			DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-PC"),
493 		},
494 		.driver_data = (void *)&kempld_platform_data_generic,
495 		.callback = kempld_create_platform_device,
496 	}, {
497 		.ident = "CNTG",
498 		.matches = {
499 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
500 			DMI_MATCH(DMI_BOARD_NAME, "COMe-bPC2"),
501 		},
502 		.driver_data = (void *)&kempld_platform_data_generic,
503 		.callback = kempld_create_platform_device,
504 	}, {
505 		.ident = "CNTX",
506 		.matches = {
507 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
508 			DMI_MATCH(DMI_BOARD_NAME, "PXT"),
509 		},
510 		.driver_data = (void *)&kempld_platform_data_generic,
511 		.callback = kempld_create_platform_device,
512 	}, {
513 		.ident = "FRI2",
514 		.matches = {
515 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
516 			DMI_MATCH(DMI_BIOS_VERSION, "FRI2"),
517 		},
518 		.driver_data = (void *)&kempld_platform_data_generic,
519 		.callback = kempld_create_platform_device,
520 	}, {
521 		.ident = "FRI2",
522 		.matches = {
523 			DMI_MATCH(DMI_PRODUCT_NAME, "Fish River Island II"),
524 		},
525 		.driver_data = (void *)&kempld_platform_data_generic,
526 		.callback = kempld_create_platform_device,
527 	}, {
528 		.ident = "MBR1",
529 		.matches = {
530 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
531 			DMI_MATCH(DMI_BOARD_NAME, "ETX-OH"),
532 		},
533 		.driver_data = (void *)&kempld_platform_data_generic,
534 		.callback = kempld_create_platform_device,
535 	}, {
536 		.ident = "NTC1",
537 		.matches = {
538 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
539 			DMI_MATCH(DMI_BOARD_NAME, "nanoETXexpress-TT"),
540 		},
541 		.driver_data = (void *)&kempld_platform_data_generic,
542 		.callback = kempld_create_platform_device,
543 	}, {
544 		.ident = "NTC1",
545 		.matches = {
546 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
547 			DMI_MATCH(DMI_BOARD_NAME, "nETXe-TT"),
548 		},
549 		.driver_data = (void *)&kempld_platform_data_generic,
550 		.callback = kempld_create_platform_device,
551 	}, {
552 		.ident = "NTC1",
553 		.matches = {
554 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
555 			DMI_MATCH(DMI_BOARD_NAME, "COMe-mTT"),
556 		},
557 		.driver_data = (void *)&kempld_platform_data_generic,
558 		.callback = kempld_create_platform_device,
559 	}, {
560 		.ident = "NUP1",
561 		.matches = {
562 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
563 			DMI_MATCH(DMI_BOARD_NAME, "COMe-mCT"),
564 		},
565 		.driver_data = (void *)&kempld_platform_data_generic,
566 		.callback = kempld_create_platform_device,
567 	}, {
568 		.ident = "UNP1",
569 		.matches = {
570 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
571 			DMI_MATCH(DMI_BOARD_NAME, "microETXexpress-DC"),
572 		},
573 		.driver_data = (void *)&kempld_platform_data_generic,
574 		.callback = kempld_create_platform_device,
575 	}, {
576 		.ident = "UNP1",
577 		.matches = {
578 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
579 			DMI_MATCH(DMI_BOARD_NAME, "COMe-cDC2"),
580 		},
581 		.driver_data = (void *)&kempld_platform_data_generic,
582 		.callback = kempld_create_platform_device,
583 	}, {
584 		.ident = "UNTG",
585 		.matches = {
586 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
587 			DMI_MATCH(DMI_BOARD_NAME, "microETXexpress-PC"),
588 		},
589 		.driver_data = (void *)&kempld_platform_data_generic,
590 		.callback = kempld_create_platform_device,
591 	}, {
592 		.ident = "UNTG",
593 		.matches = {
594 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
595 			DMI_MATCH(DMI_BOARD_NAME, "COMe-cPC2"),
596 		},
597 		.driver_data = (void *)&kempld_platform_data_generic,
598 		.callback = kempld_create_platform_device,
599 	}, {
600 		.ident = "UUP6",
601 		.matches = {
602 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
603 			DMI_MATCH(DMI_BOARD_NAME, "COMe-cCT6"),
604 		},
605 		.driver_data = (void *)&kempld_platform_data_generic,
606 		.callback = kempld_create_platform_device,
607 	},
608 	{
609 		.ident = "UTH6",
610 		.matches = {
611 			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
612 			DMI_MATCH(DMI_BOARD_NAME, "COMe-cTH6"),
613 		},
614 		.driver_data = (void *)&kempld_platform_data_generic,
615 		.callback = kempld_create_platform_device,
616 	},
617 	{}
618 };
619 MODULE_DEVICE_TABLE(dmi, kempld_dmi_table);
620 
621 static int __init kempld_init(void)
622 {
623 	const struct dmi_system_id *id;
624 	int ret;
625 
626 	if (force_device_id[0]) {
627 		for (id = kempld_dmi_table; id->matches[0].slot != DMI_NONE; id++)
628 			if (strstr(id->ident, force_device_id))
629 				if (id->callback && id->callback(id))
630 					break;
631 		if (id->matches[0].slot == DMI_NONE)
632 			return -ENODEV;
633 	} else {
634 		if (!dmi_check_system(kempld_dmi_table))
635 			return -ENODEV;
636 	}
637 
638 	ret = platform_driver_register(&kempld_driver);
639 	if (ret)
640 		return ret;
641 
642 	return 0;
643 }
644 
645 static void __exit kempld_exit(void)
646 {
647 	if (kempld_pdev)
648 		platform_device_unregister(kempld_pdev);
649 
650 	platform_driver_unregister(&kempld_driver);
651 }
652 
653 module_init(kempld_init);
654 module_exit(kempld_exit);
655 
656 MODULE_DESCRIPTION("KEM PLD Core Driver");
657 MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
658 MODULE_LICENSE("GPL");
659 MODULE_ALIAS("platform:kempld-core");
660