1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * ALSA SoC using the QUICC Multichannel Controller (QMC) |
4 | * |
5 | * Copyright 2022 CS GROUP France |
6 | * |
7 | * Author: Herve Codina <herve.codina@bootlin.com> |
8 | */ |
9 | |
10 | #include <linux/dma-mapping.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/of_platform.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/slab.h> |
16 | #include <soc/fsl/qe/qmc.h> |
17 | #include <sound/pcm_params.h> |
18 | #include <sound/soc.h> |
19 | |
20 | struct qmc_dai { |
21 | char *name; |
22 | int id; |
23 | struct device *dev; |
24 | struct qmc_chan *qmc_chan; |
25 | unsigned int nb_tx_ts; |
26 | unsigned int nb_rx_ts; |
27 | }; |
28 | |
29 | struct qmc_audio { |
30 | struct device *dev; |
31 | unsigned int num_dais; |
32 | struct qmc_dai *dais; |
33 | struct snd_soc_dai_driver *dai_drivers; |
34 | }; |
35 | |
36 | struct qmc_dai_prtd { |
37 | struct qmc_dai *qmc_dai; |
38 | dma_addr_t dma_buffer_start; |
39 | dma_addr_t period_ptr_submitted; |
40 | dma_addr_t period_ptr_ended; |
41 | dma_addr_t dma_buffer_end; |
42 | size_t period_size; |
43 | struct snd_pcm_substream *substream; |
44 | }; |
45 | |
46 | static int qmc_audio_pcm_construct(struct snd_soc_component *component, |
47 | struct snd_soc_pcm_runtime *rtd) |
48 | { |
49 | struct snd_card *card = rtd->card->snd_card; |
50 | int ret; |
51 | |
52 | ret = dma_coerce_mask_and_coherent(dev: card->dev, DMA_BIT_MASK(32)); |
53 | if (ret) |
54 | return ret; |
55 | |
56 | snd_pcm_set_managed_buffer_all(pcm: rtd->pcm, SNDRV_DMA_TYPE_DEV, data: card->dev, |
57 | size: 64*1024, max: 64*1024); |
58 | return 0; |
59 | } |
60 | |
61 | static int qmc_audio_pcm_hw_params(struct snd_soc_component *component, |
62 | struct snd_pcm_substream *substream, |
63 | struct snd_pcm_hw_params *params) |
64 | { |
65 | struct snd_pcm_runtime *runtime = substream->runtime; |
66 | struct qmc_dai_prtd *prtd = substream->runtime->private_data; |
67 | |
68 | prtd->dma_buffer_start = runtime->dma_addr; |
69 | prtd->dma_buffer_end = runtime->dma_addr + params_buffer_bytes(p: params); |
70 | prtd->period_size = params_period_bytes(p: params); |
71 | prtd->period_ptr_submitted = prtd->dma_buffer_start; |
72 | prtd->period_ptr_ended = prtd->dma_buffer_start; |
73 | prtd->substream = substream; |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static void qmc_audio_pcm_write_complete(void *context) |
79 | { |
80 | struct qmc_dai_prtd *prtd = context; |
81 | int ret; |
82 | |
83 | prtd->period_ptr_ended += prtd->period_size; |
84 | if (prtd->period_ptr_ended >= prtd->dma_buffer_end) |
85 | prtd->period_ptr_ended = prtd->dma_buffer_start; |
86 | |
87 | prtd->period_ptr_submitted += prtd->period_size; |
88 | if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) |
89 | prtd->period_ptr_submitted = prtd->dma_buffer_start; |
90 | |
91 | ret = qmc_chan_write_submit(chan: prtd->qmc_dai->qmc_chan, |
92 | addr: prtd->period_ptr_submitted, length: prtd->period_size, |
93 | complete: qmc_audio_pcm_write_complete, context: prtd); |
94 | if (ret) { |
95 | dev_err(prtd->qmc_dai->dev, "write_submit failed %d\n" , |
96 | ret); |
97 | } |
98 | |
99 | snd_pcm_period_elapsed(substream: prtd->substream); |
100 | } |
101 | |
102 | static void qmc_audio_pcm_read_complete(void *context, size_t length, unsigned int flags) |
103 | { |
104 | struct qmc_dai_prtd *prtd = context; |
105 | int ret; |
106 | |
107 | if (length != prtd->period_size) { |
108 | dev_err(prtd->qmc_dai->dev, "read complete length = %zu, exp %zu\n" , |
109 | length, prtd->period_size); |
110 | } |
111 | |
112 | prtd->period_ptr_ended += prtd->period_size; |
113 | if (prtd->period_ptr_ended >= prtd->dma_buffer_end) |
114 | prtd->period_ptr_ended = prtd->dma_buffer_start; |
115 | |
116 | prtd->period_ptr_submitted += prtd->period_size; |
117 | if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) |
118 | prtd->period_ptr_submitted = prtd->dma_buffer_start; |
119 | |
120 | ret = qmc_chan_read_submit(chan: prtd->qmc_dai->qmc_chan, |
121 | addr: prtd->period_ptr_submitted, length: prtd->period_size, |
122 | complete: qmc_audio_pcm_read_complete, context: prtd); |
123 | if (ret) { |
124 | dev_err(prtd->qmc_dai->dev, "read_submit failed %d\n" , |
125 | ret); |
126 | } |
127 | |
128 | snd_pcm_period_elapsed(substream: prtd->substream); |
129 | } |
130 | |
131 | static int qmc_audio_pcm_trigger(struct snd_soc_component *component, |
132 | struct snd_pcm_substream *substream, int cmd) |
133 | { |
134 | struct qmc_dai_prtd *prtd = substream->runtime->private_data; |
135 | int ret; |
136 | |
137 | if (!prtd->qmc_dai) { |
138 | dev_err(component->dev, "qmc_dai is not set\n" ); |
139 | return -EINVAL; |
140 | } |
141 | |
142 | switch (cmd) { |
143 | case SNDRV_PCM_TRIGGER_START: |
144 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
145 | /* Submit first chunk ... */ |
146 | ret = qmc_chan_write_submit(chan: prtd->qmc_dai->qmc_chan, |
147 | addr: prtd->period_ptr_submitted, length: prtd->period_size, |
148 | complete: qmc_audio_pcm_write_complete, context: prtd); |
149 | if (ret) { |
150 | dev_err(component->dev, "write_submit failed %d\n" , |
151 | ret); |
152 | return ret; |
153 | } |
154 | |
155 | /* ... prepare next one ... */ |
156 | prtd->period_ptr_submitted += prtd->period_size; |
157 | if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) |
158 | prtd->period_ptr_submitted = prtd->dma_buffer_start; |
159 | |
160 | /* ... and send it */ |
161 | ret = qmc_chan_write_submit(chan: prtd->qmc_dai->qmc_chan, |
162 | addr: prtd->period_ptr_submitted, length: prtd->period_size, |
163 | complete: qmc_audio_pcm_write_complete, context: prtd); |
164 | if (ret) { |
165 | dev_err(component->dev, "write_submit failed %d\n" , |
166 | ret); |
167 | return ret; |
168 | } |
169 | } else { |
170 | /* Submit first chunk ... */ |
171 | ret = qmc_chan_read_submit(chan: prtd->qmc_dai->qmc_chan, |
172 | addr: prtd->period_ptr_submitted, length: prtd->period_size, |
173 | complete: qmc_audio_pcm_read_complete, context: prtd); |
174 | if (ret) { |
175 | dev_err(component->dev, "read_submit failed %d\n" , |
176 | ret); |
177 | return ret; |
178 | } |
179 | |
180 | /* ... prepare next one ... */ |
181 | prtd->period_ptr_submitted += prtd->period_size; |
182 | if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) |
183 | prtd->period_ptr_submitted = prtd->dma_buffer_start; |
184 | |
185 | /* ... and send it */ |
186 | ret = qmc_chan_read_submit(chan: prtd->qmc_dai->qmc_chan, |
187 | addr: prtd->period_ptr_submitted, length: prtd->period_size, |
188 | complete: qmc_audio_pcm_read_complete, context: prtd); |
189 | if (ret) { |
190 | dev_err(component->dev, "write_submit failed %d\n" , |
191 | ret); |
192 | return ret; |
193 | } |
194 | } |
195 | break; |
196 | |
197 | case SNDRV_PCM_TRIGGER_RESUME: |
198 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
199 | break; |
200 | |
201 | case SNDRV_PCM_TRIGGER_STOP: |
202 | case SNDRV_PCM_TRIGGER_SUSPEND: |
203 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
204 | break; |
205 | |
206 | default: |
207 | return -EINVAL; |
208 | } |
209 | |
210 | return 0; |
211 | } |
212 | |
213 | static snd_pcm_uframes_t qmc_audio_pcm_pointer(struct snd_soc_component *component, |
214 | struct snd_pcm_substream *substream) |
215 | { |
216 | struct qmc_dai_prtd *prtd = substream->runtime->private_data; |
217 | |
218 | return bytes_to_frames(runtime: substream->runtime, |
219 | size: prtd->period_ptr_ended - prtd->dma_buffer_start); |
220 | } |
221 | |
222 | static int qmc_audio_of_xlate_dai_name(struct snd_soc_component *component, |
223 | const struct of_phandle_args *args, |
224 | const char **dai_name) |
225 | { |
226 | struct qmc_audio *qmc_audio = dev_get_drvdata(dev: component->dev); |
227 | struct snd_soc_dai_driver *dai_driver; |
228 | int id = args->args[0]; |
229 | int i; |
230 | |
231 | for (i = 0; i < qmc_audio->num_dais; i++) { |
232 | dai_driver = qmc_audio->dai_drivers + i; |
233 | if (dai_driver->id == id) { |
234 | *dai_name = dai_driver->name; |
235 | return 0; |
236 | } |
237 | } |
238 | |
239 | return -EINVAL; |
240 | } |
241 | |
242 | static const struct snd_pcm_hardware qmc_audio_pcm_hardware = { |
243 | .info = SNDRV_PCM_INFO_MMAP | |
244 | SNDRV_PCM_INFO_MMAP_VALID | |
245 | SNDRV_PCM_INFO_INTERLEAVED | |
246 | SNDRV_PCM_INFO_PAUSE, |
247 | .period_bytes_min = 32, |
248 | .period_bytes_max = 64*1024, |
249 | .periods_min = 2, |
250 | .periods_max = 2*1024, |
251 | .buffer_bytes_max = 64*1024, |
252 | }; |
253 | |
254 | static int qmc_audio_pcm_open(struct snd_soc_component *component, |
255 | struct snd_pcm_substream *substream) |
256 | { |
257 | struct snd_pcm_runtime *runtime = substream->runtime; |
258 | struct qmc_dai_prtd *prtd; |
259 | int ret; |
260 | |
261 | snd_soc_set_runtime_hwparams(substream, hw: &qmc_audio_pcm_hardware); |
262 | |
263 | /* ensure that buffer size is a multiple of period size */ |
264 | ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); |
265 | if (ret < 0) |
266 | return ret; |
267 | |
268 | prtd = kzalloc(size: sizeof(*prtd), GFP_KERNEL); |
269 | if (prtd == NULL) |
270 | return -ENOMEM; |
271 | |
272 | runtime->private_data = prtd; |
273 | |
274 | return 0; |
275 | } |
276 | |
277 | static int qmc_audio_pcm_close(struct snd_soc_component *component, |
278 | struct snd_pcm_substream *substream) |
279 | { |
280 | struct qmc_dai_prtd *prtd = substream->runtime->private_data; |
281 | |
282 | kfree(objp: prtd); |
283 | return 0; |
284 | } |
285 | |
286 | static const struct snd_soc_component_driver qmc_audio_soc_platform = { |
287 | .open = qmc_audio_pcm_open, |
288 | .close = qmc_audio_pcm_close, |
289 | .hw_params = qmc_audio_pcm_hw_params, |
290 | .trigger = qmc_audio_pcm_trigger, |
291 | .pointer = qmc_audio_pcm_pointer, |
292 | .pcm_construct = qmc_audio_pcm_construct, |
293 | .of_xlate_dai_name = qmc_audio_of_xlate_dai_name, |
294 | }; |
295 | |
296 | static unsigned int qmc_dai_get_index(struct snd_soc_dai *dai) |
297 | { |
298 | struct qmc_audio *qmc_audio = snd_soc_dai_get_drvdata(dai); |
299 | |
300 | return dai->driver - qmc_audio->dai_drivers; |
301 | } |
302 | |
303 | static struct qmc_dai *qmc_dai_get_data(struct snd_soc_dai *dai) |
304 | { |
305 | struct qmc_audio *qmc_audio = snd_soc_dai_get_drvdata(dai); |
306 | unsigned int index; |
307 | |
308 | index = qmc_dai_get_index(dai); |
309 | if (index > qmc_audio->num_dais) |
310 | return NULL; |
311 | |
312 | return qmc_audio->dais + index; |
313 | } |
314 | |
315 | /* |
316 | * The constraints for format/channel is to match with the number of 8bit |
317 | * time-slots available. |
318 | */ |
319 | static int qmc_dai_hw_rule_channels_by_format(struct qmc_dai *qmc_dai, |
320 | struct snd_pcm_hw_params *params, |
321 | unsigned int nb_ts) |
322 | { |
323 | struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); |
324 | snd_pcm_format_t format = params_format(p: params); |
325 | struct snd_interval ch = {0}; |
326 | |
327 | switch (snd_pcm_format_physical_width(format)) { |
328 | case 8: |
329 | ch.max = nb_ts; |
330 | break; |
331 | case 16: |
332 | ch.max = nb_ts/2; |
333 | break; |
334 | case 32: |
335 | ch.max = nb_ts/4; |
336 | break; |
337 | case 64: |
338 | ch.max = nb_ts/8; |
339 | break; |
340 | default: |
341 | dev_err(qmc_dai->dev, "format physical width %u not supported\n" , |
342 | snd_pcm_format_physical_width(format)); |
343 | return -EINVAL; |
344 | } |
345 | |
346 | ch.min = ch.max ? 1 : 0; |
347 | |
348 | return snd_interval_refine(i: c, v: &ch); |
349 | } |
350 | |
351 | static int qmc_dai_hw_rule_playback_channels_by_format(struct snd_pcm_hw_params *params, |
352 | struct snd_pcm_hw_rule *rule) |
353 | { |
354 | struct qmc_dai *qmc_dai = rule->private; |
355 | |
356 | return qmc_dai_hw_rule_channels_by_format(qmc_dai, params, nb_ts: qmc_dai->nb_tx_ts); |
357 | } |
358 | |
359 | static int qmc_dai_hw_rule_capture_channels_by_format( |
360 | struct snd_pcm_hw_params *params, |
361 | struct snd_pcm_hw_rule *rule) |
362 | { |
363 | struct qmc_dai *qmc_dai = rule->private; |
364 | |
365 | return qmc_dai_hw_rule_channels_by_format(qmc_dai, params, nb_ts: qmc_dai->nb_rx_ts); |
366 | } |
367 | |
368 | static int qmc_dai_hw_rule_format_by_channels(struct qmc_dai *qmc_dai, |
369 | struct snd_pcm_hw_params *params, |
370 | unsigned int nb_ts) |
371 | { |
372 | struct snd_mask *f_old = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); |
373 | unsigned int channels = params_channels(p: params); |
374 | unsigned int slot_width; |
375 | snd_pcm_format_t format; |
376 | struct snd_mask f_new; |
377 | |
378 | if (!channels || channels > nb_ts) { |
379 | dev_err(qmc_dai->dev, "channels %u not supported\n" , |
380 | nb_ts); |
381 | return -EINVAL; |
382 | } |
383 | |
384 | slot_width = (nb_ts / channels) * 8; |
385 | |
386 | snd_mask_none(mask: &f_new); |
387 | pcm_for_each_format(format) { |
388 | if (snd_mask_test_format(mask: f_old, format)) { |
389 | if (snd_pcm_format_physical_width(format) <= slot_width) |
390 | snd_mask_set_format(mask: &f_new, format); |
391 | } |
392 | } |
393 | |
394 | return snd_mask_refine(mask: f_old, v: &f_new); |
395 | } |
396 | |
397 | static int qmc_dai_hw_rule_playback_format_by_channels( |
398 | struct snd_pcm_hw_params *params, |
399 | struct snd_pcm_hw_rule *rule) |
400 | { |
401 | struct qmc_dai *qmc_dai = rule->private; |
402 | |
403 | return qmc_dai_hw_rule_format_by_channels(qmc_dai, params, nb_ts: qmc_dai->nb_tx_ts); |
404 | } |
405 | |
406 | static int qmc_dai_hw_rule_capture_format_by_channels( |
407 | struct snd_pcm_hw_params *params, |
408 | struct snd_pcm_hw_rule *rule) |
409 | { |
410 | struct qmc_dai *qmc_dai = rule->private; |
411 | |
412 | return qmc_dai_hw_rule_format_by_channels(qmc_dai, params, nb_ts: qmc_dai->nb_rx_ts); |
413 | } |
414 | |
415 | static int qmc_dai_startup(struct snd_pcm_substream *substream, |
416 | struct snd_soc_dai *dai) |
417 | { |
418 | struct qmc_dai_prtd *prtd = substream->runtime->private_data; |
419 | snd_pcm_hw_rule_func_t hw_rule_channels_by_format; |
420 | snd_pcm_hw_rule_func_t hw_rule_format_by_channels; |
421 | struct qmc_dai *qmc_dai; |
422 | unsigned int frame_bits; |
423 | int ret; |
424 | |
425 | qmc_dai = qmc_dai_get_data(dai); |
426 | if (!qmc_dai) { |
427 | dev_err(dai->dev, "Invalid dai\n" ); |
428 | return -EINVAL; |
429 | } |
430 | |
431 | prtd->qmc_dai = qmc_dai; |
432 | |
433 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { |
434 | hw_rule_channels_by_format = qmc_dai_hw_rule_capture_channels_by_format; |
435 | hw_rule_format_by_channels = qmc_dai_hw_rule_capture_format_by_channels; |
436 | frame_bits = qmc_dai->nb_rx_ts * 8; |
437 | } else { |
438 | hw_rule_channels_by_format = qmc_dai_hw_rule_playback_channels_by_format; |
439 | hw_rule_format_by_channels = qmc_dai_hw_rule_playback_format_by_channels; |
440 | frame_bits = qmc_dai->nb_tx_ts * 8; |
441 | } |
442 | |
443 | ret = snd_pcm_hw_rule_add(runtime: substream->runtime, cond: 0, SNDRV_PCM_HW_PARAM_CHANNELS, |
444 | func: hw_rule_channels_by_format, private: qmc_dai, |
445 | SNDRV_PCM_HW_PARAM_FORMAT, -1); |
446 | if (ret) { |
447 | dev_err(dai->dev, "Failed to add channels rule (%d)\n" , ret); |
448 | return ret; |
449 | } |
450 | |
451 | ret = snd_pcm_hw_rule_add(runtime: substream->runtime, cond: 0, SNDRV_PCM_HW_PARAM_FORMAT, |
452 | func: hw_rule_format_by_channels, private: qmc_dai, |
453 | SNDRV_PCM_HW_PARAM_CHANNELS, -1); |
454 | if (ret) { |
455 | dev_err(dai->dev, "Failed to add format rule (%d)\n" , ret); |
456 | return ret; |
457 | } |
458 | |
459 | ret = snd_pcm_hw_constraint_single(runtime: substream->runtime, |
460 | SNDRV_PCM_HW_PARAM_FRAME_BITS, |
461 | val: frame_bits); |
462 | if (ret < 0) { |
463 | dev_err(dai->dev, "Failed to add frame_bits constraint (%d)\n" , ret); |
464 | return ret; |
465 | } |
466 | |
467 | return 0; |
468 | } |
469 | |
470 | static int qmc_dai_hw_params(struct snd_pcm_substream *substream, |
471 | struct snd_pcm_hw_params *params, |
472 | struct snd_soc_dai *dai) |
473 | { |
474 | struct qmc_chan_param chan_param = {0}; |
475 | struct qmc_dai *qmc_dai; |
476 | int ret; |
477 | |
478 | qmc_dai = qmc_dai_get_data(dai); |
479 | if (!qmc_dai) { |
480 | dev_err(dai->dev, "Invalid dai\n" ); |
481 | return -EINVAL; |
482 | } |
483 | |
484 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { |
485 | chan_param.mode = QMC_TRANSPARENT; |
486 | chan_param.transp.max_rx_buf_size = params_period_bytes(p: params); |
487 | ret = qmc_chan_set_param(chan: qmc_dai->qmc_chan, param: &chan_param); |
488 | if (ret) { |
489 | dev_err(dai->dev, "set param failed %d\n" , |
490 | ret); |
491 | return ret; |
492 | } |
493 | } |
494 | |
495 | return 0; |
496 | } |
497 | |
498 | static int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd, |
499 | struct snd_soc_dai *dai) |
500 | { |
501 | struct qmc_dai *qmc_dai; |
502 | int direction; |
503 | int ret; |
504 | |
505 | qmc_dai = qmc_dai_get_data(dai); |
506 | if (!qmc_dai) { |
507 | dev_err(dai->dev, "Invalid dai\n" ); |
508 | return -EINVAL; |
509 | } |
510 | |
511 | direction = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? |
512 | QMC_CHAN_WRITE : QMC_CHAN_READ; |
513 | |
514 | switch (cmd) { |
515 | case SNDRV_PCM_TRIGGER_START: |
516 | case SNDRV_PCM_TRIGGER_RESUME: |
517 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
518 | ret = qmc_chan_start(chan: qmc_dai->qmc_chan, direction); |
519 | if (ret) |
520 | return ret; |
521 | break; |
522 | |
523 | case SNDRV_PCM_TRIGGER_STOP: |
524 | ret = qmc_chan_stop(chan: qmc_dai->qmc_chan, direction); |
525 | if (ret) |
526 | return ret; |
527 | ret = qmc_chan_reset(chan: qmc_dai->qmc_chan, direction); |
528 | if (ret) |
529 | return ret; |
530 | break; |
531 | |
532 | case SNDRV_PCM_TRIGGER_SUSPEND: |
533 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
534 | ret = qmc_chan_stop(chan: qmc_dai->qmc_chan, direction); |
535 | if (ret) |
536 | return ret; |
537 | break; |
538 | |
539 | default: |
540 | return -EINVAL; |
541 | } |
542 | |
543 | return 0; |
544 | } |
545 | |
546 | static const struct snd_soc_dai_ops qmc_dai_ops = { |
547 | .startup = qmc_dai_startup, |
548 | .trigger = qmc_dai_trigger, |
549 | .hw_params = qmc_dai_hw_params, |
550 | }; |
551 | |
552 | static u64 qmc_audio_formats(u8 nb_ts) |
553 | { |
554 | unsigned int format_width; |
555 | unsigned int chan_width; |
556 | snd_pcm_format_t format; |
557 | u64 formats_mask; |
558 | |
559 | if (!nb_ts) |
560 | return 0; |
561 | |
562 | formats_mask = 0; |
563 | chan_width = nb_ts * 8; |
564 | pcm_for_each_format(format) { |
565 | /* |
566 | * Support format other than little-endian (ie big-endian or |
567 | * without endianness such as 8bit formats) |
568 | */ |
569 | if (snd_pcm_format_little_endian(format) == 1) |
570 | continue; |
571 | |
572 | /* Support physical width multiple of 8bit */ |
573 | format_width = snd_pcm_format_physical_width(format); |
574 | if (format_width == 0 || format_width % 8) |
575 | continue; |
576 | |
577 | /* |
578 | * And support physical width that can fit N times in the |
579 | * channel |
580 | */ |
581 | if (format_width > chan_width || chan_width % format_width) |
582 | continue; |
583 | |
584 | formats_mask |= pcm_format_to_bits(pcm_format: format); |
585 | } |
586 | return formats_mask; |
587 | } |
588 | |
589 | static int qmc_audio_dai_parse(struct qmc_audio *qmc_audio, struct device_node *np, |
590 | struct qmc_dai *qmc_dai, struct snd_soc_dai_driver *qmc_soc_dai_driver) |
591 | { |
592 | struct qmc_chan_info info; |
593 | u32 val; |
594 | int ret; |
595 | |
596 | qmc_dai->dev = qmc_audio->dev; |
597 | |
598 | ret = of_property_read_u32(np, propname: "reg" , out_value: &val); |
599 | if (ret) { |
600 | dev_err(qmc_audio->dev, "%pOF: failed to read reg\n" , np); |
601 | return ret; |
602 | } |
603 | qmc_dai->id = val; |
604 | |
605 | qmc_dai->name = devm_kasprintf(dev: qmc_audio->dev, GFP_KERNEL, fmt: "%s.%d" , |
606 | np->parent->name, qmc_dai->id); |
607 | |
608 | qmc_dai->qmc_chan = devm_qmc_chan_get_byphandle(dev: qmc_audio->dev, np, |
609 | phandle_name: "fsl,qmc-chan" ); |
610 | if (IS_ERR(ptr: qmc_dai->qmc_chan)) { |
611 | ret = PTR_ERR(ptr: qmc_dai->qmc_chan); |
612 | return dev_err_probe(dev: qmc_audio->dev, err: ret, |
613 | fmt: "dai %d get QMC channel failed\n" , qmc_dai->id); |
614 | } |
615 | |
616 | qmc_soc_dai_driver->id = qmc_dai->id; |
617 | qmc_soc_dai_driver->name = qmc_dai->name; |
618 | |
619 | ret = qmc_chan_get_info(chan: qmc_dai->qmc_chan, info: &info); |
620 | if (ret) { |
621 | dev_err(qmc_audio->dev, "dai %d get QMC channel info failed %d\n" , |
622 | qmc_dai->id, ret); |
623 | return ret; |
624 | } |
625 | dev_info(qmc_audio->dev, "dai %d QMC channel mode %d, nb_tx_ts %u, nb_rx_ts %u\n" , |
626 | qmc_dai->id, info.mode, info.nb_tx_ts, info.nb_rx_ts); |
627 | |
628 | if (info.mode != QMC_TRANSPARENT) { |
629 | dev_err(qmc_audio->dev, "dai %d QMC chan mode %d is not QMC_TRANSPARENT\n" , |
630 | qmc_dai->id, info.mode); |
631 | return -EINVAL; |
632 | } |
633 | qmc_dai->nb_tx_ts = info.nb_tx_ts; |
634 | qmc_dai->nb_rx_ts = info.nb_rx_ts; |
635 | |
636 | qmc_soc_dai_driver->playback.channels_min = 0; |
637 | qmc_soc_dai_driver->playback.channels_max = 0; |
638 | if (qmc_dai->nb_tx_ts) { |
639 | qmc_soc_dai_driver->playback.channels_min = 1; |
640 | qmc_soc_dai_driver->playback.channels_max = qmc_dai->nb_tx_ts; |
641 | } |
642 | qmc_soc_dai_driver->playback.formats = qmc_audio_formats(nb_ts: qmc_dai->nb_tx_ts); |
643 | |
644 | qmc_soc_dai_driver->capture.channels_min = 0; |
645 | qmc_soc_dai_driver->capture.channels_max = 0; |
646 | if (qmc_dai->nb_rx_ts) { |
647 | qmc_soc_dai_driver->capture.channels_min = 1; |
648 | qmc_soc_dai_driver->capture.channels_max = qmc_dai->nb_rx_ts; |
649 | } |
650 | qmc_soc_dai_driver->capture.formats = qmc_audio_formats(nb_ts: qmc_dai->nb_rx_ts); |
651 | |
652 | qmc_soc_dai_driver->playback.rates = snd_pcm_rate_to_rate_bit(rate: info.tx_fs_rate); |
653 | qmc_soc_dai_driver->playback.rate_min = info.tx_fs_rate; |
654 | qmc_soc_dai_driver->playback.rate_max = info.tx_fs_rate; |
655 | qmc_soc_dai_driver->capture.rates = snd_pcm_rate_to_rate_bit(rate: info.rx_fs_rate); |
656 | qmc_soc_dai_driver->capture.rate_min = info.rx_fs_rate; |
657 | qmc_soc_dai_driver->capture.rate_max = info.rx_fs_rate; |
658 | |
659 | qmc_soc_dai_driver->ops = &qmc_dai_ops; |
660 | |
661 | return 0; |
662 | } |
663 | |
664 | static int qmc_audio_probe(struct platform_device *pdev) |
665 | { |
666 | struct device_node *np = pdev->dev.of_node; |
667 | struct qmc_audio *qmc_audio; |
668 | struct device_node *child; |
669 | unsigned int i; |
670 | int ret; |
671 | |
672 | qmc_audio = devm_kzalloc(dev: &pdev->dev, size: sizeof(*qmc_audio), GFP_KERNEL); |
673 | if (!qmc_audio) |
674 | return -ENOMEM; |
675 | |
676 | qmc_audio->dev = &pdev->dev; |
677 | |
678 | qmc_audio->num_dais = of_get_available_child_count(np); |
679 | if (qmc_audio->num_dais) { |
680 | qmc_audio->dais = devm_kcalloc(dev: &pdev->dev, n: qmc_audio->num_dais, |
681 | size: sizeof(*qmc_audio->dais), |
682 | GFP_KERNEL); |
683 | if (!qmc_audio->dais) |
684 | return -ENOMEM; |
685 | |
686 | qmc_audio->dai_drivers = devm_kcalloc(dev: &pdev->dev, n: qmc_audio->num_dais, |
687 | size: sizeof(*qmc_audio->dai_drivers), |
688 | GFP_KERNEL); |
689 | if (!qmc_audio->dai_drivers) |
690 | return -ENOMEM; |
691 | } |
692 | |
693 | i = 0; |
694 | for_each_available_child_of_node(np, child) { |
695 | ret = qmc_audio_dai_parse(qmc_audio, np: child, |
696 | qmc_dai: qmc_audio->dais + i, |
697 | qmc_soc_dai_driver: qmc_audio->dai_drivers + i); |
698 | if (ret) { |
699 | of_node_put(node: child); |
700 | return ret; |
701 | } |
702 | i++; |
703 | } |
704 | |
705 | |
706 | platform_set_drvdata(pdev, data: qmc_audio); |
707 | |
708 | ret = devm_snd_soc_register_component(dev: qmc_audio->dev, |
709 | component_driver: &qmc_audio_soc_platform, |
710 | dai_drv: qmc_audio->dai_drivers, |
711 | num_dai: qmc_audio->num_dais); |
712 | if (ret) |
713 | return ret; |
714 | |
715 | return 0; |
716 | } |
717 | |
718 | static const struct of_device_id qmc_audio_id_table[] = { |
719 | { .compatible = "fsl,qmc-audio" }, |
720 | {} /* sentinel */ |
721 | }; |
722 | MODULE_DEVICE_TABLE(of, qmc_audio_id_table); |
723 | |
724 | static struct platform_driver qmc_audio_driver = { |
725 | .driver = { |
726 | .name = "fsl-qmc-audio" , |
727 | .of_match_table = of_match_ptr(qmc_audio_id_table), |
728 | }, |
729 | .probe = qmc_audio_probe, |
730 | }; |
731 | module_platform_driver(qmc_audio_driver); |
732 | |
733 | MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>" ); |
734 | MODULE_DESCRIPTION("CPM/QE QMC audio driver" ); |
735 | MODULE_LICENSE("GPL" ); |
736 | |