]>
Commit | Line | Data |
---|---|---|
17467f23 TT |
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 | ||
5a0e3ad6 | 12 | #include <linux/slab.h> |
17467f23 TT |
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 | */ | |
30 | struct mpc8610_hpcd_data { | |
31 | struct snd_soc_device sound_devdata; | |
32 | struct snd_soc_dai_link dai; | |
87506549 | 33 | struct snd_soc_card machine; |
17467f23 TT |
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 | /** | |
421f91d2 | 49 | * mpc8610_hpcd_machine_probe: initialize the board |
17467f23 TT |
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 | */ | |
56 | static 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 */ | |
83544994 | 62 | guts_set_dmacr(machine_data->guts, machine_data->dma_id, |
17467f23 | 63 | machine_data->dma_channel_id[0], CCSR_GUTS_DMACR_DEV_SSI); |
83544994 | 64 | guts_set_dmacr(machine_data->guts, machine_data->dma_id, |
17467f23 TT |
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 | ||
17467f23 TT |
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 | */ | |
93 | static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream) | |
94 | { | |
95 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
8cf7b2b3 LG |
96 | struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; |
97 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | |
17467f23 TT |
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. */ | |
64105cfd LG |
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; | |
17467f23 TT |
108 | } |
109 | ||
110 | /* Tell the codec driver what the serial protocol is. */ | |
64105cfd LG |
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; | |
17467f23 TT |
116 | } |
117 | ||
118 | /* | |
119 | * Tell the CPU driver what the clock frequency is, and whether it's a | |
120 | * slave or master. | |
121 | */ | |
64105cfd LG |
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; | |
17467f23 TT |
129 | } |
130 | ||
131 | /* | |
132 | * Tell the codec driver what the MCLK frequency is, and whether it's | |
133 | * a slave or master. | |
134 | */ | |
64105cfd LG |
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; | |
17467f23 TT |
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 | */ | |
153 | int 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 | ||
83544994 | 160 | guts_set_dmacr(machine_data->guts, machine_data->dma_id, |
17467f23 | 161 | machine_data->dma_channel_id[0], 0); |
83544994 | 162 | guts_set_dmacr(machine_data->guts, machine_data->dma_id, |
17467f23 TT |
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, | |
83544994 | 172 | CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA); |
17467f23 TT |
173 | break; |
174 | } | |
175 | ||
176 | return 0; | |
177 | } | |
178 | ||
179 | /** | |
180 | * mpc8610_hpcd_ops: ASoC fabric driver operations | |
181 | */ | |
182 | static struct snd_soc_ops mpc8610_hpcd_ops = { | |
183 | .startup = mpc8610_hpcd_startup, | |
184 | }; | |
185 | ||
17467f23 TT |
186 | /** |
187 | * mpc8610_hpcd_probe: OF probe function for the fabric driver | |
188 | * | |
189 | * This function gets called when an SSI node is found in the device tree. | |
190 | * | |
191 | * Although this is a fabric driver, the SSI node is the "master" node with | |
192 | * respect to audio hardware connections. Therefore, we create a new ASoC | |
193 | * device for each new SSI node that has a codec attached. | |
194 | * | |
195 | * FIXME: Currently, we only support one DMA controller, so if there are | |
196 | * multiple SSI nodes with codecs, only the first will be supported. | |
197 | * | |
198 | * FIXME: Even if we did support multiple DMA controllers, we have no | |
199 | * mechanism for assigning DMA controllers and channels to the individual | |
200 | * SSI devices. We also probably aren't compatible with the generic Elo DMA | |
201 | * device driver. | |
202 | */ | |
2dc11581 | 203 | static int mpc8610_hpcd_probe(struct platform_device *ofdev, |
17467f23 TT |
204 | const struct of_device_id *match) |
205 | { | |
61c7a080 | 206 | struct device_node *np = ofdev->dev.of_node; |
17467f23 TT |
207 | struct device_node *codec_np = NULL; |
208 | struct device_node *guts_np = NULL; | |
209 | struct device_node *dma_np = NULL; | |
210 | struct device_node *dma_channel_np = NULL; | |
211 | const phandle *codec_ph; | |
212 | const char *sprop; | |
213 | const u32 *iprop; | |
214 | struct resource res; | |
215 | struct platform_device *sound_device = NULL; | |
216 | struct mpc8610_hpcd_data *machine_data; | |
217 | struct fsl_ssi_info ssi_info; | |
218 | struct fsl_dma_info dma_info; | |
219 | int ret = -ENODEV; | |
4f3ea08a TT |
220 | unsigned int playback_dma_channel; |
221 | unsigned int capture_dma_channel; | |
17467f23 TT |
222 | |
223 | machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL); | |
224 | if (!machine_data) | |
225 | return -ENOMEM; | |
226 | ||
227 | memset(&ssi_info, 0, sizeof(ssi_info)); | |
228 | memset(&dma_info, 0, sizeof(dma_info)); | |
229 | ||
230 | ssi_info.dev = &ofdev->dev; | |
231 | ||
232 | /* | |
233 | * We are only interested in SSIs with a codec phandle in them, so let's | |
234 | * make sure this SSI has one. | |
235 | */ | |
236 | codec_ph = of_get_property(np, "codec-handle", NULL); | |
237 | if (!codec_ph) | |
238 | goto error; | |
239 | ||
240 | codec_np = of_find_node_by_phandle(*codec_ph); | |
241 | if (!codec_np) | |
242 | goto error; | |
243 | ||
244 | /* The MPC8610 HPCD only knows about the CS4270 codec, so reject | |
245 | anything else. */ | |
246 | if (!of_device_is_compatible(codec_np, "cirrus,cs4270")) | |
247 | goto error; | |
248 | ||
249 | /* Get the device ID */ | |
250 | iprop = of_get_property(np, "cell-index", NULL); | |
251 | if (!iprop) { | |
252 | dev_err(&ofdev->dev, "cell-index property not found\n"); | |
253 | ret = -EINVAL; | |
254 | goto error; | |
255 | } | |
256 | machine_data->ssi_id = *iprop; | |
257 | ssi_info.id = *iprop; | |
258 | ||
259 | /* Get the serial format and clock direction. */ | |
260 | sprop = of_get_property(np, "fsl,mode", NULL); | |
261 | if (!sprop) { | |
262 | dev_err(&ofdev->dev, "fsl,mode property not found\n"); | |
263 | ret = -EINVAL; | |
264 | goto error; | |
265 | } | |
266 | ||
267 | if (strcasecmp(sprop, "i2s-slave") == 0) { | |
268 | machine_data->dai_format = SND_SOC_DAIFMT_I2S; | |
269 | machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; | |
270 | machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; | |
271 | ||
272 | /* | |
273 | * In i2s-slave mode, the codec has its own clock source, so we | |
274 | * need to get the frequency from the device tree and pass it to | |
275 | * the codec driver. | |
276 | */ | |
277 | iprop = of_get_property(codec_np, "clock-frequency", NULL); | |
278 | if (!iprop || !*iprop) { | |
279 | dev_err(&ofdev->dev, "codec bus-frequency property " | |
280 | "is missing or invalid\n"); | |
281 | ret = -EINVAL; | |
282 | goto error; | |
283 | } | |
284 | machine_data->clk_frequency = *iprop; | |
285 | } else if (strcasecmp(sprop, "i2s-master") == 0) { | |
286 | machine_data->dai_format = SND_SOC_DAIFMT_I2S; | |
287 | machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; | |
288 | machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; | |
289 | } else if (strcasecmp(sprop, "lj-slave") == 0) { | |
290 | machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J; | |
291 | machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; | |
292 | machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; | |
293 | } else if (strcasecmp(sprop, "lj-master") == 0) { | |
294 | machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J; | |
295 | machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; | |
296 | machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; | |
e5c21571 | 297 | } else if (strcasecmp(sprop, "rj-slave") == 0) { |
17467f23 TT |
298 | machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J; |
299 | machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; | |
300 | machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; | |
301 | } else if (strcasecmp(sprop, "rj-master") == 0) { | |
302 | machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J; | |
303 | machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; | |
304 | machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; | |
305 | } else if (strcasecmp(sprop, "ac97-slave") == 0) { | |
306 | machine_data->dai_format = SND_SOC_DAIFMT_AC97; | |
307 | machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; | |
308 | machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; | |
309 | } else if (strcasecmp(sprop, "ac97-master") == 0) { | |
310 | machine_data->dai_format = SND_SOC_DAIFMT_AC97; | |
311 | machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; | |
312 | machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; | |
313 | } else { | |
314 | dev_err(&ofdev->dev, | |
315 | "unrecognized fsl,mode property \"%s\"\n", sprop); | |
316 | ret = -EINVAL; | |
317 | goto error; | |
318 | } | |
319 | ||
320 | if (!machine_data->clk_frequency) { | |
321 | dev_err(&ofdev->dev, "unknown clock frequency\n"); | |
322 | ret = -EINVAL; | |
323 | goto error; | |
324 | } | |
325 | ||
326 | /* Read the SSI information from the device tree */ | |
327 | ret = of_address_to_resource(np, 0, &res); | |
328 | if (ret) { | |
329 | dev_err(&ofdev->dev, "could not obtain SSI address\n"); | |
330 | goto error; | |
331 | } | |
332 | if (!res.start) { | |
333 | dev_err(&ofdev->dev, "invalid SSI address\n"); | |
334 | goto error; | |
335 | } | |
336 | ssi_info.ssi_phys = res.start; | |
337 | ||
338 | machine_data->ssi = ioremap(ssi_info.ssi_phys, sizeof(struct ccsr_ssi)); | |
339 | if (!machine_data->ssi) { | |
340 | dev_err(&ofdev->dev, "could not map SSI address %x\n", | |
341 | ssi_info.ssi_phys); | |
342 | ret = -EINVAL; | |
343 | goto error; | |
344 | } | |
345 | ssi_info.ssi = machine_data->ssi; | |
346 | ||
347 | ||
348 | /* Get the IRQ of the SSI */ | |
349 | machine_data->ssi_irq = irq_of_parse_and_map(np, 0); | |
350 | if (!machine_data->ssi_irq) { | |
351 | dev_err(&ofdev->dev, "could not get SSI IRQ\n"); | |
352 | ret = -EINVAL; | |
353 | goto error; | |
354 | } | |
355 | ssi_info.irq = machine_data->ssi_irq; | |
356 | ||
a454dad1 TT |
357 | /* Do we want to use asynchronous mode? */ |
358 | ssi_info.asynchronous = | |
359 | of_find_property(np, "fsl,ssi-asynchronous", NULL) ? 1 : 0; | |
360 | if (ssi_info.asynchronous) | |
361 | dev_info(&ofdev->dev, "using asynchronous mode\n"); | |
17467f23 TT |
362 | |
363 | /* Map the global utilities registers. */ | |
364 | guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts"); | |
365 | if (!guts_np) { | |
366 | dev_err(&ofdev->dev, "could not obtain address of GUTS\n"); | |
367 | ret = -EINVAL; | |
368 | goto error; | |
369 | } | |
370 | machine_data->guts = of_iomap(guts_np, 0); | |
371 | of_node_put(guts_np); | |
372 | if (!machine_data->guts) { | |
373 | dev_err(&ofdev->dev, "could not map GUTS\n"); | |
374 | ret = -EINVAL; | |
375 | goto error; | |
376 | } | |
377 | ||
4f3ea08a TT |
378 | /* Find the DMA channels to use. Both SSIs need to use the same DMA |
379 | * controller, so let's use DMA#1. | |
380 | */ | |
17467f23 TT |
381 | for_each_compatible_node(dma_np, NULL, "fsl,mpc8610-dma") { |
382 | iprop = of_get_property(dma_np, "cell-index", NULL); | |
383 | if (iprop && (*iprop == 0)) { | |
384 | of_node_put(dma_np); | |
385 | break; | |
386 | } | |
387 | } | |
388 | if (!dma_np) { | |
389 | dev_err(&ofdev->dev, "could not find DMA node\n"); | |
390 | ret = -EINVAL; | |
391 | goto error; | |
392 | } | |
393 | machine_data->dma_id = *iprop; | |
394 | ||
4f3ea08a TT |
395 | /* SSI1 needs to use DMA Channels 0 and 1, and SSI2 needs to use DMA |
396 | * channels 2 and 3. This is just how the MPC8610 is wired | |
397 | * internally. | |
398 | */ | |
399 | playback_dma_channel = (machine_data->ssi_id == 0) ? 0 : 2; | |
400 | capture_dma_channel = (machine_data->ssi_id == 0) ? 1 : 3; | |
401 | ||
17467f23 | 402 | /* |
4f3ea08a | 403 | * Find the DMA channels to use. |
17467f23 TT |
404 | */ |
405 | while ((dma_channel_np = of_get_next_child(dma_np, dma_channel_np))) { | |
406 | iprop = of_get_property(dma_channel_np, "cell-index", NULL); | |
4f3ea08a | 407 | if (iprop && (*iprop == playback_dma_channel)) { |
17467f23 TT |
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 | } | |
4f3ea08a | 415 | if (iprop && (*iprop == capture_dma_channel)) { |
17467f23 TT |
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 | ||
8a9dee59 TT |
454 | machine_data->machine.probe = mpc8610_hpcd_machine_probe; |
455 | machine_data->machine.remove = mpc8610_hpcd_machine_remove; | |
456 | machine_data->machine.name = "MPC8610 HPCD"; | |
457 | machine_data->machine.num_links = 1; | |
458 | machine_data->machine.dai_link = &machine_data->dai; | |
17467f23 TT |
459 | |
460 | /* Allocate a new audio platform device structure */ | |
461 | sound_device = platform_device_alloc("soc-audio", -1); | |
462 | if (!sound_device) { | |
463 | dev_err(&ofdev->dev, "platform device allocation failed\n"); | |
464 | ret = -ENOMEM; | |
465 | goto error; | |
466 | } | |
467 | ||
8a9dee59 | 468 | machine_data->sound_devdata.card = &machine_data->machine; |
17467f23 | 469 | machine_data->sound_devdata.codec_dev = &soc_codec_device_cs4270; |
87689d56 | 470 | machine_data->machine.platform = &fsl_soc_platform; |
17467f23 TT |
471 | |
472 | sound_device->dev.platform_data = machine_data; | |
473 | ||
474 | ||
475 | /* Set the platform device and ASoC device to point to each other */ | |
476 | platform_set_drvdata(sound_device, &machine_data->sound_devdata); | |
477 | ||
478 | machine_data->sound_devdata.dev = &sound_device->dev; | |
479 | ||
480 | ||
481 | /* Tell ASoC to probe us. This will call mpc8610_hpcd_machine.probe(), | |
482 | if it exists. */ | |
483 | ret = platform_device_add(sound_device); | |
484 | ||
485 | if (ret) { | |
486 | dev_err(&ofdev->dev, "platform device add failed\n"); | |
487 | goto error; | |
488 | } | |
489 | ||
490 | dev_set_drvdata(&ofdev->dev, sound_device); | |
491 | ||
492 | return 0; | |
493 | ||
494 | error: | |
495 | of_node_put(codec_np); | |
496 | of_node_put(guts_np); | |
497 | of_node_put(dma_np); | |
498 | of_node_put(dma_channel_np); | |
499 | ||
500 | if (sound_device) | |
501 | platform_device_unregister(sound_device); | |
502 | ||
503 | if (machine_data->dai.cpu_dai) | |
504 | fsl_ssi_destroy_dai(machine_data->dai.cpu_dai); | |
505 | ||
506 | if (ssi_info.ssi) | |
507 | iounmap(ssi_info.ssi); | |
508 | ||
509 | if (ssi_info.irq) | |
510 | irq_dispose_mapping(ssi_info.irq); | |
511 | ||
512 | if (dma_info.dma_channel[0]) | |
513 | iounmap(dma_info.dma_channel[0]); | |
514 | ||
515 | if (dma_info.dma_channel[1]) | |
516 | iounmap(dma_info.dma_channel[1]); | |
517 | ||
518 | if (dma_info.dma_irq[0]) | |
519 | irq_dispose_mapping(dma_info.dma_irq[0]); | |
520 | ||
521 | if (dma_info.dma_irq[1]) | |
522 | irq_dispose_mapping(dma_info.dma_irq[1]); | |
523 | ||
524 | if (machine_data->guts) | |
525 | iounmap(machine_data->guts); | |
526 | ||
527 | kfree(machine_data); | |
528 | ||
529 | return ret; | |
530 | } | |
531 | ||
532 | /** | |
533 | * mpc8610_hpcd_remove: remove the OF device | |
534 | * | |
535 | * This function is called when the OF device is removed. | |
536 | */ | |
2dc11581 | 537 | static int mpc8610_hpcd_remove(struct platform_device *ofdev) |
17467f23 TT |
538 | { |
539 | struct platform_device *sound_device = dev_get_drvdata(&ofdev->dev); | |
540 | struct mpc8610_hpcd_data *machine_data = | |
541 | sound_device->dev.platform_data; | |
542 | ||
543 | platform_device_unregister(sound_device); | |
544 | ||
545 | if (machine_data->dai.cpu_dai) | |
546 | fsl_ssi_destroy_dai(machine_data->dai.cpu_dai); | |
547 | ||
548 | if (machine_data->ssi) | |
549 | iounmap(machine_data->ssi); | |
550 | ||
551 | if (machine_data->dma[0]) | |
552 | iounmap(machine_data->dma[0]); | |
553 | ||
554 | if (machine_data->dma[1]) | |
555 | iounmap(machine_data->dma[1]); | |
556 | ||
557 | if (machine_data->dma_irq[0]) | |
558 | irq_dispose_mapping(machine_data->dma_irq[0]); | |
559 | ||
560 | if (machine_data->dma_irq[1]) | |
561 | irq_dispose_mapping(machine_data->dma_irq[1]); | |
562 | ||
563 | if (machine_data->guts) | |
564 | iounmap(machine_data->guts); | |
565 | ||
566 | kfree(machine_data); | |
567 | sound_device->dev.platform_data = NULL; | |
568 | ||
569 | dev_set_drvdata(&ofdev->dev, NULL); | |
570 | ||
571 | return 0; | |
572 | } | |
573 | ||
574 | static struct of_device_id mpc8610_hpcd_match[] = { | |
575 | { | |
576 | .compatible = "fsl,mpc8610-ssi", | |
577 | }, | |
578 | {} | |
579 | }; | |
580 | MODULE_DEVICE_TABLE(of, mpc8610_hpcd_match); | |
581 | ||
582 | static struct of_platform_driver mpc8610_hpcd_of_driver = { | |
4018294b GL |
583 | .driver = { |
584 | .name = "mpc8610_hpcd", | |
585 | .owner = THIS_MODULE, | |
586 | .of_match_table = mpc8610_hpcd_match, | |
587 | }, | |
17467f23 TT |
588 | .probe = mpc8610_hpcd_probe, |
589 | .remove = mpc8610_hpcd_remove, | |
590 | }; | |
591 | ||
592 | /** | |
593 | * mpc8610_hpcd_init: fabric driver initialization. | |
594 | * | |
595 | * This function is called when this module is loaded. | |
596 | */ | |
597 | static int __init mpc8610_hpcd_init(void) | |
598 | { | |
599 | int ret; | |
600 | ||
601 | printk(KERN_INFO "Freescale MPC8610 HPCD ALSA SoC fabric driver\n"); | |
602 | ||
603 | ret = of_register_platform_driver(&mpc8610_hpcd_of_driver); | |
604 | ||
605 | if (ret) | |
606 | printk(KERN_ERR | |
607 | "mpc8610-hpcd: failed to register platform driver\n"); | |
608 | ||
609 | return ret; | |
610 | } | |
611 | ||
612 | /** | |
613 | * mpc8610_hpcd_exit: fabric driver exit | |
614 | * | |
615 | * This function is called when this driver is unloaded. | |
616 | */ | |
617 | static void __exit mpc8610_hpcd_exit(void) | |
618 | { | |
619 | of_unregister_platform_driver(&mpc8610_hpcd_of_driver); | |
620 | } | |
621 | ||
622 | module_init(mpc8610_hpcd_init); | |
623 | module_exit(mpc8610_hpcd_exit); | |
624 | ||
625 | MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); | |
626 | MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC fabric driver"); | |
627 | MODULE_LICENSE("GPL"); |