Commit | Line | Data |
---|---|---|
88eb404c KL |
1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | // linux/sound/bcm/bcm63xx-pcm-whistler.c | |
3 | // BCM63xx whistler pcm interface | |
4 | // Copyright (c) 2020 Broadcom Corporation | |
5 | // Author: Kevin-Ke Li <kevin-ke.li@broadcom.com> | |
6 | ||
7 | #include <linux/dma-mapping.h> | |
8 | #include <linux/io.h> | |
9 | #include <linux/module.h> | |
10 | #include <sound/pcm_params.h> | |
11 | #include <linux/regmap.h> | |
12 | #include <linux/of_device.h> | |
13 | #include <sound/soc.h> | |
14 | #include "bcm63xx-i2s.h" | |
15 | ||
16 | ||
17 | struct i2s_dma_desc { | |
18 | unsigned char *dma_area; | |
19 | dma_addr_t dma_addr; | |
20 | unsigned int dma_len; | |
21 | }; | |
22 | ||
23 | struct bcm63xx_runtime_data { | |
24 | int dma_len; | |
25 | dma_addr_t dma_addr; | |
26 | dma_addr_t dma_addr_next; | |
27 | }; | |
28 | ||
29 | static const struct snd_pcm_hardware bcm63xx_pcm_hardware = { | |
30 | .info = SNDRV_PCM_INFO_MMAP | | |
31 | SNDRV_PCM_INFO_MMAP_VALID | | |
32 | SNDRV_PCM_INFO_INTERLEAVED | | |
33 | SNDRV_PCM_INFO_PAUSE | | |
34 | SNDRV_PCM_INFO_RESUME, | |
35 | .formats = SNDRV_PCM_FMTBIT_S32_LE, /* support S32 only */ | |
36 | .period_bytes_max = 8192 - 32, | |
37 | .periods_min = 1, | |
38 | .periods_max = PAGE_SIZE/sizeof(struct i2s_dma_desc), | |
39 | .buffer_bytes_max = 128 * 1024, | |
40 | .fifo_size = 32, | |
41 | }; | |
42 | ||
43 | static int bcm63xx_pcm_hw_params(struct snd_soc_component *component, | |
44 | struct snd_pcm_substream *substream, | |
45 | struct snd_pcm_hw_params *params) | |
46 | { | |
47 | struct i2s_dma_desc *dma_desc; | |
48 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
49 | struct snd_pcm_runtime *runtime = substream->runtime; | |
50 | ||
51 | snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); | |
52 | runtime->dma_bytes = params_buffer_bytes(params); | |
53 | ||
54 | dma_desc = kzalloc(sizeof(*dma_desc), GFP_NOWAIT); | |
55 | if (!dma_desc) | |
56 | return -ENOMEM; | |
57 | ||
fc392364 | 58 | snd_soc_dai_set_dma_data(asoc_rtd_to_cpu(rtd, 0), substream, dma_desc); |
88eb404c KL |
59 | |
60 | return 0; | |
61 | } | |
62 | ||
63 | static int bcm63xx_pcm_hw_free(struct snd_soc_component *component, | |
64 | struct snd_pcm_substream *substream) | |
65 | { | |
66 | struct i2s_dma_desc *dma_desc; | |
67 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
68 | ||
fc392364 | 69 | dma_desc = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); |
88eb404c KL |
70 | kfree(dma_desc); |
71 | snd_pcm_set_runtime_buffer(substream, NULL); | |
72 | ||
73 | return 0; | |
74 | } | |
75 | ||
76 | static int bcm63xx_pcm_trigger(struct snd_soc_component *component, | |
77 | struct snd_pcm_substream *substream, int cmd) | |
78 | { | |
79 | int ret = 0; | |
80 | struct snd_soc_pcm_runtime *rtd; | |
81 | struct bcm_i2s_priv *i2s_priv; | |
82 | struct regmap *regmap_i2s; | |
83 | ||
84 | rtd = substream->private_data; | |
fc392364 | 85 | i2s_priv = dev_get_drvdata(asoc_rtd_to_cpu(rtd, 0)->dev); |
88eb404c KL |
86 | regmap_i2s = i2s_priv->regmap_i2s; |
87 | ||
88 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
89 | switch (cmd) { | |
90 | case SNDRV_PCM_TRIGGER_START: | |
91 | regmap_update_bits(regmap_i2s, | |
92 | I2S_TX_IRQ_EN, | |
93 | I2S_TX_DESC_OFF_INTR_EN, | |
94 | I2S_TX_DESC_OFF_INTR_EN); | |
95 | regmap_update_bits(regmap_i2s, | |
96 | I2S_TX_CFG, | |
97 | I2S_TX_ENABLE_MASK, | |
98 | I2S_TX_ENABLE); | |
99 | break; | |
100 | case SNDRV_PCM_TRIGGER_STOP: | |
101 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
102 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
103 | regmap_write(regmap_i2s, | |
104 | I2S_TX_IRQ_EN, | |
105 | 0); | |
106 | regmap_update_bits(regmap_i2s, | |
107 | I2S_TX_CFG, | |
108 | I2S_TX_ENABLE_MASK, | |
109 | 0); | |
110 | break; | |
111 | default: | |
112 | ret = -EINVAL; | |
113 | } | |
114 | } else { | |
115 | switch (cmd) { | |
116 | case SNDRV_PCM_TRIGGER_START: | |
117 | regmap_update_bits(regmap_i2s, | |
118 | I2S_RX_IRQ_EN, | |
119 | I2S_RX_DESC_OFF_INTR_EN_MSK, | |
120 | I2S_RX_DESC_OFF_INTR_EN); | |
121 | regmap_update_bits(regmap_i2s, | |
122 | I2S_RX_CFG, | |
123 | I2S_RX_ENABLE_MASK, | |
124 | I2S_RX_ENABLE); | |
125 | break; | |
126 | case SNDRV_PCM_TRIGGER_STOP: | |
127 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
128 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
129 | regmap_update_bits(regmap_i2s, | |
130 | I2S_RX_IRQ_EN, | |
131 | I2S_RX_DESC_OFF_INTR_EN_MSK, | |
132 | 0); | |
133 | regmap_update_bits(regmap_i2s, | |
134 | I2S_RX_CFG, | |
135 | I2S_RX_ENABLE_MASK, | |
136 | 0); | |
137 | break; | |
138 | default: | |
139 | ret = -EINVAL; | |
140 | } | |
141 | } | |
142 | return ret; | |
143 | } | |
144 | ||
145 | static int bcm63xx_pcm_prepare(struct snd_soc_component *component, | |
146 | struct snd_pcm_substream *substream) | |
147 | { | |
148 | struct i2s_dma_desc *dma_desc; | |
149 | struct regmap *regmap_i2s; | |
150 | struct bcm_i2s_priv *i2s_priv; | |
151 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
152 | struct snd_pcm_runtime *runtime = substream->runtime; | |
153 | uint32_t regaddr_desclen, regaddr_descaddr; | |
154 | ||
fc392364 | 155 | dma_desc = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); |
88eb404c KL |
156 | dma_desc->dma_len = snd_pcm_lib_period_bytes(substream); |
157 | dma_desc->dma_addr = runtime->dma_addr; | |
158 | dma_desc->dma_area = runtime->dma_area; | |
159 | ||
160 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
161 | regaddr_desclen = I2S_TX_DESC_IFF_LEN; | |
162 | regaddr_descaddr = I2S_TX_DESC_IFF_ADDR; | |
163 | } else { | |
164 | regaddr_desclen = I2S_RX_DESC_IFF_LEN; | |
165 | regaddr_descaddr = I2S_RX_DESC_IFF_ADDR; | |
166 | } | |
167 | ||
fc392364 | 168 | i2s_priv = dev_get_drvdata(asoc_rtd_to_cpu(rtd, 0)->dev); |
88eb404c KL |
169 | regmap_i2s = i2s_priv->regmap_i2s; |
170 | ||
171 | regmap_write(regmap_i2s, regaddr_desclen, dma_desc->dma_len); | |
172 | regmap_write(regmap_i2s, regaddr_descaddr, dma_desc->dma_addr); | |
173 | ||
174 | return 0; | |
175 | } | |
176 | ||
177 | static snd_pcm_uframes_t | |
178 | bcm63xx_pcm_pointer(struct snd_soc_component *component, | |
179 | struct snd_pcm_substream *substream) | |
180 | { | |
181 | snd_pcm_uframes_t x; | |
182 | struct bcm63xx_runtime_data *prtd = substream->runtime->private_data; | |
183 | ||
76385a66 | 184 | if (!prtd->dma_addr_next) |
88eb404c KL |
185 | prtd->dma_addr_next = substream->runtime->dma_addr; |
186 | ||
187 | x = bytes_to_frames(substream->runtime, | |
188 | prtd->dma_addr_next - substream->runtime->dma_addr); | |
189 | ||
190 | return x == substream->runtime->buffer_size ? 0 : x; | |
191 | } | |
192 | ||
193 | static int bcm63xx_pcm_mmap(struct snd_soc_component *component, | |
194 | struct snd_pcm_substream *substream, | |
195 | struct vm_area_struct *vma) | |
196 | { | |
197 | struct snd_pcm_runtime *runtime = substream->runtime; | |
198 | ||
199 | return dma_mmap_wc(substream->pcm->card->dev, vma, | |
200 | runtime->dma_area, | |
201 | runtime->dma_addr, | |
202 | runtime->dma_bytes); | |
203 | ||
204 | } | |
205 | ||
206 | static int bcm63xx_pcm_open(struct snd_soc_component *component, | |
207 | struct snd_pcm_substream *substream) | |
208 | { | |
209 | int ret = 0; | |
210 | struct snd_pcm_runtime *runtime = substream->runtime; | |
211 | struct bcm63xx_runtime_data *prtd; | |
212 | ||
213 | runtime->hw = bcm63xx_pcm_hardware; | |
214 | ret = snd_pcm_hw_constraint_step(runtime, 0, | |
215 | SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); | |
216 | if (ret) | |
217 | goto out; | |
218 | ||
219 | ret = snd_pcm_hw_constraint_step(runtime, 0, | |
220 | SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); | |
221 | if (ret) | |
222 | goto out; | |
223 | ||
224 | ret = snd_pcm_hw_constraint_integer(runtime, | |
225 | SNDRV_PCM_HW_PARAM_PERIODS); | |
226 | if (ret < 0) | |
227 | goto out; | |
228 | ||
229 | ret = -ENOMEM; | |
230 | prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); | |
231 | if (!prtd) | |
232 | goto out; | |
233 | ||
234 | runtime->private_data = prtd; | |
235 | return 0; | |
236 | out: | |
237 | return ret; | |
238 | } | |
239 | ||
240 | static int bcm63xx_pcm_close(struct snd_soc_component *component, | |
241 | struct snd_pcm_substream *substream) | |
242 | { | |
243 | struct snd_pcm_runtime *runtime = substream->runtime; | |
244 | struct bcm63xx_runtime_data *prtd = runtime->private_data; | |
245 | ||
246 | kfree(prtd); | |
247 | return 0; | |
248 | } | |
249 | ||
250 | static irqreturn_t i2s_dma_isr(int irq, void *bcm_i2s_priv) | |
251 | { | |
252 | unsigned int availdepth, ifflevel, offlevel, int_status, val_1, val_2; | |
253 | struct bcm63xx_runtime_data *prtd; | |
254 | struct snd_pcm_substream *substream; | |
255 | struct snd_pcm_runtime *runtime; | |
256 | struct regmap *regmap_i2s; | |
257 | struct i2s_dma_desc *dma_desc; | |
258 | struct snd_soc_pcm_runtime *rtd; | |
259 | struct bcm_i2s_priv *i2s_priv; | |
260 | ||
261 | i2s_priv = (struct bcm_i2s_priv *)bcm_i2s_priv; | |
262 | regmap_i2s = i2s_priv->regmap_i2s; | |
263 | ||
264 | /* rx */ | |
265 | regmap_read(regmap_i2s, I2S_RX_IRQ_CTL, &int_status); | |
266 | ||
267 | if (int_status & I2S_RX_DESC_OFF_INTR_EN_MSK) { | |
268 | substream = i2s_priv->capture_substream; | |
269 | runtime = substream->runtime; | |
270 | rtd = substream->private_data; | |
271 | prtd = runtime->private_data; | |
fc392364 | 272 | dma_desc = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); |
88eb404c KL |
273 | |
274 | offlevel = (int_status & I2S_RX_DESC_OFF_LEVEL_MASK) >> | |
275 | I2S_RX_DESC_OFF_LEVEL_SHIFT; | |
276 | while (offlevel) { | |
277 | regmap_read(regmap_i2s, I2S_RX_DESC_OFF_ADDR, &val_1); | |
278 | regmap_read(regmap_i2s, I2S_RX_DESC_OFF_LEN, &val_2); | |
279 | offlevel--; | |
280 | } | |
281 | prtd->dma_addr_next = val_1 + val_2; | |
282 | ifflevel = (int_status & I2S_RX_DESC_IFF_LEVEL_MASK) >> | |
283 | I2S_RX_DESC_IFF_LEVEL_SHIFT; | |
284 | ||
285 | availdepth = I2S_DESC_FIFO_DEPTH - ifflevel; | |
286 | while (availdepth) { | |
287 | dma_desc->dma_addr += | |
288 | snd_pcm_lib_period_bytes(substream); | |
289 | dma_desc->dma_area += | |
290 | snd_pcm_lib_period_bytes(substream); | |
291 | if (dma_desc->dma_addr - runtime->dma_addr >= | |
292 | runtime->dma_bytes) { | |
293 | dma_desc->dma_addr = runtime->dma_addr; | |
294 | dma_desc->dma_area = runtime->dma_area; | |
295 | } | |
296 | ||
297 | prtd->dma_addr = dma_desc->dma_addr; | |
298 | regmap_write(regmap_i2s, I2S_RX_DESC_IFF_LEN, | |
299 | snd_pcm_lib_period_bytes(substream)); | |
300 | regmap_write(regmap_i2s, I2S_RX_DESC_IFF_ADDR, | |
301 | dma_desc->dma_addr); | |
302 | availdepth--; | |
303 | } | |
304 | ||
305 | snd_pcm_period_elapsed(substream); | |
306 | ||
307 | /* Clear interrupt by writing 0 */ | |
308 | regmap_update_bits(regmap_i2s, I2S_RX_IRQ_CTL, | |
309 | I2S_RX_INTR_MASK, 0); | |
310 | } | |
311 | ||
312 | /* tx */ | |
313 | regmap_read(regmap_i2s, I2S_TX_IRQ_CTL, &int_status); | |
314 | ||
315 | if (int_status & I2S_TX_DESC_OFF_INTR_EN_MSK) { | |
316 | substream = i2s_priv->play_substream; | |
317 | runtime = substream->runtime; | |
318 | rtd = substream->private_data; | |
319 | prtd = runtime->private_data; | |
fc392364 | 320 | dma_desc = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); |
88eb404c KL |
321 | |
322 | offlevel = (int_status & I2S_TX_DESC_OFF_LEVEL_MASK) >> | |
323 | I2S_TX_DESC_OFF_LEVEL_SHIFT; | |
324 | while (offlevel) { | |
325 | regmap_read(regmap_i2s, I2S_TX_DESC_OFF_ADDR, &val_1); | |
326 | regmap_read(regmap_i2s, I2S_TX_DESC_OFF_LEN, &val_2); | |
327 | prtd->dma_addr_next = val_1 + val_2; | |
328 | offlevel--; | |
329 | } | |
330 | ||
331 | ifflevel = (int_status & I2S_TX_DESC_IFF_LEVEL_MASK) >> | |
332 | I2S_TX_DESC_IFF_LEVEL_SHIFT; | |
333 | availdepth = I2S_DESC_FIFO_DEPTH - ifflevel; | |
334 | ||
335 | while (availdepth) { | |
336 | dma_desc->dma_addr += | |
337 | snd_pcm_lib_period_bytes(substream); | |
338 | dma_desc->dma_area += | |
339 | snd_pcm_lib_period_bytes(substream); | |
340 | ||
341 | if (dma_desc->dma_addr - runtime->dma_addr >= | |
342 | runtime->dma_bytes) { | |
343 | dma_desc->dma_addr = runtime->dma_addr; | |
344 | dma_desc->dma_area = runtime->dma_area; | |
345 | } | |
346 | ||
347 | prtd->dma_addr = dma_desc->dma_addr; | |
348 | regmap_write(regmap_i2s, I2S_TX_DESC_IFF_LEN, | |
349 | snd_pcm_lib_period_bytes(substream)); | |
350 | regmap_write(regmap_i2s, I2S_TX_DESC_IFF_ADDR, | |
351 | dma_desc->dma_addr); | |
352 | availdepth--; | |
353 | } | |
354 | ||
355 | snd_pcm_period_elapsed(substream); | |
356 | ||
357 | /* Clear interrupt by writing 0 */ | |
358 | regmap_update_bits(regmap_i2s, I2S_TX_IRQ_CTL, | |
359 | I2S_TX_INTR_MASK, 0); | |
360 | } | |
361 | ||
362 | return IRQ_HANDLED; | |
363 | } | |
364 | ||
365 | static int bcm63xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) | |
366 | { | |
367 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | |
368 | struct snd_dma_buffer *buf = &substream->dma_buffer; | |
369 | size_t size = bcm63xx_pcm_hardware.buffer_bytes_max; | |
370 | ||
371 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | |
372 | buf->dev.dev = pcm->card->dev; | |
373 | buf->private_data = NULL; | |
374 | ||
375 | buf->area = dma_alloc_wc(pcm->card->dev, | |
376 | size, &buf->addr, | |
377 | GFP_KERNEL); | |
378 | if (!buf->area) | |
379 | return -ENOMEM; | |
380 | buf->bytes = size; | |
381 | return 0; | |
382 | } | |
383 | ||
384 | static int bcm63xx_soc_pcm_new(struct snd_soc_component *component, | |
385 | struct snd_soc_pcm_runtime *rtd) | |
386 | { | |
387 | struct snd_pcm *pcm = rtd->pcm; | |
388 | struct bcm_i2s_priv *i2s_priv; | |
389 | int ret; | |
390 | ||
fc392364 | 391 | i2s_priv = dev_get_drvdata(asoc_rtd_to_cpu(rtd, 0)->dev); |
88eb404c KL |
392 | |
393 | of_dma_configure(pcm->card->dev, pcm->card->dev->of_node, 1); | |
394 | ||
395 | ret = dma_coerce_mask_and_coherent(pcm->card->dev, DMA_BIT_MASK(32)); | |
396 | if (ret) | |
397 | goto out; | |
398 | ||
399 | if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { | |
400 | ret = bcm63xx_pcm_preallocate_dma_buffer(pcm, | |
401 | SNDRV_PCM_STREAM_PLAYBACK); | |
402 | if (ret) | |
403 | goto out; | |
404 | ||
405 | i2s_priv->play_substream = | |
406 | pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; | |
407 | } | |
408 | ||
409 | if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { | |
410 | ret = bcm63xx_pcm_preallocate_dma_buffer(pcm, | |
411 | SNDRV_PCM_STREAM_CAPTURE); | |
412 | if (ret) | |
413 | goto out; | |
414 | i2s_priv->capture_substream = | |
415 | pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; | |
416 | } | |
417 | ||
418 | out: | |
419 | return ret; | |
420 | } | |
421 | ||
422 | static void bcm63xx_pcm_free_dma_buffers(struct snd_soc_component *component, | |
423 | struct snd_pcm *pcm) | |
424 | { | |
425 | int stream; | |
426 | struct snd_dma_buffer *buf; | |
427 | struct snd_pcm_substream *substream; | |
428 | ||
429 | for (stream = 0; stream < 2; stream++) { | |
430 | substream = pcm->streams[stream].substream; | |
431 | if (!substream) | |
432 | continue; | |
433 | buf = &substream->dma_buffer; | |
434 | if (!buf->area) | |
435 | continue; | |
436 | dma_free_wc(pcm->card->dev, buf->bytes, | |
437 | buf->area, buf->addr); | |
438 | buf->area = NULL; | |
439 | } | |
440 | } | |
441 | ||
442 | static const struct snd_soc_component_driver bcm63xx_soc_platform = { | |
443 | .open = bcm63xx_pcm_open, | |
444 | .close = bcm63xx_pcm_close, | |
445 | .hw_params = bcm63xx_pcm_hw_params, | |
446 | .hw_free = bcm63xx_pcm_hw_free, | |
447 | .prepare = bcm63xx_pcm_prepare, | |
448 | .trigger = bcm63xx_pcm_trigger, | |
449 | .pointer = bcm63xx_pcm_pointer, | |
450 | .mmap = bcm63xx_pcm_mmap, | |
451 | .pcm_construct = bcm63xx_soc_pcm_new, | |
452 | .pcm_destruct = bcm63xx_pcm_free_dma_buffers, | |
453 | }; | |
454 | ||
455 | int bcm63xx_soc_platform_probe(struct platform_device *pdev, | |
456 | struct bcm_i2s_priv *i2s_priv) | |
457 | { | |
458 | int ret; | |
459 | ||
460 | i2s_priv->r_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
461 | if (!i2s_priv->r_irq) { | |
462 | dev_err(&pdev->dev, "Unable to get register irq resource.\n"); | |
463 | return -ENODEV; | |
464 | } | |
465 | ||
466 | ret = devm_request_irq(&pdev->dev, i2s_priv->r_irq->start, i2s_dma_isr, | |
467 | i2s_priv->r_irq->flags, "i2s_dma", (void *)i2s_priv); | |
468 | if (ret) { | |
469 | dev_err(&pdev->dev, | |
470 | "i2s_init: failed to request interrupt.ret=%d\n", ret); | |
471 | return ret; | |
472 | } | |
473 | ||
474 | return devm_snd_soc_register_component(&pdev->dev, | |
475 | &bcm63xx_soc_platform, NULL, 0); | |
476 | } | |
477 | ||
478 | int bcm63xx_soc_platform_remove(struct platform_device *pdev) | |
479 | { | |
480 | return 0; | |
481 | } | |
482 | ||
483 | MODULE_AUTHOR("Kevin,Li <kevin-ke.li@broadcom.com>"); | |
484 | MODULE_DESCRIPTION("Broadcom DSL XPON ASOC PCM Interface"); | |
485 | MODULE_LICENSE("GPL v2"); |