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