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