diff options
Diffstat (limited to 'sound/soc/fsl/p1022_ds.c')
-rw-r--r-- | sound/soc/fsl/p1022_ds.c | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c new file mode 100644 index 000000000000..63b9eaa1ebc2 --- /dev/null +++ b/sound/soc/fsl/p1022_ds.c | |||
@@ -0,0 +1,591 @@ | |||
1 | /** | ||
2 | * Freescale P1022DS ALSA SoC Machine driver | ||
3 | * | ||
4 | * Author: Timur Tabi <timur@freescale.com> | ||
5 | * | ||
6 | * Copyright 2010 Freescale Semiconductor, Inc. | ||
7 | * | ||
8 | * This file is licensed under the terms of the GNU General Public License | ||
9 | * version 2. This program is licensed "as is" without any warranty of any | ||
10 | * kind, whether express or implied. | ||
11 | */ | ||
12 | |||
13 | #include <linux/module.h> | ||
14 | #include <linux/interrupt.h> | ||
15 | #include <linux/of_device.h> | ||
16 | #include <linux/slab.h> | ||
17 | #include <sound/soc.h> | ||
18 | #include <asm/fsl_guts.h> | ||
19 | |||
20 | #include "fsl_dma.h" | ||
21 | #include "fsl_ssi.h" | ||
22 | |||
23 | /* P1022-specific PMUXCR and DMUXCR bit definitions */ | ||
24 | |||
25 | #define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK 0x0001c000 | ||
26 | #define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI 0x00010000 | ||
27 | #define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI 0x00018000 | ||
28 | |||
29 | #define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK 0x00000c00 | ||
30 | #define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI 0x00000000 | ||
31 | |||
32 | #define CCSR_GUTS_DMUXCR_PAD 1 /* DMA controller/channel set to pad */ | ||
33 | #define CCSR_GUTS_DMUXCR_SSI 2 /* DMA controller/channel set to SSI */ | ||
34 | |||
35 | /* | ||
36 | * Set the DMACR register in the GUTS | ||
37 | * | ||
38 | * The DMACR register determines the source of initiated transfers for each | ||
39 | * channel on each DMA controller. Rather than have a bunch of repetitive | ||
40 | * macros for the bit patterns, we just have a function that calculates | ||
41 | * them. | ||
42 | * | ||
43 | * guts: Pointer to GUTS structure | ||
44 | * co: The DMA controller (0 or 1) | ||
45 | * ch: The channel on the DMA controller (0, 1, 2, or 3) | ||
46 | * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx) | ||
47 | */ | ||
48 | static inline void guts_set_dmuxcr(struct ccsr_guts_85xx __iomem *guts, | ||
49 | unsigned int co, unsigned int ch, unsigned int device) | ||
50 | { | ||
51 | unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch)); | ||
52 | |||
53 | clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift); | ||
54 | } | ||
55 | |||
56 | /* There's only one global utilities register */ | ||
57 | static phys_addr_t guts_phys; | ||
58 | |||
59 | #define DAI_NAME_SIZE 32 | ||
60 | |||
61 | /** | ||
62 | * machine_data: machine-specific ASoC device data | ||
63 | * | ||
64 | * This structure contains data for a single sound platform device on an | ||
65 | * P1022 DS. Some of the data is taken from the device tree. | ||
66 | */ | ||
67 | struct machine_data { | ||
68 | struct snd_soc_dai_link dai[2]; | ||
69 | struct snd_soc_card card; | ||
70 | unsigned int dai_format; | ||
71 | unsigned int codec_clk_direction; | ||
72 | unsigned int cpu_clk_direction; | ||
73 | unsigned int clk_frequency; | ||
74 | unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */ | ||
75 | unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ | ||
76 | unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ | ||
77 | char codec_name[DAI_NAME_SIZE]; | ||
78 | char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ | ||
79 | }; | ||
80 | |||
81 | /** | ||
82 | * p1022_ds_machine_probe: initialize the board | ||
83 | * | ||
84 | * This function is used to initialize the board-specific hardware. | ||
85 | * | ||
86 | * Here we program the DMACR and PMUXCR registers. | ||
87 | */ | ||
88 | static int p1022_ds_machine_probe(struct platform_device *sound_device) | ||
89 | { | ||
90 | struct snd_soc_card *card = platform_get_drvdata(sound_device); | ||
91 | struct machine_data *mdata = | ||
92 | container_of(card, struct machine_data, card); | ||
93 | struct ccsr_guts_85xx __iomem *guts; | ||
94 | |||
95 | guts = ioremap(guts_phys, sizeof(struct ccsr_guts_85xx)); | ||
96 | if (!guts) { | ||
97 | dev_err(card->dev, "could not map global utilities\n"); | ||
98 | return -ENOMEM; | ||
99 | } | ||
100 | |||
101 | /* Enable SSI Tx signal */ | ||
102 | clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK, | ||
103 | CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI); | ||
104 | |||
105 | /* Enable SSI Rx signal */ | ||
106 | clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK, | ||
107 | CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI); | ||
108 | |||
109 | /* Enable DMA Channel for SSI */ | ||
110 | guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], | ||
111 | CCSR_GUTS_DMUXCR_SSI); | ||
112 | |||
113 | guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], | ||
114 | CCSR_GUTS_DMUXCR_SSI); | ||
115 | |||
116 | iounmap(guts); | ||
117 | |||
118 | return 0; | ||
119 | } | ||
120 | |||
121 | /** | ||
122 | * p1022_ds_startup: program the board with various hardware parameters | ||
123 | * | ||
124 | * This function takes board-specific information, like clock frequencies | ||
125 | * and serial data formats, and passes that information to the codec and | ||
126 | * transport drivers. | ||
127 | */ | ||
128 | static int p1022_ds_startup(struct snd_pcm_substream *substream) | ||
129 | { | ||
130 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
131 | struct machine_data *mdata = | ||
132 | container_of(rtd->card, struct machine_data, card); | ||
133 | struct device *dev = rtd->card->dev; | ||
134 | int ret = 0; | ||
135 | |||
136 | /* Tell the codec driver what the serial protocol is. */ | ||
137 | ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format); | ||
138 | if (ret < 0) { | ||
139 | dev_err(dev, "could not set codec driver audio format\n"); | ||
140 | return ret; | ||
141 | } | ||
142 | |||
143 | /* | ||
144 | * Tell the codec driver what the MCLK frequency is, and whether it's | ||
145 | * a slave or master. | ||
146 | */ | ||
147 | ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, mdata->clk_frequency, | ||
148 | mdata->codec_clk_direction); | ||
149 | if (ret < 0) { | ||
150 | dev_err(dev, "could not set codec driver clock params\n"); | ||
151 | return ret; | ||
152 | } | ||
153 | |||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | /** | ||
158 | * p1022_ds_machine_remove: Remove the sound device | ||
159 | * | ||
160 | * This function is called to remove the sound device for one SSI. We | ||
161 | * de-program the DMACR and PMUXCR register. | ||
162 | */ | ||
163 | static int p1022_ds_machine_remove(struct platform_device *sound_device) | ||
164 | { | ||
165 | struct snd_soc_card *card = platform_get_drvdata(sound_device); | ||
166 | struct machine_data *mdata = | ||
167 | container_of(card, struct machine_data, card); | ||
168 | struct ccsr_guts_85xx __iomem *guts; | ||
169 | |||
170 | guts = ioremap(guts_phys, sizeof(struct ccsr_guts_85xx)); | ||
171 | if (!guts) { | ||
172 | dev_err(card->dev, "could not map global utilities\n"); | ||
173 | return -ENOMEM; | ||
174 | } | ||
175 | |||
176 | /* Restore the signal routing */ | ||
177 | clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK); | ||
178 | clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK); | ||
179 | guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0); | ||
180 | guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0); | ||
181 | |||
182 | iounmap(guts); | ||
183 | |||
184 | return 0; | ||
185 | } | ||
186 | |||
187 | /** | ||
188 | * p1022_ds_ops: ASoC machine driver operations | ||
189 | */ | ||
190 | static struct snd_soc_ops p1022_ds_ops = { | ||
191 | .startup = p1022_ds_startup, | ||
192 | }; | ||
193 | |||
194 | /** | ||
195 | * get_node_by_phandle_name - get a node by its phandle name | ||
196 | * | ||
197 | * This function takes a node, the name of a property in that node, and a | ||
198 | * compatible string. Assuming the property is a phandle to another node, | ||
199 | * it returns that node, (optionally) if that node is compatible. | ||
200 | * | ||
201 | * If the property is not a phandle, or the node it points to is not compatible | ||
202 | * with the specific string, then NULL is returned. | ||
203 | */ | ||
204 | static struct device_node *get_node_by_phandle_name(struct device_node *np, | ||
205 | const char *name, const char *compatible) | ||
206 | { | ||
207 | np = of_parse_phandle(np, name, 0); | ||
208 | if (!np) | ||
209 | return NULL; | ||
210 | |||
211 | if (!of_device_is_compatible(np, compatible)) { | ||
212 | of_node_put(np); | ||
213 | return NULL; | ||
214 | } | ||
215 | |||
216 | return np; | ||
217 | } | ||
218 | |||
219 | /** | ||
220 | * get_parent_cell_index -- return the cell-index of the parent of a node | ||
221 | * | ||
222 | * Return the value of the cell-index property of the parent of the given | ||
223 | * node. This is used for DMA channel nodes that need to know the DMA ID | ||
224 | * of the controller they are on. | ||
225 | */ | ||
226 | static int get_parent_cell_index(struct device_node *np) | ||
227 | { | ||
228 | struct device_node *parent = of_get_parent(np); | ||
229 | const u32 *iprop; | ||
230 | int ret = -1; | ||
231 | |||
232 | if (!parent) | ||
233 | return -1; | ||
234 | |||
235 | iprop = of_get_property(parent, "cell-index", NULL); | ||
236 | if (iprop) | ||
237 | ret = *iprop; | ||
238 | |||
239 | of_node_put(parent); | ||
240 | |||
241 | return ret; | ||
242 | } | ||
243 | |||
244 | /** | ||
245 | * codec_node_dev_name - determine the dev_name for a codec node | ||
246 | * | ||
247 | * This function determines the dev_name for an I2C node. This is the name | ||
248 | * that would be returned by dev_name() if this device_node were part of a | ||
249 | * 'struct device' It's ugly and hackish, but it works. | ||
250 | * | ||
251 | * The dev_name for such devices include the bus number and I2C address. For | ||
252 | * example, "cs4270-codec.0-004f". | ||
253 | */ | ||
254 | static int codec_node_dev_name(struct device_node *np, char *buf, size_t len) | ||
255 | { | ||
256 | const u32 *iprop; | ||
257 | int bus, addr; | ||
258 | char temp[DAI_NAME_SIZE]; | ||
259 | |||
260 | of_modalias_node(np, temp, DAI_NAME_SIZE); | ||
261 | |||
262 | iprop = of_get_property(np, "reg", NULL); | ||
263 | if (!iprop) | ||
264 | return -EINVAL; | ||
265 | |||
266 | addr = *iprop; | ||
267 | |||
268 | bus = get_parent_cell_index(np); | ||
269 | if (bus < 0) | ||
270 | return bus; | ||
271 | |||
272 | snprintf(buf, len, "%s-codec.%u-%04x", temp, bus, addr); | ||
273 | |||
274 | return 0; | ||
275 | } | ||
276 | |||
277 | static int get_dma_channel(struct device_node *ssi_np, | ||
278 | const char *compatible, | ||
279 | struct snd_soc_dai_link *dai, | ||
280 | unsigned int *dma_channel_id, | ||
281 | unsigned int *dma_id) | ||
282 | { | ||
283 | struct resource res; | ||
284 | struct device_node *dma_channel_np; | ||
285 | const u32 *iprop; | ||
286 | int ret; | ||
287 | |||
288 | dma_channel_np = get_node_by_phandle_name(ssi_np, compatible, | ||
289 | "fsl,ssi-dma-channel"); | ||
290 | if (!dma_channel_np) | ||
291 | return -EINVAL; | ||
292 | |||
293 | /* Determine the dev_name for the device_node. This code mimics the | ||
294 | * behavior of of_device_make_bus_id(). We need this because ASoC uses | ||
295 | * the dev_name() of the device to match the platform (DMA) device with | ||
296 | * the CPU (SSI) device. It's all ugly and hackish, but it works (for | ||
297 | * now). | ||
298 | * | ||
299 | * dai->platform name should already point to an allocated buffer. | ||
300 | */ | ||
301 | ret = of_address_to_resource(dma_channel_np, 0, &res); | ||
302 | if (ret) | ||
303 | return ret; | ||
304 | snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s", | ||
305 | (unsigned long long) res.start, dma_channel_np->name); | ||
306 | |||
307 | iprop = of_get_property(dma_channel_np, "cell-index", NULL); | ||
308 | if (!iprop) { | ||
309 | of_node_put(dma_channel_np); | ||
310 | return -EINVAL; | ||
311 | } | ||
312 | |||
313 | *dma_channel_id = *iprop; | ||
314 | *dma_id = get_parent_cell_index(dma_channel_np); | ||
315 | of_node_put(dma_channel_np); | ||
316 | |||
317 | return 0; | ||
318 | } | ||
319 | |||
320 | /** | ||
321 | * p1022_ds_probe: platform probe function for the machine driver | ||
322 | * | ||
323 | * Although this is a machine driver, the SSI node is the "master" node with | ||
324 | * respect to audio hardware connections. Therefore, we create a new ASoC | ||
325 | * device for each new SSI node that has a codec attached. | ||
326 | */ | ||
327 | static int p1022_ds_probe(struct platform_device *pdev) | ||
328 | { | ||
329 | struct device *dev = pdev->dev.parent; | ||
330 | /* ssi_pdev is the platform device for the SSI node that probed us */ | ||
331 | struct platform_device *ssi_pdev = | ||
332 | container_of(dev, struct platform_device, dev); | ||
333 | struct device_node *np = ssi_pdev->dev.of_node; | ||
334 | struct device_node *codec_np = NULL; | ||
335 | struct platform_device *sound_device = NULL; | ||
336 | struct machine_data *mdata; | ||
337 | int ret = -ENODEV; | ||
338 | const char *sprop; | ||
339 | const u32 *iprop; | ||
340 | |||
341 | /* Find the codec node for this SSI. */ | ||
342 | codec_np = of_parse_phandle(np, "codec-handle", 0); | ||
343 | if (!codec_np) { | ||
344 | dev_err(dev, "could not find codec node\n"); | ||
345 | return -EINVAL; | ||
346 | } | ||
347 | |||
348 | mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL); | ||
349 | if (!mdata) { | ||
350 | ret = -ENOMEM; | ||
351 | goto error_put; | ||
352 | } | ||
353 | |||
354 | mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); | ||
355 | mdata->dai[0].ops = &p1022_ds_ops; | ||
356 | |||
357 | /* Determine the codec name, it will be used as the codec DAI name */ | ||
358 | ret = codec_node_dev_name(codec_np, mdata->codec_name, DAI_NAME_SIZE); | ||
359 | if (ret) { | ||
360 | dev_err(&pdev->dev, "invalid codec node %s\n", | ||
361 | codec_np->full_name); | ||
362 | ret = -EINVAL; | ||
363 | goto error; | ||
364 | } | ||
365 | mdata->dai[0].codec_name = mdata->codec_name; | ||
366 | |||
367 | /* We register two DAIs per SSI, one for playback and the other for | ||
368 | * capture. We support codecs that have separate DAIs for both playback | ||
369 | * and capture. | ||
370 | */ | ||
371 | memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link)); | ||
372 | |||
373 | /* The DAI names from the codec (snd_soc_dai_driver.name) */ | ||
374 | mdata->dai[0].codec_dai_name = "wm8776-hifi-playback"; | ||
375 | mdata->dai[1].codec_dai_name = "wm8776-hifi-capture"; | ||
376 | |||
377 | /* Get the device ID */ | ||
378 | iprop = of_get_property(np, "cell-index", NULL); | ||
379 | if (!iprop) { | ||
380 | dev_err(&pdev->dev, "cell-index property not found\n"); | ||
381 | ret = -EINVAL; | ||
382 | goto error; | ||
383 | } | ||
384 | mdata->ssi_id = *iprop; | ||
385 | |||
386 | /* Get the serial format and clock direction. */ | ||
387 | sprop = of_get_property(np, "fsl,mode", NULL); | ||
388 | if (!sprop) { | ||
389 | dev_err(&pdev->dev, "fsl,mode property not found\n"); | ||
390 | ret = -EINVAL; | ||
391 | goto error; | ||
392 | } | ||
393 | |||
394 | if (strcasecmp(sprop, "i2s-slave") == 0) { | ||
395 | mdata->dai_format = SND_SOC_DAIFMT_I2S; | ||
396 | mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; | ||
397 | mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; | ||
398 | |||
399 | /* In i2s-slave mode, the codec has its own clock source, so we | ||
400 | * need to get the frequency from the device tree and pass it to | ||
401 | * the codec driver. | ||
402 | */ | ||
403 | iprop = of_get_property(codec_np, "clock-frequency", NULL); | ||
404 | if (!iprop || !*iprop) { | ||
405 | dev_err(&pdev->dev, "codec bus-frequency " | ||
406 | "property is missing or invalid\n"); | ||
407 | ret = -EINVAL; | ||
408 | goto error; | ||
409 | } | ||
410 | mdata->clk_frequency = *iprop; | ||
411 | } else if (strcasecmp(sprop, "i2s-master") == 0) { | ||
412 | mdata->dai_format = SND_SOC_DAIFMT_I2S; | ||
413 | mdata->codec_clk_direction = SND_SOC_CLOCK_IN; | ||
414 | mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; | ||
415 | } else if (strcasecmp(sprop, "lj-slave") == 0) { | ||
416 | mdata->dai_format = SND_SOC_DAIFMT_LEFT_J; | ||
417 | mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; | ||
418 | mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; | ||
419 | } else if (strcasecmp(sprop, "lj-master") == 0) { | ||
420 | mdata->dai_format = SND_SOC_DAIFMT_LEFT_J; | ||
421 | mdata->codec_clk_direction = SND_SOC_CLOCK_IN; | ||
422 | mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; | ||
423 | } else if (strcasecmp(sprop, "rj-slave") == 0) { | ||
424 | mdata->dai_format = SND_SOC_DAIFMT_RIGHT_J; | ||
425 | mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; | ||
426 | mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; | ||
427 | } else if (strcasecmp(sprop, "rj-master") == 0) { | ||
428 | mdata->dai_format = SND_SOC_DAIFMT_RIGHT_J; | ||
429 | mdata->codec_clk_direction = SND_SOC_CLOCK_IN; | ||
430 | mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; | ||
431 | } else if (strcasecmp(sprop, "ac97-slave") == 0) { | ||
432 | mdata->dai_format = SND_SOC_DAIFMT_AC97; | ||
433 | mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; | ||
434 | mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; | ||
435 | } else if (strcasecmp(sprop, "ac97-master") == 0) { | ||
436 | mdata->dai_format = SND_SOC_DAIFMT_AC97; | ||
437 | mdata->codec_clk_direction = SND_SOC_CLOCK_IN; | ||
438 | mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; | ||
439 | } else { | ||
440 | dev_err(&pdev->dev, | ||
441 | "unrecognized fsl,mode property '%s'\n", sprop); | ||
442 | ret = -EINVAL; | ||
443 | goto error; | ||
444 | } | ||
445 | |||
446 | if (!mdata->clk_frequency) { | ||
447 | dev_err(&pdev->dev, "unknown clock frequency\n"); | ||
448 | ret = -EINVAL; | ||
449 | goto error; | ||
450 | } | ||
451 | |||
452 | /* Find the playback DMA channel to use. */ | ||
453 | mdata->dai[0].platform_name = mdata->platform_name[0]; | ||
454 | ret = get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0], | ||
455 | &mdata->dma_channel_id[0], | ||
456 | &mdata->dma_id[0]); | ||
457 | if (ret) { | ||
458 | dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n"); | ||
459 | goto error; | ||
460 | } | ||
461 | |||
462 | /* Find the capture DMA channel to use. */ | ||
463 | mdata->dai[1].platform_name = mdata->platform_name[1]; | ||
464 | ret = get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1], | ||
465 | &mdata->dma_channel_id[1], | ||
466 | &mdata->dma_id[1]); | ||
467 | if (ret) { | ||
468 | dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n"); | ||
469 | goto error; | ||
470 | } | ||
471 | |||
472 | /* Initialize our DAI data structure. */ | ||
473 | mdata->dai[0].stream_name = "playback"; | ||
474 | mdata->dai[1].stream_name = "capture"; | ||
475 | mdata->dai[0].name = mdata->dai[0].stream_name; | ||
476 | mdata->dai[1].name = mdata->dai[1].stream_name; | ||
477 | |||
478 | mdata->card.probe = p1022_ds_machine_probe; | ||
479 | mdata->card.remove = p1022_ds_machine_remove; | ||
480 | mdata->card.name = pdev->name; /* The platform driver name */ | ||
481 | mdata->card.num_links = 2; | ||
482 | mdata->card.dai_link = mdata->dai; | ||
483 | |||
484 | /* Allocate a new audio platform device structure */ | ||
485 | sound_device = platform_device_alloc("soc-audio", -1); | ||
486 | if (!sound_device) { | ||
487 | dev_err(&pdev->dev, "platform device alloc failed\n"); | ||
488 | ret = -ENOMEM; | ||
489 | goto error; | ||
490 | } | ||
491 | |||
492 | /* Associate the card data with the sound device */ | ||
493 | platform_set_drvdata(sound_device, &mdata->card); | ||
494 | |||
495 | /* Register with ASoC */ | ||
496 | ret = platform_device_add(sound_device); | ||
497 | if (ret) { | ||
498 | dev_err(&pdev->dev, "platform device add failed\n"); | ||
499 | goto error; | ||
500 | } | ||
501 | |||
502 | of_node_put(codec_np); | ||
503 | |||
504 | return 0; | ||
505 | |||
506 | error: | ||
507 | if (sound_device) | ||
508 | platform_device_unregister(sound_device); | ||
509 | |||
510 | kfree(mdata); | ||
511 | error_put: | ||
512 | of_node_put(codec_np); | ||
513 | return ret; | ||
514 | } | ||
515 | |||
516 | /** | ||
517 | * p1022_ds_remove: remove the platform device | ||
518 | * | ||
519 | * This function is called when the platform device is removed. | ||
520 | */ | ||
521 | static int __devexit p1022_ds_remove(struct platform_device *pdev) | ||
522 | { | ||
523 | struct platform_device *sound_device = dev_get_drvdata(&pdev->dev); | ||
524 | struct snd_soc_card *card = platform_get_drvdata(sound_device); | ||
525 | struct machine_data *mdata = | ||
526 | container_of(card, struct machine_data, card); | ||
527 | |||
528 | platform_device_unregister(sound_device); | ||
529 | |||
530 | kfree(mdata); | ||
531 | sound_device->dev.platform_data = NULL; | ||
532 | |||
533 | dev_set_drvdata(&pdev->dev, NULL); | ||
534 | |||
535 | return 0; | ||
536 | } | ||
537 | |||
538 | static struct platform_driver p1022_ds_driver = { | ||
539 | .probe = p1022_ds_probe, | ||
540 | .remove = __devexit_p(p1022_ds_remove), | ||
541 | .driver = { | ||
542 | /* The name must match the 'model' property in the device tree, | ||
543 | * in lowercase letters, but only the part after that last | ||
544 | * comma. This is because some model properties have a "fsl," | ||
545 | * prefix. | ||
546 | */ | ||
547 | .name = "snd-soc-p1022", | ||
548 | .owner = THIS_MODULE, | ||
549 | }, | ||
550 | }; | ||
551 | |||
552 | /** | ||
553 | * p1022_ds_init: machine driver initialization. | ||
554 | * | ||
555 | * This function is called when this module is loaded. | ||
556 | */ | ||
557 | static int __init p1022_ds_init(void) | ||
558 | { | ||
559 | struct device_node *guts_np; | ||
560 | struct resource res; | ||
561 | |||
562 | pr_info("Freescale P1022 DS ALSA SoC machine driver\n"); | ||
563 | |||
564 | /* Get the physical address of the global utilities registers */ | ||
565 | guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts"); | ||
566 | if (of_address_to_resource(guts_np, 0, &res)) { | ||
567 | pr_err("p1022-ds: missing/invalid global utilities node\n"); | ||
568 | return -EINVAL; | ||
569 | } | ||
570 | guts_phys = res.start; | ||
571 | of_node_put(guts_np); | ||
572 | |||
573 | return platform_driver_register(&p1022_ds_driver); | ||
574 | } | ||
575 | |||
576 | /** | ||
577 | * p1022_ds_exit: machine driver exit | ||
578 | * | ||
579 | * This function is called when this driver is unloaded. | ||
580 | */ | ||
581 | static void __exit p1022_ds_exit(void) | ||
582 | { | ||
583 | platform_driver_unregister(&p1022_ds_driver); | ||
584 | } | ||
585 | |||
586 | module_init(p1022_ds_init); | ||
587 | module_exit(p1022_ds_exit); | ||
588 | |||
589 | MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); | ||
590 | MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver"); | ||
591 | MODULE_LICENSE("GPL v2"); | ||