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