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 |
15 | |
16 | #include <linux/module.h> |
17 | #include <linux/fsl/guts.h> |
18 | #include <linux/interrupt.h> |
19 | #include <linux/of.h> |
20 | #include <linux/of_address.h> |
21 | #include <linux/slab.h> |
22 | #include <sound/soc.h> |
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 | * @card: ASoC card instance |
85 | * |
86 | * This function is used to initialize the board-specific hardware. |
87 | * |
88 | * Here we program the DMACR and PMUXCR registers. |
89 | * |
90 | * Returns: %0 on success or negative errno value on error |
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(offset: guts_phys, size: 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, co: mdata->dma_id[0], ch: mdata->dma_channel_id[0], |
114 | CCSR_GUTS_DMUXCR_SSI); |
115 | |
116 | guts_set_dmuxcr(guts, co: mdata->dma_id[1], ch: mdata->dma_channel_id[1], |
117 | CCSR_GUTS_DMUXCR_SSI); |
118 | |
119 | iounmap(addr: guts); |
120 | |
121 | return 0; |
122 | } |
123 | |
124 | /** |
125 | * p1022_rdk_startup - program the board with various hardware parameters |
126 | * @substream: ASoC substream object |
127 | * |
128 | * This function takes board-specific information, like clock frequencies |
129 | * and serial data formats, and passes that information to the codec and |
130 | * transport drivers. |
131 | * |
132 | * Returns: %0 on success or negative errno value on error |
133 | */ |
134 | static int p1022_rdk_startup(struct snd_pcm_substream *substream) |
135 | { |
136 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
137 | struct machine_data *mdata = |
138 | container_of(rtd->card, struct machine_data, card); |
139 | struct device *dev = rtd->card->dev; |
140 | int ret = 0; |
141 | |
142 | /* Tell the codec driver what the serial protocol is. */ |
143 | ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 0), fmt: mdata->dai_format); |
144 | if (ret < 0) { |
145 | dev_err(dev, "could not set codec driver audio format (ret=%i)\n" , |
146 | ret); |
147 | return ret; |
148 | } |
149 | |
150 | ret = snd_soc_dai_set_pll(snd_soc_rtd_to_codec(rtd, 0), pll_id: 0, source: 0, freq_in: mdata->clk_frequency, |
151 | freq_out: mdata->clk_frequency); |
152 | if (ret < 0) { |
153 | dev_err(dev, "could not set codec PLL frequency (ret=%i)\n" , |
154 | ret); |
155 | return ret; |
156 | } |
157 | |
158 | return 0; |
159 | } |
160 | |
161 | /** |
162 | * p1022_rdk_machine_remove - Remove the sound device |
163 | * @card: ASoC card instance |
164 | * |
165 | * This function is called to remove the sound device for one SSI. We |
166 | * de-program the DMACR and PMUXCR register. |
167 | * |
168 | * Returns: %0 on success or negative errno value on error |
169 | */ |
170 | static int p1022_rdk_machine_remove(struct snd_soc_card *card) |
171 | { |
172 | struct machine_data *mdata = |
173 | container_of(card, struct machine_data, card); |
174 | struct ccsr_guts __iomem *guts; |
175 | |
176 | guts = ioremap(offset: guts_phys, size: sizeof(struct ccsr_guts)); |
177 | if (!guts) { |
178 | dev_err(card->dev, "could not map global utilities\n" ); |
179 | return -ENOMEM; |
180 | } |
181 | |
182 | /* Restore the signal routing */ |
183 | clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK); |
184 | clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK); |
185 | guts_set_dmuxcr(guts, co: mdata->dma_id[0], ch: mdata->dma_channel_id[0], device: 0); |
186 | guts_set_dmuxcr(guts, co: mdata->dma_id[1], ch: mdata->dma_channel_id[1], device: 0); |
187 | |
188 | iounmap(addr: guts); |
189 | |
190 | return 0; |
191 | } |
192 | |
193 | /* |
194 | * p1022_rdk_ops: ASoC machine driver operations |
195 | */ |
196 | static const struct snd_soc_ops p1022_rdk_ops = { |
197 | .startup = p1022_rdk_startup, |
198 | }; |
199 | |
200 | /** |
201 | * p1022_rdk_probe - platform probe function for the machine driver |
202 | * @pdev: platform device pointer |
203 | * |
204 | * Although this is a machine driver, the SSI node is the "master" node with |
205 | * respect to audio hardware connections. Therefore, we create a new ASoC |
206 | * device for each new SSI node that has a codec attached. |
207 | * |
208 | * Returns: %0 on success or negative errno value on error |
209 | */ |
210 | static int p1022_rdk_probe(struct platform_device *pdev) |
211 | { |
212 | struct device *dev = pdev->dev.parent; |
213 | /* ssi_pdev is the platform device for the SSI node that probed us */ |
214 | struct platform_device *ssi_pdev = to_platform_device(dev); |
215 | struct device_node *np = ssi_pdev->dev.of_node; |
216 | struct device_node *codec_np = NULL; |
217 | struct machine_data *mdata; |
218 | struct snd_soc_dai_link_component *comp; |
219 | const u32 *iprop; |
220 | int ret; |
221 | |
222 | /* Find the codec node for this SSI. */ |
223 | codec_np = of_parse_phandle(np, phandle_name: "codec-handle" , index: 0); |
224 | if (!codec_np) { |
225 | dev_err(dev, "could not find codec node\n" ); |
226 | return -EINVAL; |
227 | } |
228 | |
229 | mdata = kzalloc(size: sizeof(struct machine_data), GFP_KERNEL); |
230 | if (!mdata) { |
231 | ret = -ENOMEM; |
232 | goto error_put; |
233 | } |
234 | |
235 | comp = devm_kzalloc(dev: &pdev->dev, size: 6 * sizeof(*comp), GFP_KERNEL); |
236 | if (!comp) { |
237 | ret = -ENOMEM; |
238 | goto error_put; |
239 | } |
240 | |
241 | mdata->dai[0].cpus = &comp[0]; |
242 | mdata->dai[0].codecs = &comp[1]; |
243 | mdata->dai[0].platforms = &comp[2]; |
244 | |
245 | mdata->dai[0].num_cpus = 1; |
246 | mdata->dai[0].num_codecs = 1; |
247 | mdata->dai[0].num_platforms = 1; |
248 | |
249 | mdata->dai[1].cpus = &comp[3]; |
250 | mdata->dai[1].codecs = &comp[4]; |
251 | mdata->dai[1].platforms = &comp[5]; |
252 | |
253 | mdata->dai[1].num_cpus = 1; |
254 | mdata->dai[1].num_codecs = 1; |
255 | mdata->dai[1].num_platforms = 1; |
256 | |
257 | mdata->dai[0].cpus->dai_name = dev_name(dev: &ssi_pdev->dev); |
258 | mdata->dai[0].ops = &p1022_rdk_ops; |
259 | |
260 | /* ASoC core can match codec with device node */ |
261 | mdata->dai[0].codecs->of_node = codec_np; |
262 | |
263 | /* |
264 | * We register two DAIs per SSI, one for playback and the other for |
265 | * capture. We support codecs that have separate DAIs for both playback |
266 | * and capture. |
267 | */ |
268 | memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link)); |
269 | |
270 | /* The DAI names from the codec (snd_soc_dai_driver.name) */ |
271 | mdata->dai[0].codecs->dai_name = "wm8960-hifi" ; |
272 | mdata->dai[1].codecs->dai_name = mdata->dai[0].codecs->dai_name; |
273 | |
274 | /* |
275 | * Configure the SSI for I2S slave mode. Older device trees have |
276 | * an fsl,mode property, but we ignore that since there's really |
277 | * only one way to configure the SSI. |
278 | */ |
279 | mdata->dai_format = SND_SOC_DAIFMT_NB_NF | |
280 | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBP_CFP; |
281 | mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; |
282 | mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; |
283 | |
284 | /* |
285 | * In i2s-slave mode, the codec has its own clock source, so we |
286 | * need to get the frequency from the device tree and pass it to |
287 | * the codec driver. |
288 | */ |
289 | iprop = of_get_property(node: codec_np, name: "clock-frequency" , NULL); |
290 | if (!iprop || !*iprop) { |
291 | dev_err(&pdev->dev, "codec bus-frequency property is missing or invalid\n" ); |
292 | ret = -EINVAL; |
293 | goto error; |
294 | } |
295 | mdata->clk_frequency = be32_to_cpup(p: iprop); |
296 | |
297 | if (!mdata->clk_frequency) { |
298 | dev_err(&pdev->dev, "unknown clock frequency\n" ); |
299 | ret = -EINVAL; |
300 | goto error; |
301 | } |
302 | |
303 | /* Find the playback DMA channel to use. */ |
304 | mdata->dai[0].platforms->name = mdata->platform_name[0]; |
305 | ret = fsl_asoc_get_dma_channel(ssi_np: np, name: "fsl,playback-dma" , dai: &mdata->dai[0], |
306 | dma_channel_id: &mdata->dma_channel_id[0], |
307 | dma_id: &mdata->dma_id[0]); |
308 | if (ret) { |
309 | dev_err(&pdev->dev, "missing/invalid playback DMA phandle (ret=%i)\n" , |
310 | ret); |
311 | goto error; |
312 | } |
313 | |
314 | /* Find the capture DMA channel to use. */ |
315 | mdata->dai[1].platforms->name = mdata->platform_name[1]; |
316 | ret = fsl_asoc_get_dma_channel(ssi_np: np, name: "fsl,capture-dma" , dai: &mdata->dai[1], |
317 | dma_channel_id: &mdata->dma_channel_id[1], |
318 | dma_id: &mdata->dma_id[1]); |
319 | if (ret) { |
320 | dev_err(&pdev->dev, "missing/invalid capture DMA phandle (ret=%i)\n" , |
321 | ret); |
322 | goto error; |
323 | } |
324 | |
325 | /* Initialize our DAI data structure. */ |
326 | mdata->dai[0].stream_name = "playback" ; |
327 | mdata->dai[1].stream_name = "capture" ; |
328 | mdata->dai[0].name = mdata->dai[0].stream_name; |
329 | mdata->dai[1].name = mdata->dai[1].stream_name; |
330 | |
331 | mdata->card.probe = p1022_rdk_machine_probe; |
332 | mdata->card.remove = p1022_rdk_machine_remove; |
333 | mdata->card.name = pdev->name; /* The platform driver name */ |
334 | mdata->card.owner = THIS_MODULE; |
335 | mdata->card.dev = &pdev->dev; |
336 | mdata->card.num_links = 2; |
337 | mdata->card.dai_link = mdata->dai; |
338 | |
339 | /* Register with ASoC */ |
340 | ret = snd_soc_register_card(card: &mdata->card); |
341 | if (ret) { |
342 | dev_err(&pdev->dev, "could not register card (ret=%i)\n" , ret); |
343 | goto error; |
344 | } |
345 | |
346 | return 0; |
347 | |
348 | error: |
349 | kfree(objp: mdata); |
350 | error_put: |
351 | of_node_put(node: codec_np); |
352 | return ret; |
353 | } |
354 | |
355 | /** |
356 | * p1022_rdk_remove - remove the platform device |
357 | * @pdev: platform device pointer |
358 | * |
359 | * This function is called when the platform device is removed. |
360 | */ |
361 | static void p1022_rdk_remove(struct platform_device *pdev) |
362 | { |
363 | struct snd_soc_card *card = platform_get_drvdata(pdev); |
364 | struct machine_data *mdata = |
365 | container_of(card, struct machine_data, card); |
366 | |
367 | snd_soc_unregister_card(card); |
368 | kfree(objp: mdata); |
369 | } |
370 | |
371 | static struct platform_driver p1022_rdk_driver = { |
372 | .probe = p1022_rdk_probe, |
373 | .remove_new = p1022_rdk_remove, |
374 | .driver = { |
375 | /* |
376 | * The name must match 'compatible' property in the device tree, |
377 | * in lowercase letters. |
378 | */ |
379 | .name = "snd-soc-p1022rdk" , |
380 | }, |
381 | }; |
382 | |
383 | /** |
384 | * p1022_rdk_init - machine driver initialization. |
385 | * |
386 | * This function is called when this module is loaded. |
387 | * |
388 | * Returns: %0 on success or negative errno value on error |
389 | */ |
390 | static int __init p1022_rdk_init(void) |
391 | { |
392 | struct device_node *guts_np; |
393 | struct resource res; |
394 | |
395 | /* Get the physical address of the global utilities registers */ |
396 | guts_np = of_find_compatible_node(NULL, NULL, compat: "fsl,p1022-guts" ); |
397 | if (of_address_to_resource(dev: guts_np, index: 0, r: &res)) { |
398 | pr_err("snd-soc-p1022rdk: missing/invalid global utils node\n" ); |
399 | of_node_put(node: guts_np); |
400 | return -EINVAL; |
401 | } |
402 | guts_phys = res.start; |
403 | of_node_put(node: guts_np); |
404 | |
405 | return platform_driver_register(&p1022_rdk_driver); |
406 | } |
407 | |
408 | /** |
409 | * p1022_rdk_exit - machine driver exit |
410 | * |
411 | * This function is called when this driver is unloaded. |
412 | */ |
413 | static void __exit p1022_rdk_exit(void) |
414 | { |
415 | platform_driver_unregister(&p1022_rdk_driver); |
416 | } |
417 | |
418 | late_initcall(p1022_rdk_init); |
419 | module_exit(p1022_rdk_exit); |
420 | |
421 | MODULE_AUTHOR("Timur Tabi <timur@freescale.com>" ); |
422 | MODULE_DESCRIPTION("Freescale / iVeia P1022 RDK ALSA SoC machine driver" ); |
423 | MODULE_LICENSE("GPL v2" ); |
424 | |