Commit | Line | Data |
---|---|---|
96a0af6d TT |
1 | /** |
2 | * Freescale P1022RDK ALSA SoC Machine driver | |
3 | * | |
4 | * Author: Timur Tabi <timur@freescale.com> | |
5 | * | |
6 | * Copyright 2012 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 | * Note: in order for audio to work correctly, the output controls need | |
13 | * to be enabled, because they control the clock. So for playback, for | |
14 | * example: | |
15 | * | |
16 | * amixer sset 'Left Output Mixer PCM' on | |
17 | * amixer sset 'Right Output Mixer PCM' on | |
18 | */ | |
19 | ||
20 | #include <linux/module.h> | |
21 | #include <linux/interrupt.h> | |
22 | #include <linux/of_device.h> | |
23 | #include <linux/slab.h> | |
24 | #include <sound/soc.h> | |
25 | #include <asm/fsl_guts.h> | |
26 | ||
27 | #include "fsl_dma.h" | |
28 | #include "fsl_ssi.h" | |
29 | #include "fsl_utils.h" | |
30 | ||
31 | /* P1022-specific PMUXCR and DMUXCR bit definitions */ | |
32 | ||
33 | #define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK 0x0001c000 | |
34 | #define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI 0x00010000 | |
35 | #define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI 0x00018000 | |
36 | ||
37 | #define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK 0x00000c00 | |
38 | #define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI 0x00000000 | |
39 | ||
40 | #define CCSR_GUTS_DMUXCR_PAD 1 /* DMA controller/channel set to pad */ | |
41 | #define CCSR_GUTS_DMUXCR_SSI 2 /* DMA controller/channel set to SSI */ | |
42 | ||
43 | /* | |
44 | * Set the DMACR register in the GUTS | |
45 | * | |
46 | * The DMACR register determines the source of initiated transfers for each | |
47 | * channel on each DMA controller. Rather than have a bunch of repetitive | |
48 | * macros for the bit patterns, we just have a function that calculates | |
49 | * them. | |
50 | * | |
51 | * guts: Pointer to GUTS structure | |
52 | * co: The DMA controller (0 or 1) | |
53 | * ch: The channel on the DMA controller (0, 1, 2, or 3) | |
54 | * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx) | |
55 | */ | |
56 | static inline void guts_set_dmuxcr(struct ccsr_guts __iomem *guts, | |
57 | unsigned int co, unsigned int ch, unsigned int device) | |
58 | { | |
59 | unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch)); | |
60 | ||
61 | clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift); | |
62 | } | |
63 | ||
64 | /* There's only one global utilities register */ | |
65 | static phys_addr_t guts_phys; | |
66 | ||
67 | /** | |
68 | * machine_data: machine-specific ASoC device data | |
69 | * | |
70 | * This structure contains data for a single sound platform device on an | |
71 | * P1022 RDK. Some of the data is taken from the device tree. | |
72 | */ | |
73 | struct machine_data { | |
74 | struct snd_soc_dai_link dai[2]; | |
75 | struct snd_soc_card card; | |
76 | unsigned int dai_format; | |
77 | unsigned int codec_clk_direction; | |
78 | unsigned int cpu_clk_direction; | |
79 | unsigned int clk_frequency; | |
80 | unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ | |
81 | unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ | |
82 | char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ | |
83 | }; | |
84 | ||
85 | /** | |
86 | * p1022_rdk_machine_probe: initialize the board | |
87 | * | |
88 | * This function is used to initialize the board-specific hardware. | |
89 | * | |
90 | * Here we program the DMACR and PMUXCR registers. | |
91 | */ | |
92 | static int p1022_rdk_machine_probe(struct snd_soc_card *card) | |
93 | { | |
94 | struct machine_data *mdata = | |
95 | container_of(card, struct machine_data, card); | |
96 | struct ccsr_guts __iomem *guts; | |
97 | ||
98 | guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); | |
99 | if (!guts) { | |
100 | dev_err(card->dev, "could not map global utilities\n"); | |
101 | return -ENOMEM; | |
102 | } | |
103 | ||
104 | /* Enable SSI Tx signal */ | |
105 | clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK, | |
106 | CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI); | |
107 | ||
108 | /* Enable SSI Rx signal */ | |
109 | clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK, | |
110 | CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI); | |
111 | ||
112 | /* Enable DMA Channel for SSI */ | |
113 | guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], | |
114 | CCSR_GUTS_DMUXCR_SSI); | |
115 | ||
116 | guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], | |
117 | CCSR_GUTS_DMUXCR_SSI); | |
118 | ||
119 | iounmap(guts); | |
120 | ||
121 | return 0; | |
122 | } | |
123 | ||
124 | /** | |
125 | * p1022_rdk_startup: program the board with various hardware parameters | |
126 | * | |
127 | * This function takes board-specific information, like clock frequencies | |
128 | * and serial data formats, and passes that information to the codec and | |
129 | * transport drivers. | |
130 | */ | |
131 | static int p1022_rdk_startup(struct snd_pcm_substream *substream) | |
132 | { | |
133 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
134 | struct machine_data *mdata = | |
135 | container_of(rtd->card, struct machine_data, card); | |
136 | struct device *dev = rtd->card->dev; | |
137 | int ret = 0; | |
138 | ||
139 | /* Tell the codec driver what the serial protocol is. */ | |
140 | ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format); | |
141 | if (ret < 0) { | |
142 | dev_err(dev, "could not set codec driver audio format (ret=%i)\n", | |
143 | ret); | |
144 | return ret; | |
145 | } | |
146 | ||
147 | ret = snd_soc_dai_set_pll(rtd->codec_dai, 0, 0, mdata->clk_frequency, | |
148 | mdata->clk_frequency); | |
149 | if (ret < 0) { | |
150 | dev_err(dev, "could not set codec PLL frequency (ret=%i)\n", | |
151 | ret); | |
152 | return ret; | |
153 | } | |
154 | ||
155 | return 0; | |
156 | } | |
157 | ||
158 | /** | |
159 | * p1022_rdk_machine_remove: Remove the sound device | |
160 | * | |
161 | * This function is called to remove the sound device for one SSI. We | |
162 | * de-program the DMACR and PMUXCR register. | |
163 | */ | |
164 | static int p1022_rdk_machine_remove(struct snd_soc_card *card) | |
165 | { | |
166 | struct machine_data *mdata = | |
167 | container_of(card, struct machine_data, card); | |
168 | struct ccsr_guts __iomem *guts; | |
169 | ||
170 | guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); | |
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_rdk_ops: ASoC machine driver operations | |
189 | */ | |
190 | static struct snd_soc_ops p1022_rdk_ops = { | |
191 | .startup = p1022_rdk_startup, | |
192 | }; | |
193 | ||
194 | /** | |
195 | * p1022_rdk_probe: platform probe function for the machine driver | |
196 | * | |
197 | * Although this is a machine driver, the SSI node is the "master" node with | |
198 | * respect to audio hardware connections. Therefore, we create a new ASoC | |
199 | * device for each new SSI node that has a codec attached. | |
200 | */ | |
201 | static int p1022_rdk_probe(struct platform_device *pdev) | |
202 | { | |
203 | struct device *dev = pdev->dev.parent; | |
204 | /* ssi_pdev is the platform device for the SSI node that probed us */ | |
205 | struct platform_device *ssi_pdev = | |
206 | container_of(dev, struct platform_device, dev); | |
207 | struct device_node *np = ssi_pdev->dev.of_node; | |
208 | struct device_node *codec_np = NULL; | |
209 | struct machine_data *mdata; | |
210 | const u32 *iprop; | |
211 | int ret; | |
212 | ||
213 | /* Find the codec node for this SSI. */ | |
214 | codec_np = of_parse_phandle(np, "codec-handle", 0); | |
215 | if (!codec_np) { | |
216 | dev_err(dev, "could not find codec node\n"); | |
217 | return -EINVAL; | |
218 | } | |
219 | ||
220 | mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL); | |
221 | if (!mdata) { | |
222 | ret = -ENOMEM; | |
223 | goto error_put; | |
224 | } | |
225 | ||
226 | mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); | |
227 | mdata->dai[0].ops = &p1022_rdk_ops; | |
228 | ||
229 | /* ASoC core can match codec with device node */ | |
230 | mdata->dai[0].codec_of_node = codec_np; | |
231 | ||
232 | /* | |
233 | * We register two DAIs per SSI, one for playback and the other for | |
234 | * capture. We support codecs that have separate DAIs for both playback | |
235 | * and capture. | |
236 | */ | |
237 | memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link)); | |
238 | ||
239 | /* The DAI names from the codec (snd_soc_dai_driver.name) */ | |
240 | mdata->dai[0].codec_dai_name = "wm8960-hifi"; | |
241 | mdata->dai[1].codec_dai_name = mdata->dai[0].codec_dai_name; | |
242 | ||
243 | /* | |
244 | * Configure the SSI for I2S slave mode. Older device trees have | |
245 | * an fsl,mode property, but we ignore that since there's really | |
246 | * only one way to configure the SSI. | |
247 | */ | |
248 | mdata->dai_format = SND_SOC_DAIFMT_NB_NF | | |
249 | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; | |
250 | mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; | |
251 | mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; | |
252 | ||
253 | /* | |
254 | * In i2s-slave mode, the codec has its own clock source, so we | |
255 | * need to get the frequency from the device tree and pass it to | |
256 | * the codec driver. | |
257 | */ | |
258 | iprop = of_get_property(codec_np, "clock-frequency", NULL); | |
259 | if (!iprop || !*iprop) { | |
260 | dev_err(&pdev->dev, "codec bus-frequency property is missing or invalid\n"); | |
261 | ret = -EINVAL; | |
262 | goto error; | |
263 | } | |
264 | mdata->clk_frequency = be32_to_cpup(iprop); | |
265 | ||
266 | if (!mdata->clk_frequency) { | |
267 | dev_err(&pdev->dev, "unknown clock frequency\n"); | |
268 | ret = -EINVAL; | |
269 | goto error; | |
270 | } | |
271 | ||
272 | /* Find the playback DMA channel to use. */ | |
273 | mdata->dai[0].platform_name = mdata->platform_name[0]; | |
274 | ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0], | |
275 | &mdata->dma_channel_id[0], | |
276 | &mdata->dma_id[0]); | |
277 | if (ret) { | |
278 | dev_err(&pdev->dev, "missing/invalid playback DMA phandle (ret=%i)\n", | |
279 | ret); | |
280 | goto error; | |
281 | } | |
282 | ||
283 | /* Find the capture DMA channel to use. */ | |
284 | mdata->dai[1].platform_name = mdata->platform_name[1]; | |
285 | ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1], | |
286 | &mdata->dma_channel_id[1], | |
287 | &mdata->dma_id[1]); | |
288 | if (ret) { | |
289 | dev_err(&pdev->dev, "missing/invalid capture DMA phandle (ret=%i)\n", | |
290 | ret); | |
291 | goto error; | |
292 | } | |
293 | ||
294 | /* Initialize our DAI data structure. */ | |
295 | mdata->dai[0].stream_name = "playback"; | |
296 | mdata->dai[1].stream_name = "capture"; | |
297 | mdata->dai[0].name = mdata->dai[0].stream_name; | |
298 | mdata->dai[1].name = mdata->dai[1].stream_name; | |
299 | ||
300 | mdata->card.probe = p1022_rdk_machine_probe; | |
301 | mdata->card.remove = p1022_rdk_machine_remove; | |
302 | mdata->card.name = pdev->name; /* The platform driver name */ | |
303 | mdata->card.owner = THIS_MODULE; | |
304 | mdata->card.dev = &pdev->dev; | |
305 | mdata->card.num_links = 2; | |
306 | mdata->card.dai_link = mdata->dai; | |
307 | ||
308 | /* Register with ASoC */ | |
309 | ret = snd_soc_register_card(&mdata->card); | |
310 | if (ret) { | |
311 | dev_err(&pdev->dev, "could not register card (ret=%i)\n", ret); | |
312 | goto error; | |
313 | } | |
314 | ||
315 | return 0; | |
316 | ||
317 | error: | |
318 | kfree(mdata); | |
319 | error_put: | |
320 | of_node_put(codec_np); | |
321 | return ret; | |
322 | } | |
323 | ||
324 | /** | |
325 | * p1022_rdk_remove: remove the platform device | |
326 | * | |
327 | * This function is called when the platform device is removed. | |
328 | */ | |
a0a3d518 | 329 | static int p1022_rdk_remove(struct platform_device *pdev) |
96a0af6d TT |
330 | { |
331 | struct snd_soc_card *card = platform_get_drvdata(pdev); | |
332 | struct machine_data *mdata = | |
333 | container_of(card, struct machine_data, card); | |
334 | ||
335 | snd_soc_unregister_card(card); | |
336 | kfree(mdata); | |
337 | ||
338 | return 0; | |
339 | } | |
340 | ||
341 | static struct platform_driver p1022_rdk_driver = { | |
342 | .probe = p1022_rdk_probe, | |
a0a3d518 | 343 | .remove = p1022_rdk_remove, |
96a0af6d TT |
344 | .driver = { |
345 | /* | |
346 | * The name must match 'compatible' property in the device tree, | |
347 | * in lowercase letters. | |
348 | */ | |
349 | .name = "snd-soc-p1022rdk", | |
350 | .owner = THIS_MODULE, | |
351 | }, | |
352 | }; | |
353 | ||
354 | /** | |
355 | * p1022_rdk_init: machine driver initialization. | |
356 | * | |
357 | * This function is called when this module is loaded. | |
358 | */ | |
359 | static int __init p1022_rdk_init(void) | |
360 | { | |
361 | struct device_node *guts_np; | |
362 | struct resource res; | |
363 | ||
364 | /* Get the physical address of the global utilities registers */ | |
365 | guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts"); | |
366 | if (of_address_to_resource(guts_np, 0, &res)) { | |
367 | pr_err("snd-soc-p1022rdk: missing/invalid global utils node\n"); | |
368 | of_node_put(guts_np); | |
369 | return -EINVAL; | |
370 | } | |
371 | guts_phys = res.start; | |
372 | of_node_put(guts_np); | |
373 | ||
374 | return platform_driver_register(&p1022_rdk_driver); | |
375 | } | |
376 | ||
377 | /** | |
378 | * p1022_rdk_exit: machine driver exit | |
379 | * | |
380 | * This function is called when this driver is unloaded. | |
381 | */ | |
382 | static void __exit p1022_rdk_exit(void) | |
383 | { | |
384 | platform_driver_unregister(&p1022_rdk_driver); | |
385 | } | |
386 | ||
387 | late_initcall(p1022_rdk_init); | |
388 | module_exit(p1022_rdk_exit); | |
389 | ||
390 | MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); | |
391 | MODULE_DESCRIPTION("Freescale / iVeia P1022 RDK ALSA SoC machine driver"); | |
392 | MODULE_LICENSE("GPL v2"); |