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

source code of linux/sound/soc/bcm/bcm63xx-pcm-whistler.c