xref: /openbmc/linux/sound/soc/fsl/mpc8610_hpcd.c (revision 545e4006)
1 /**
2  * Freescale MPC8610HPCD ALSA SoC Fabric driver
3  *
4  * Author: Timur Tabi <timur@freescale.com>
5  *
6  * Copyright 2007-2008 Freescale Semiconductor, Inc.  This file is licensed
7  * under the terms of the GNU General Public License version 2.  This
8  * program is licensed "as is" without any warranty of any kind, whether
9  * express or implied.
10  */
11 
12 #include <linux/module.h>
13 #include <linux/interrupt.h>
14 #include <linux/of_device.h>
15 #include <linux/of_platform.h>
16 #include <sound/soc.h>
17 #include <asm/immap_86xx.h>
18 
19 #include "../codecs/cs4270.h"
20 #include "fsl_dma.h"
21 #include "fsl_ssi.h"
22 
23 /**
24  * mpc8610_hpcd_data: fabric-specific ASoC device data
25  *
26  * This structure contains data for a single sound platform device on an
27  * MPC8610 HPCD.  Some of the data is taken from the device tree.
28  */
29 struct mpc8610_hpcd_data {
30 	struct snd_soc_device sound_devdata;
31 	struct snd_soc_dai_link dai;
32 	struct snd_soc_machine machine;
33 	unsigned int dai_format;
34 	unsigned int codec_clk_direction;
35 	unsigned int cpu_clk_direction;
36 	unsigned int clk_frequency;
37 	struct ccsr_guts __iomem *guts;
38 	struct ccsr_ssi __iomem *ssi;
39 	unsigned int ssi_id;    	/* 0 = SSI1, 1 = SSI2, etc */
40 	unsigned int ssi_irq;
41 	unsigned int dma_id;    	/* 0 = DMA1, 1 = DMA2, etc */
42 	unsigned int dma_irq[2];
43 	struct ccsr_dma_channel __iomem *dma[2];
44 	unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
45 };
46 
47 /**
48  * mpc8610_hpcd_machine_probe: initalize the board
49  *
50  * This function is called when platform_device_add() is called.  It is used
51  * to initialize the board-specific hardware.
52  *
53  * Here we program the DMACR and PMUXCR registers.
54  */
55 static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device)
56 {
57 	struct mpc8610_hpcd_data *machine_data =
58 		sound_device->dev.platform_data;
59 
60 	/* Program the signal routing between the SSI and the DMA */
61 	guts_set_dmacr(machine_data->guts, machine_data->dma_id,
62 		machine_data->dma_channel_id[0], CCSR_GUTS_DMACR_DEV_SSI);
63 	guts_set_dmacr(machine_data->guts, machine_data->dma_id,
64 		machine_data->dma_channel_id[1], CCSR_GUTS_DMACR_DEV_SSI);
65 
66 	guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id,
67 		machine_data->dma_channel_id[0], 0);
68 	guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id,
69 		machine_data->dma_channel_id[1], 0);
70 
71 	guts_set_pmuxcr_dma(machine_data->guts, 1, 0, 0);
72 	guts_set_pmuxcr_dma(machine_data->guts, 1, 3, 0);
73 	guts_set_pmuxcr_dma(machine_data->guts, 0, 3, 0);
74 
75 	switch (machine_data->ssi_id) {
76 	case 0:
77 		clrsetbits_be32(&machine_data->guts->pmuxcr,
78 			CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
79 		break;
80 	case 1:
81 		clrsetbits_be32(&machine_data->guts->pmuxcr,
82 			CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
83 		break;
84 	}
85 
86 	return 0;
87 }
88 
89 /**
90  * mpc8610_hpcd_startup: program the board with various hardware parameters
91  *
92  * This function takes board-specific information, like clock frequencies
93  * and serial data formats, and passes that information to the codec and
94  * transport drivers.
95  */
96 static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
97 {
98 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
99 	struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
100 	struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
101 	struct mpc8610_hpcd_data *machine_data =
102 		rtd->socdev->dev->platform_data;
103 	int ret = 0;
104 
105 	/* Tell the CPU driver what the serial protocol is. */
106 	ret = snd_soc_dai_set_fmt(cpu_dai, machine_data->dai_format);
107 	if (ret < 0) {
108 		dev_err(substream->pcm->card->dev,
109 			"could not set CPU driver audio format\n");
110 		return ret;
111 	}
112 
113 	/* Tell the codec driver what the serial protocol is. */
114 	ret = snd_soc_dai_set_fmt(codec_dai, machine_data->dai_format);
115 	if (ret < 0) {
116 		dev_err(substream->pcm->card->dev,
117 			"could not set codec driver audio format\n");
118 		return ret;
119 	}
120 
121 	/*
122 	 * Tell the CPU driver what the clock frequency is, and whether it's a
123 	 * slave or master.
124 	 */
125 	ret = snd_soc_dai_set_sysclk(cpu_dai, 0,
126 					machine_data->clk_frequency,
127 					machine_data->cpu_clk_direction);
128 	if (ret < 0) {
129 		dev_err(substream->pcm->card->dev,
130 			"could not set CPU driver clock parameters\n");
131 		return ret;
132 	}
133 
134 	/*
135 	 * Tell the codec driver what the MCLK frequency is, and whether it's
136 	 * a slave or master.
137 	 */
138 	ret = snd_soc_dai_set_sysclk(codec_dai, 0,
139 					machine_data->clk_frequency,
140 					machine_data->codec_clk_direction);
141 	if (ret < 0) {
142 		dev_err(substream->pcm->card->dev,
143 			"could not set codec driver clock params\n");
144 		return ret;
145 	}
146 
147 	return 0;
148 }
149 
150 /**
151  * mpc8610_hpcd_machine_remove: Remove the sound device
152  *
153  * This function is called to remove the sound device for one SSI.  We
154  * de-program the DMACR and PMUXCR register.
155  */
156 int mpc8610_hpcd_machine_remove(struct platform_device *sound_device)
157 {
158 	struct mpc8610_hpcd_data *machine_data =
159 		sound_device->dev.platform_data;
160 
161 	/* Restore the signal routing */
162 
163 	guts_set_dmacr(machine_data->guts, machine_data->dma_id,
164 		machine_data->dma_channel_id[0], 0);
165 	guts_set_dmacr(machine_data->guts, machine_data->dma_id,
166 		machine_data->dma_channel_id[1], 0);
167 
168 	switch (machine_data->ssi_id) {
169 	case 0:
170 		clrsetbits_be32(&machine_data->guts->pmuxcr,
171 			CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
172 		break;
173 	case 1:
174 		clrsetbits_be32(&machine_data->guts->pmuxcr,
175 			CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA);
176 		break;
177 	}
178 
179 	return 0;
180 }
181 
182 /**
183  * mpc8610_hpcd_ops: ASoC fabric driver operations
184  */
185 static struct snd_soc_ops mpc8610_hpcd_ops = {
186 	.startup = mpc8610_hpcd_startup,
187 };
188 
189 /**
190  * mpc8610_hpcd_machine: ASoC machine data
191  */
192 static struct snd_soc_machine mpc8610_hpcd_machine = {
193 	.probe = mpc8610_hpcd_machine_probe,
194 	.remove = mpc8610_hpcd_machine_remove,
195 	.name = "MPC8610 HPCD",
196 	.num_links = 1,
197 };
198 
199 /**
200  * mpc8610_hpcd_probe: OF probe function for the fabric driver
201  *
202  * This function gets called when an SSI node is found in the device tree.
203  *
204  * Although this is a fabric driver, the SSI node is the "master" node with
205  * respect to audio hardware connections.  Therefore, we create a new ASoC
206  * device for each new SSI node that has a codec attached.
207  *
208  * FIXME: Currently, we only support one DMA controller, so if there are
209  * multiple SSI nodes with codecs, only the first will be supported.
210  *
211  * FIXME: Even if we did support multiple DMA controllers, we have no
212  * mechanism for assigning DMA controllers and channels to the individual
213  * SSI devices.  We also probably aren't compatible with the generic Elo DMA
214  * device driver.
215  */
216 static int mpc8610_hpcd_probe(struct of_device *ofdev,
217 	const struct of_device_id *match)
218 {
219 	struct device_node *np = ofdev->node;
220 	struct device_node *codec_np = NULL;
221 	struct device_node *guts_np = NULL;
222 	struct device_node *dma_np = NULL;
223 	struct device_node *dma_channel_np = NULL;
224 	const phandle *codec_ph;
225 	const char *sprop;
226 	const u32 *iprop;
227 	struct resource res;
228 	struct platform_device *sound_device = NULL;
229 	struct mpc8610_hpcd_data *machine_data;
230 	struct fsl_ssi_info ssi_info;
231 	struct fsl_dma_info dma_info;
232 	int ret = -ENODEV;
233 
234 	machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
235 	if (!machine_data)
236 		return -ENOMEM;
237 
238 	memset(&ssi_info, 0, sizeof(ssi_info));
239 	memset(&dma_info, 0, sizeof(dma_info));
240 
241 	ssi_info.dev = &ofdev->dev;
242 
243 	/*
244 	 * We are only interested in SSIs with a codec phandle in them, so let's
245 	 * make sure this SSI has one.
246 	 */
247 	codec_ph = of_get_property(np, "codec-handle", NULL);
248 	if (!codec_ph)
249 		goto error;
250 
251 	codec_np = of_find_node_by_phandle(*codec_ph);
252 	if (!codec_np)
253 		goto error;
254 
255 	/* The MPC8610 HPCD only knows about the CS4270 codec, so reject
256 	   anything else. */
257 	if (!of_device_is_compatible(codec_np, "cirrus,cs4270"))
258 		goto error;
259 
260 	/* Get the device ID */
261 	iprop = of_get_property(np, "cell-index", NULL);
262 	if (!iprop) {
263 		dev_err(&ofdev->dev, "cell-index property not found\n");
264 		ret = -EINVAL;
265 		goto error;
266 	}
267 	machine_data->ssi_id = *iprop;
268 	ssi_info.id = *iprop;
269 
270 	/* Get the serial format and clock direction. */
271 	sprop = of_get_property(np, "fsl,mode", NULL);
272 	if (!sprop) {
273 		dev_err(&ofdev->dev, "fsl,mode property not found\n");
274 		ret = -EINVAL;
275 		goto error;
276 	}
277 
278 	if (strcasecmp(sprop, "i2s-slave") == 0) {
279 		machine_data->dai_format = SND_SOC_DAIFMT_I2S;
280 		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
281 		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
282 
283 		/*
284 		 * In i2s-slave mode, the codec has its own clock source, so we
285 		 * need to get the frequency from the device tree and pass it to
286 		 * the codec driver.
287 		 */
288 		iprop = of_get_property(codec_np, "clock-frequency", NULL);
289 		if (!iprop || !*iprop) {
290 			dev_err(&ofdev->dev, "codec bus-frequency property "
291 				"is missing or invalid\n");
292 			ret = -EINVAL;
293 			goto error;
294 		}
295 		machine_data->clk_frequency = *iprop;
296 	} else if (strcasecmp(sprop, "i2s-master") == 0) {
297 		machine_data->dai_format = SND_SOC_DAIFMT_I2S;
298 		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
299 		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
300 	} else if (strcasecmp(sprop, "lj-slave") == 0) {
301 		machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
302 		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
303 		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
304 	} else if (strcasecmp(sprop, "lj-master") == 0) {
305 		machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
306 		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
307 		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
308 	} else if (strcasecmp(sprop, "rj-slave") == 0) {
309 		machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
310 		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
311 		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
312 	} else if (strcasecmp(sprop, "rj-master") == 0) {
313 		machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
314 		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
315 		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
316 	} else if (strcasecmp(sprop, "ac97-slave") == 0) {
317 		machine_data->dai_format = SND_SOC_DAIFMT_AC97;
318 		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
319 		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
320 	} else if (strcasecmp(sprop, "ac97-master") == 0) {
321 		machine_data->dai_format = SND_SOC_DAIFMT_AC97;
322 		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
323 		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
324 	} else {
325 		dev_err(&ofdev->dev,
326 			"unrecognized fsl,mode property \"%s\"\n", sprop);
327 		ret = -EINVAL;
328 		goto error;
329 	}
330 
331 	if (!machine_data->clk_frequency) {
332 		dev_err(&ofdev->dev, "unknown clock frequency\n");
333 		ret = -EINVAL;
334 		goto error;
335 	}
336 
337 	/* Read the SSI information from the device tree */
338 	ret = of_address_to_resource(np, 0, &res);
339 	if (ret) {
340 		dev_err(&ofdev->dev, "could not obtain SSI address\n");
341 		goto error;
342 	}
343 	if (!res.start) {
344 		dev_err(&ofdev->dev, "invalid SSI address\n");
345 		goto error;
346 	}
347 	ssi_info.ssi_phys = res.start;
348 
349 	machine_data->ssi = ioremap(ssi_info.ssi_phys, sizeof(struct ccsr_ssi));
350 	if (!machine_data->ssi) {
351 		dev_err(&ofdev->dev, "could not map SSI address %x\n",
352 			ssi_info.ssi_phys);
353 		ret = -EINVAL;
354 		goto error;
355 	}
356 	ssi_info.ssi = machine_data->ssi;
357 
358 
359 	/* Get the IRQ of the SSI */
360 	machine_data->ssi_irq = irq_of_parse_and_map(np, 0);
361 	if (!machine_data->ssi_irq) {
362 		dev_err(&ofdev->dev, "could not get SSI IRQ\n");
363 		ret = -EINVAL;
364 		goto error;
365 	}
366 	ssi_info.irq = machine_data->ssi_irq;
367 
368 
369 	/* Map the global utilities registers. */
370 	guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
371 	if (!guts_np) {
372 		dev_err(&ofdev->dev, "could not obtain address of GUTS\n");
373 		ret = -EINVAL;
374 		goto error;
375 	}
376 	machine_data->guts = of_iomap(guts_np, 0);
377 	of_node_put(guts_np);
378 	if (!machine_data->guts) {
379 		dev_err(&ofdev->dev, "could not map GUTS\n");
380 		ret = -EINVAL;
381 		goto error;
382 	}
383 
384 	/* Find the DMA channels to use.  For now, we always use the first DMA
385 	   controller. */
386 	for_each_compatible_node(dma_np, NULL, "fsl,mpc8610-dma") {
387 		iprop = of_get_property(dma_np, "cell-index", NULL);
388 		if (iprop && (*iprop == 0)) {
389 			of_node_put(dma_np);
390 			break;
391 		}
392 	}
393 	if (!dma_np) {
394 		dev_err(&ofdev->dev, "could not find DMA node\n");
395 		ret = -EINVAL;
396 		goto error;
397 	}
398 	machine_data->dma_id = *iprop;
399 
400 	/*
401 	 * Find the DMA channels to use.  For now, we always use DMA channel 0
402 	 * for playback, and DMA channel 1 for capture.
403 	 */
404 	while ((dma_channel_np = of_get_next_child(dma_np, dma_channel_np))) {
405 		iprop = of_get_property(dma_channel_np, "cell-index", NULL);
406 		/* Is it DMA channel 0? */
407 		if (iprop && (*iprop == 0)) {
408 			/* dma_channel[0] and dma_irq[0] are for playback */
409 			dma_info.dma_channel[0] = of_iomap(dma_channel_np, 0);
410 			dma_info.dma_irq[0] =
411 				irq_of_parse_and_map(dma_channel_np, 0);
412 			machine_data->dma_channel_id[0] = *iprop;
413 			continue;
414 		}
415 		if (iprop && (*iprop == 1)) {
416 			/* dma_channel[1] and dma_irq[1] are for capture */
417 			dma_info.dma_channel[1] = of_iomap(dma_channel_np, 0);
418 			dma_info.dma_irq[1] =
419 				irq_of_parse_and_map(dma_channel_np, 0);
420 			machine_data->dma_channel_id[1] = *iprop;
421 			continue;
422 		}
423 	}
424 	if (!dma_info.dma_channel[0] || !dma_info.dma_channel[1] ||
425 	    !dma_info.dma_irq[0] || !dma_info.dma_irq[1]) {
426 		dev_err(&ofdev->dev, "could not find DMA channels\n");
427 		ret = -EINVAL;
428 		goto error;
429 	}
430 
431 	dma_info.ssi_stx_phys = ssi_info.ssi_phys +
432 		offsetof(struct ccsr_ssi, stx0);
433 	dma_info.ssi_srx_phys = ssi_info.ssi_phys +
434 		offsetof(struct ccsr_ssi, srx0);
435 
436 	/* We have the DMA information, so tell the DMA driver what it is */
437 	if (!fsl_dma_configure(&dma_info)) {
438 		dev_err(&ofdev->dev, "could not instantiate DMA device\n");
439 		ret = -EBUSY;
440 		goto error;
441 	}
442 
443 	/*
444 	 * Initialize our DAI data structure.  We should probably get this
445 	 * information from the device tree.
446 	 */
447 	machine_data->dai.name = "CS4270";
448 	machine_data->dai.stream_name = "CS4270";
449 
450 	machine_data->dai.cpu_dai = fsl_ssi_create_dai(&ssi_info);
451 	machine_data->dai.codec_dai = &cs4270_dai; /* The codec_dai we want */
452 	machine_data->dai.ops = &mpc8610_hpcd_ops;
453 
454 	mpc8610_hpcd_machine.dai_link = &machine_data->dai;
455 
456 	/* Allocate a new audio platform device structure */
457 	sound_device = platform_device_alloc("soc-audio", -1);
458 	if (!sound_device) {
459 		dev_err(&ofdev->dev, "platform device allocation failed\n");
460 		ret = -ENOMEM;
461 		goto error;
462 	}
463 
464 	machine_data->sound_devdata.machine = &mpc8610_hpcd_machine;
465 	machine_data->sound_devdata.codec_dev = &soc_codec_device_cs4270;
466 	machine_data->sound_devdata.platform = &fsl_soc_platform;
467 
468 	sound_device->dev.platform_data = machine_data;
469 
470 
471 	/* Set the platform device and ASoC device to point to each other */
472 	platform_set_drvdata(sound_device, &machine_data->sound_devdata);
473 
474 	machine_data->sound_devdata.dev = &sound_device->dev;
475 
476 
477 	/* Tell ASoC to probe us.  This will call mpc8610_hpcd_machine.probe(),
478 	   if it exists. */
479 	ret = platform_device_add(sound_device);
480 
481 	if (ret) {
482 		dev_err(&ofdev->dev, "platform device add failed\n");
483 		goto error;
484 	}
485 
486 	dev_set_drvdata(&ofdev->dev, sound_device);
487 
488 	return 0;
489 
490 error:
491 	of_node_put(codec_np);
492 	of_node_put(guts_np);
493 	of_node_put(dma_np);
494 	of_node_put(dma_channel_np);
495 
496 	if (sound_device)
497 		platform_device_unregister(sound_device);
498 
499 	if (machine_data->dai.cpu_dai)
500 		fsl_ssi_destroy_dai(machine_data->dai.cpu_dai);
501 
502 	if (ssi_info.ssi)
503 		iounmap(ssi_info.ssi);
504 
505 	if (ssi_info.irq)
506 		irq_dispose_mapping(ssi_info.irq);
507 
508 	if (dma_info.dma_channel[0])
509 		iounmap(dma_info.dma_channel[0]);
510 
511 	if (dma_info.dma_channel[1])
512 		iounmap(dma_info.dma_channel[1]);
513 
514 	if (dma_info.dma_irq[0])
515 		irq_dispose_mapping(dma_info.dma_irq[0]);
516 
517 	if (dma_info.dma_irq[1])
518 		irq_dispose_mapping(dma_info.dma_irq[1]);
519 
520 	if (machine_data->guts)
521 		iounmap(machine_data->guts);
522 
523 	kfree(machine_data);
524 
525 	return ret;
526 }
527 
528 /**
529  * mpc8610_hpcd_remove: remove the OF device
530  *
531  * This function is called when the OF device is removed.
532  */
533 static int mpc8610_hpcd_remove(struct of_device *ofdev)
534 {
535 	struct platform_device *sound_device = dev_get_drvdata(&ofdev->dev);
536 	struct mpc8610_hpcd_data *machine_data =
537 		sound_device->dev.platform_data;
538 
539 	platform_device_unregister(sound_device);
540 
541 	if (machine_data->dai.cpu_dai)
542 		fsl_ssi_destroy_dai(machine_data->dai.cpu_dai);
543 
544 	if (machine_data->ssi)
545 		iounmap(machine_data->ssi);
546 
547 	if (machine_data->dma[0])
548 		iounmap(machine_data->dma[0]);
549 
550 	if (machine_data->dma[1])
551 		iounmap(machine_data->dma[1]);
552 
553 	if (machine_data->dma_irq[0])
554 		irq_dispose_mapping(machine_data->dma_irq[0]);
555 
556 	if (machine_data->dma_irq[1])
557 		irq_dispose_mapping(machine_data->dma_irq[1]);
558 
559 	if (machine_data->guts)
560 		iounmap(machine_data->guts);
561 
562 	kfree(machine_data);
563 	sound_device->dev.platform_data = NULL;
564 
565 	dev_set_drvdata(&ofdev->dev, NULL);
566 
567 	return 0;
568 }
569 
570 static struct of_device_id mpc8610_hpcd_match[] = {
571 	{
572 		.compatible = "fsl,mpc8610-ssi",
573 	},
574 	{}
575 };
576 MODULE_DEVICE_TABLE(of, mpc8610_hpcd_match);
577 
578 static struct of_platform_driver mpc8610_hpcd_of_driver = {
579 	.owner  	= THIS_MODULE,
580 	.name   	= "mpc8610_hpcd",
581 	.match_table    = mpc8610_hpcd_match,
582 	.probe  	= mpc8610_hpcd_probe,
583 	.remove 	= mpc8610_hpcd_remove,
584 };
585 
586 /**
587  * mpc8610_hpcd_init: fabric driver initialization.
588  *
589  * This function is called when this module is loaded.
590  */
591 static int __init mpc8610_hpcd_init(void)
592 {
593 	int ret;
594 
595 	printk(KERN_INFO "Freescale MPC8610 HPCD ALSA SoC fabric driver\n");
596 
597 	ret = of_register_platform_driver(&mpc8610_hpcd_of_driver);
598 
599 	if (ret)
600 		printk(KERN_ERR
601 			"mpc8610-hpcd: failed to register platform driver\n");
602 
603 	return ret;
604 }
605 
606 /**
607  * mpc8610_hpcd_exit: fabric driver exit
608  *
609  * This function is called when this driver is unloaded.
610  */
611 static void __exit mpc8610_hpcd_exit(void)
612 {
613 	of_unregister_platform_driver(&mpc8610_hpcd_of_driver);
614 }
615 
616 module_init(mpc8610_hpcd_init);
617 module_exit(mpc8610_hpcd_exit);
618 
619 MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
620 MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC fabric driver");
621 MODULE_LICENSE("GPL");
622