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 | |
18 | struct i2s_dma_desc { |
19 | unsigned char *dma_area; |
20 | dma_addr_t dma_addr; |
21 | unsigned int dma_len; |
22 | }; |
23 | |
24 | struct bcm63xx_runtime_data { |
25 | int dma_len; |
26 | dma_addr_t dma_addr; |
27 | dma_addr_t dma_addr_next; |
28 | }; |
29 | |
30 | static 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 | |
44 | static 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 | |
60 | static 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 | |
72 | static 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 | |
141 | static 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 | |
173 | static snd_pcm_uframes_t |
174 | bcm63xx_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 | |
189 | static 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; |
219 | out: |
220 | return ret; |
221 | } |
222 | |
223 | static 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 | |
233 | static 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 | |
348 | static 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 | |
375 | static 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 | |
386 | int 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 | |
407 | int bcm63xx_soc_platform_remove(struct platform_device *pdev) |
408 | { |
409 | return 0; |
410 | } |
411 | |
412 | MODULE_AUTHOR("Kevin,Li <kevin-ke.li@broadcom.com>" ); |
413 | MODULE_DESCRIPTION("Broadcom DSL XPON ASOC PCM Interface" ); |
414 | MODULE_LICENSE("GPL v2" ); |
415 | |