Commit | Line | Data |
---|---|---|
27ef3744 TT |
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); | |
880b8ffd JL |
349 | if (!mdata) { |
350 | ret = -ENOMEM; | |
351 | goto error_put; | |
352 | } | |
27ef3744 TT |
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 | } | |
39a54555 | 501 | dev_set_drvdata(&pdev->dev, sound_device); |
27ef3744 TT |
502 | |
503 | of_node_put(codec_np); | |
504 | ||
505 | return 0; | |
506 | ||
507 | error: | |
27ef3744 TT |
508 | if (sound_device) |
509 | platform_device_unregister(sound_device); | |
510 | ||
511 | kfree(mdata); | |
880b8ffd JL |
512 | error_put: |
513 | of_node_put(codec_np); | |
27ef3744 TT |
514 | return ret; |
515 | } | |
516 | ||
517 | /** | |
518 | * p1022_ds_remove: remove the platform device | |
519 | * | |
520 | * This function is called when the platform device is removed. | |
521 | */ | |
522 | static int __devexit p1022_ds_remove(struct platform_device *pdev) | |
523 | { | |
524 | struct platform_device *sound_device = dev_get_drvdata(&pdev->dev); | |
525 | struct snd_soc_card *card = platform_get_drvdata(sound_device); | |
526 | struct machine_data *mdata = | |
527 | container_of(card, struct machine_data, card); | |
528 | ||
529 | platform_device_unregister(sound_device); | |
530 | ||
531 | kfree(mdata); | |
532 | sound_device->dev.platform_data = NULL; | |
533 | ||
534 | dev_set_drvdata(&pdev->dev, NULL); | |
535 | ||
536 | return 0; | |
537 | } | |
538 | ||
539 | static struct platform_driver p1022_ds_driver = { | |
540 | .probe = p1022_ds_probe, | |
541 | .remove = __devexit_p(p1022_ds_remove), | |
542 | .driver = { | |
543 | /* The name must match the 'model' property in the device tree, | |
544 | * in lowercase letters, but only the part after that last | |
545 | * comma. This is because some model properties have a "fsl," | |
546 | * prefix. | |
547 | */ | |
548 | .name = "snd-soc-p1022", | |
549 | .owner = THIS_MODULE, | |
550 | }, | |
551 | }; | |
552 | ||
553 | /** | |
554 | * p1022_ds_init: machine driver initialization. | |
555 | * | |
556 | * This function is called when this module is loaded. | |
557 | */ | |
558 | static int __init p1022_ds_init(void) | |
559 | { | |
560 | struct device_node *guts_np; | |
561 | struct resource res; | |
562 | ||
563 | pr_info("Freescale P1022 DS ALSA SoC machine driver\n"); | |
564 | ||
565 | /* Get the physical address of the global utilities registers */ | |
566 | guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts"); | |
567 | if (of_address_to_resource(guts_np, 0, &res)) { | |
568 | pr_err("p1022-ds: missing/invalid global utilities node\n"); | |
569 | return -EINVAL; | |
570 | } | |
571 | guts_phys = res.start; | |
572 | of_node_put(guts_np); | |
573 | ||
574 | return platform_driver_register(&p1022_ds_driver); | |
575 | } | |
576 | ||
577 | /** | |
578 | * p1022_ds_exit: machine driver exit | |
579 | * | |
580 | * This function is called when this driver is unloaded. | |
581 | */ | |
582 | static void __exit p1022_ds_exit(void) | |
583 | { | |
584 | platform_driver_unregister(&p1022_ds_driver); | |
585 | } | |
586 | ||
587 | module_init(p1022_ds_init); | |
588 | module_exit(p1022_ds_exit); | |
589 | ||
590 | MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); | |
591 | MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver"); | |
592 | MODULE_LICENSE("GPL v2"); |