1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // Copyright (C) 2014-2015 Broadcom Corporation |
3 | #include <linux/debugfs.h> |
4 | #include <linux/dma-mapping.h> |
5 | #include <linux/init.h> |
6 | #include <linux/io.h> |
7 | #include <linux/module.h> |
8 | #include <linux/slab.h> |
9 | #include <linux/timer.h> |
10 | #include <sound/core.h> |
11 | #include <sound/pcm.h> |
12 | #include <sound/pcm_params.h> |
13 | #include <sound/soc.h> |
14 | #include <sound/soc-dai.h> |
15 | |
16 | #include "cygnus-ssp.h" |
17 | |
18 | /* Register offset needed for ASoC PCM module */ |
19 | |
20 | #define INTH_R5F_STATUS_OFFSET 0x040 |
21 | #define INTH_R5F_CLEAR_OFFSET 0x048 |
22 | #define INTH_R5F_MASK_SET_OFFSET 0x050 |
23 | #define INTH_R5F_MASK_CLEAR_OFFSET 0x054 |
24 | |
25 | #define BF_REARM_FREE_MARK_OFFSET 0x344 |
26 | #define BF_REARM_FULL_MARK_OFFSET 0x348 |
27 | |
28 | /* Ring Buffer Ctrl Regs --- Start */ |
29 | /* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_RDADDR_REG_BASE */ |
30 | #define SRC_RBUF_0_RDADDR_OFFSET 0x500 |
31 | #define SRC_RBUF_1_RDADDR_OFFSET 0x518 |
32 | #define SRC_RBUF_2_RDADDR_OFFSET 0x530 |
33 | #define SRC_RBUF_3_RDADDR_OFFSET 0x548 |
34 | #define SRC_RBUF_4_RDADDR_OFFSET 0x560 |
35 | #define SRC_RBUF_5_RDADDR_OFFSET 0x578 |
36 | #define SRC_RBUF_6_RDADDR_OFFSET 0x590 |
37 | |
38 | /* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_WRADDR_REG_BASE */ |
39 | #define SRC_RBUF_0_WRADDR_OFFSET 0x504 |
40 | #define SRC_RBUF_1_WRADDR_OFFSET 0x51c |
41 | #define SRC_RBUF_2_WRADDR_OFFSET 0x534 |
42 | #define SRC_RBUF_3_WRADDR_OFFSET 0x54c |
43 | #define SRC_RBUF_4_WRADDR_OFFSET 0x564 |
44 | #define SRC_RBUF_5_WRADDR_OFFSET 0x57c |
45 | #define SRC_RBUF_6_WRADDR_OFFSET 0x594 |
46 | |
47 | /* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_BASEADDR_REG_BASE */ |
48 | #define SRC_RBUF_0_BASEADDR_OFFSET 0x508 |
49 | #define SRC_RBUF_1_BASEADDR_OFFSET 0x520 |
50 | #define SRC_RBUF_2_BASEADDR_OFFSET 0x538 |
51 | #define SRC_RBUF_3_BASEADDR_OFFSET 0x550 |
52 | #define SRC_RBUF_4_BASEADDR_OFFSET 0x568 |
53 | #define SRC_RBUF_5_BASEADDR_OFFSET 0x580 |
54 | #define SRC_RBUF_6_BASEADDR_OFFSET 0x598 |
55 | |
56 | /* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_ENDADDR_REG_BASE */ |
57 | #define SRC_RBUF_0_ENDADDR_OFFSET 0x50c |
58 | #define SRC_RBUF_1_ENDADDR_OFFSET 0x524 |
59 | #define SRC_RBUF_2_ENDADDR_OFFSET 0x53c |
60 | #define SRC_RBUF_3_ENDADDR_OFFSET 0x554 |
61 | #define SRC_RBUF_4_ENDADDR_OFFSET 0x56c |
62 | #define SRC_RBUF_5_ENDADDR_OFFSET 0x584 |
63 | #define SRC_RBUF_6_ENDADDR_OFFSET 0x59c |
64 | |
65 | /* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_FREE_MARK_REG_BASE */ |
66 | #define SRC_RBUF_0_FREE_MARK_OFFSET 0x510 |
67 | #define SRC_RBUF_1_FREE_MARK_OFFSET 0x528 |
68 | #define SRC_RBUF_2_FREE_MARK_OFFSET 0x540 |
69 | #define SRC_RBUF_3_FREE_MARK_OFFSET 0x558 |
70 | #define SRC_RBUF_4_FREE_MARK_OFFSET 0x570 |
71 | #define SRC_RBUF_5_FREE_MARK_OFFSET 0x588 |
72 | #define SRC_RBUF_6_FREE_MARK_OFFSET 0x5a0 |
73 | |
74 | /* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_RDADDR_REG_BASE */ |
75 | #define DST_RBUF_0_RDADDR_OFFSET 0x5c0 |
76 | #define DST_RBUF_1_RDADDR_OFFSET 0x5d8 |
77 | #define DST_RBUF_2_RDADDR_OFFSET 0x5f0 |
78 | #define DST_RBUF_3_RDADDR_OFFSET 0x608 |
79 | #define DST_RBUF_4_RDADDR_OFFSET 0x620 |
80 | #define DST_RBUF_5_RDADDR_OFFSET 0x638 |
81 | |
82 | /* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_WRADDR_REG_BASE */ |
83 | #define DST_RBUF_0_WRADDR_OFFSET 0x5c4 |
84 | #define DST_RBUF_1_WRADDR_OFFSET 0x5dc |
85 | #define DST_RBUF_2_WRADDR_OFFSET 0x5f4 |
86 | #define DST_RBUF_3_WRADDR_OFFSET 0x60c |
87 | #define DST_RBUF_4_WRADDR_OFFSET 0x624 |
88 | #define DST_RBUF_5_WRADDR_OFFSET 0x63c |
89 | |
90 | /* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_BASEADDR_REG_BASE */ |
91 | #define DST_RBUF_0_BASEADDR_OFFSET 0x5c8 |
92 | #define DST_RBUF_1_BASEADDR_OFFSET 0x5e0 |
93 | #define DST_RBUF_2_BASEADDR_OFFSET 0x5f8 |
94 | #define DST_RBUF_3_BASEADDR_OFFSET 0x610 |
95 | #define DST_RBUF_4_BASEADDR_OFFSET 0x628 |
96 | #define DST_RBUF_5_BASEADDR_OFFSET 0x640 |
97 | |
98 | /* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_ENDADDR_REG_BASE */ |
99 | #define DST_RBUF_0_ENDADDR_OFFSET 0x5cc |
100 | #define DST_RBUF_1_ENDADDR_OFFSET 0x5e4 |
101 | #define DST_RBUF_2_ENDADDR_OFFSET 0x5fc |
102 | #define DST_RBUF_3_ENDADDR_OFFSET 0x614 |
103 | #define DST_RBUF_4_ENDADDR_OFFSET 0x62c |
104 | #define DST_RBUF_5_ENDADDR_OFFSET 0x644 |
105 | |
106 | /* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_FULL_MARK_REG_BASE */ |
107 | #define DST_RBUF_0_FULL_MARK_OFFSET 0x5d0 |
108 | #define DST_RBUF_1_FULL_MARK_OFFSET 0x5e8 |
109 | #define DST_RBUF_2_FULL_MARK_OFFSET 0x600 |
110 | #define DST_RBUF_3_FULL_MARK_OFFSET 0x618 |
111 | #define DST_RBUF_4_FULL_MARK_OFFSET 0x630 |
112 | #define DST_RBUF_5_FULL_MARK_OFFSET 0x648 |
113 | /* Ring Buffer Ctrl Regs --- End */ |
114 | |
115 | /* Error Status Regs --- Start */ |
116 | /* AUD_FMM_BF_ESR_ESRX_STATUS_REG_BASE */ |
117 | #define ESR0_STATUS_OFFSET 0x900 |
118 | #define ESR1_STATUS_OFFSET 0x918 |
119 | #define ESR2_STATUS_OFFSET 0x930 |
120 | #define ESR3_STATUS_OFFSET 0x948 |
121 | #define ESR4_STATUS_OFFSET 0x960 |
122 | |
123 | /* AUD_FMM_BF_ESR_ESRX_STATUS_CLEAR_REG_BASE */ |
124 | #define ESR0_STATUS_CLR_OFFSET 0x908 |
125 | #define ESR1_STATUS_CLR_OFFSET 0x920 |
126 | #define ESR2_STATUS_CLR_OFFSET 0x938 |
127 | #define ESR3_STATUS_CLR_OFFSET 0x950 |
128 | #define ESR4_STATUS_CLR_OFFSET 0x968 |
129 | |
130 | /* AUD_FMM_BF_ESR_ESRX_MASK_REG_BASE */ |
131 | #define ESR0_MASK_STATUS_OFFSET 0x90c |
132 | #define ESR1_MASK_STATUS_OFFSET 0x924 |
133 | #define ESR2_MASK_STATUS_OFFSET 0x93c |
134 | #define ESR3_MASK_STATUS_OFFSET 0x954 |
135 | #define ESR4_MASK_STATUS_OFFSET 0x96c |
136 | |
137 | /* AUD_FMM_BF_ESR_ESRX_MASK_SET_REG_BASE */ |
138 | #define ESR0_MASK_SET_OFFSET 0x910 |
139 | #define ESR1_MASK_SET_OFFSET 0x928 |
140 | #define ESR2_MASK_SET_OFFSET 0x940 |
141 | #define ESR3_MASK_SET_OFFSET 0x958 |
142 | #define ESR4_MASK_SET_OFFSET 0x970 |
143 | |
144 | /* AUD_FMM_BF_ESR_ESRX_MASK_CLEAR_REG_BASE */ |
145 | #define ESR0_MASK_CLR_OFFSET 0x914 |
146 | #define ESR1_MASK_CLR_OFFSET 0x92c |
147 | #define ESR2_MASK_CLR_OFFSET 0x944 |
148 | #define ESR3_MASK_CLR_OFFSET 0x95c |
149 | #define ESR4_MASK_CLR_OFFSET 0x974 |
150 | /* Error Status Regs --- End */ |
151 | |
152 | #define R5F_ESR0_SHIFT 0 /* esr0 = fifo underflow */ |
153 | #define R5F_ESR1_SHIFT 1 /* esr1 = ringbuf underflow */ |
154 | #define R5F_ESR2_SHIFT 2 /* esr2 = ringbuf overflow */ |
155 | #define R5F_ESR3_SHIFT 3 /* esr3 = freemark */ |
156 | #define R5F_ESR4_SHIFT 4 /* esr4 = fullmark */ |
157 | |
158 | |
159 | /* Mask for R5F register. Set all relevant interrupt for playback handler */ |
160 | #define ANY_PLAYBACK_IRQ (BIT(R5F_ESR0_SHIFT) | \ |
161 | BIT(R5F_ESR1_SHIFT) | \ |
162 | BIT(R5F_ESR3_SHIFT)) |
163 | |
164 | /* Mask for R5F register. Set all relevant interrupt for capture handler */ |
165 | #define ANY_CAPTURE_IRQ (BIT(R5F_ESR2_SHIFT) | BIT(R5F_ESR4_SHIFT)) |
166 | |
167 | /* |
168 | * PERIOD_BYTES_MIN is the number of bytes to at which the interrupt will tick. |
169 | * This number should be a multiple of 256. Minimum value is 256 |
170 | */ |
171 | #define PERIOD_BYTES_MIN 0x100 |
172 | |
173 | static const struct snd_pcm_hardware cygnus_pcm_hw = { |
174 | .info = SNDRV_PCM_INFO_MMAP | |
175 | SNDRV_PCM_INFO_MMAP_VALID | |
176 | SNDRV_PCM_INFO_INTERLEAVED, |
177 | .formats = SNDRV_PCM_FMTBIT_S16_LE | |
178 | SNDRV_PCM_FMTBIT_S32_LE, |
179 | |
180 | /* A period is basically an interrupt */ |
181 | .period_bytes_min = PERIOD_BYTES_MIN, |
182 | .period_bytes_max = 0x10000, |
183 | |
184 | /* period_min/max gives range of approx interrupts per buffer */ |
185 | .periods_min = 2, |
186 | .periods_max = 8, |
187 | |
188 | /* |
189 | * maximum buffer size in bytes = period_bytes_max * periods_max |
190 | * We allocate this amount of data for each enabled channel |
191 | */ |
192 | .buffer_bytes_max = 4 * 0x8000, |
193 | }; |
194 | |
195 | static u64 cygnus_dma_dmamask = DMA_BIT_MASK(32); |
196 | |
197 | static struct cygnus_aio_port *cygnus_dai_get_dma_data( |
198 | struct snd_pcm_substream *substream) |
199 | { |
200 | struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); |
201 | |
202 | return snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(soc_runtime, 0), substream); |
203 | } |
204 | |
205 | static void ringbuf_set_initial(void __iomem *audio_io, |
206 | struct ringbuf_regs *p_rbuf, |
207 | bool is_playback, |
208 | u32 start, |
209 | u32 periodsize, |
210 | u32 bufsize) |
211 | { |
212 | u32 initial_rd; |
213 | u32 initial_wr; |
214 | u32 end; |
215 | u32 fmark_val; /* free or full mark */ |
216 | |
217 | p_rbuf->period_bytes = periodsize; |
218 | p_rbuf->buf_size = bufsize; |
219 | |
220 | if (is_playback) { |
221 | /* Set the pointers to indicate full (flip uppermost bit) */ |
222 | initial_rd = start; |
223 | initial_wr = initial_rd ^ BIT(31); |
224 | } else { |
225 | /* Set the pointers to indicate empty */ |
226 | initial_wr = start; |
227 | initial_rd = initial_wr; |
228 | } |
229 | |
230 | end = start + bufsize - 1; |
231 | |
232 | /* |
233 | * The interrupt will fire when free/full mark is *exceeded* |
234 | * The fmark value must be multiple of PERIOD_BYTES_MIN so set fmark |
235 | * to be PERIOD_BYTES_MIN less than the period size. |
236 | */ |
237 | fmark_val = periodsize - PERIOD_BYTES_MIN; |
238 | |
239 | writel(val: start, addr: audio_io + p_rbuf->baseaddr); |
240 | writel(val: end, addr: audio_io + p_rbuf->endaddr); |
241 | writel(val: fmark_val, addr: audio_io + p_rbuf->fmark); |
242 | writel(val: initial_rd, addr: audio_io + p_rbuf->rdaddr); |
243 | writel(val: initial_wr, addr: audio_io + p_rbuf->wraddr); |
244 | } |
245 | |
246 | static int configure_ringbuf_regs(struct snd_pcm_substream *substream) |
247 | { |
248 | struct cygnus_aio_port *aio; |
249 | struct ringbuf_regs *p_rbuf; |
250 | int status = 0; |
251 | |
252 | aio = cygnus_dai_get_dma_data(substream); |
253 | |
254 | /* Map the ssp portnum to a set of ring buffers. */ |
255 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
256 | p_rbuf = &aio->play_rb_regs; |
257 | |
258 | switch (aio->portnum) { |
259 | case 0: |
260 | *p_rbuf = RINGBUF_REG_PLAYBACK(0); |
261 | break; |
262 | case 1: |
263 | *p_rbuf = RINGBUF_REG_PLAYBACK(2); |
264 | break; |
265 | case 2: |
266 | *p_rbuf = RINGBUF_REG_PLAYBACK(4); |
267 | break; |
268 | case 3: /* SPDIF */ |
269 | *p_rbuf = RINGBUF_REG_PLAYBACK(6); |
270 | break; |
271 | default: |
272 | status = -EINVAL; |
273 | } |
274 | } else { |
275 | p_rbuf = &aio->capture_rb_regs; |
276 | |
277 | switch (aio->portnum) { |
278 | case 0: |
279 | *p_rbuf = RINGBUF_REG_CAPTURE(0); |
280 | break; |
281 | case 1: |
282 | *p_rbuf = RINGBUF_REG_CAPTURE(2); |
283 | break; |
284 | case 2: |
285 | *p_rbuf = RINGBUF_REG_CAPTURE(4); |
286 | break; |
287 | default: |
288 | status = -EINVAL; |
289 | } |
290 | } |
291 | |
292 | return status; |
293 | } |
294 | |
295 | static struct ringbuf_regs *get_ringbuf(struct snd_pcm_substream *substream) |
296 | { |
297 | struct cygnus_aio_port *aio; |
298 | struct ringbuf_regs *p_rbuf = NULL; |
299 | |
300 | aio = cygnus_dai_get_dma_data(substream); |
301 | |
302 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
303 | p_rbuf = &aio->play_rb_regs; |
304 | else |
305 | p_rbuf = &aio->capture_rb_regs; |
306 | |
307 | return p_rbuf; |
308 | } |
309 | |
310 | static void enable_intr(struct snd_pcm_substream *substream) |
311 | { |
312 | struct cygnus_aio_port *aio; |
313 | u32 clear_mask; |
314 | |
315 | aio = cygnus_dai_get_dma_data(substream); |
316 | |
317 | /* The port number maps to the bit position to be cleared */ |
318 | clear_mask = BIT(aio->portnum); |
319 | |
320 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
321 | /* Clear interrupt status before enabling them */ |
322 | writel(val: clear_mask, addr: aio->cygaud->audio + ESR0_STATUS_CLR_OFFSET); |
323 | writel(val: clear_mask, addr: aio->cygaud->audio + ESR1_STATUS_CLR_OFFSET); |
324 | writel(val: clear_mask, addr: aio->cygaud->audio + ESR3_STATUS_CLR_OFFSET); |
325 | /* Unmask the interrupts of the given port*/ |
326 | writel(val: clear_mask, addr: aio->cygaud->audio + ESR0_MASK_CLR_OFFSET); |
327 | writel(val: clear_mask, addr: aio->cygaud->audio + ESR1_MASK_CLR_OFFSET); |
328 | writel(val: clear_mask, addr: aio->cygaud->audio + ESR3_MASK_CLR_OFFSET); |
329 | |
330 | writel(ANY_PLAYBACK_IRQ, |
331 | addr: aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET); |
332 | } else { |
333 | writel(val: clear_mask, addr: aio->cygaud->audio + ESR2_STATUS_CLR_OFFSET); |
334 | writel(val: clear_mask, addr: aio->cygaud->audio + ESR4_STATUS_CLR_OFFSET); |
335 | writel(val: clear_mask, addr: aio->cygaud->audio + ESR2_MASK_CLR_OFFSET); |
336 | writel(val: clear_mask, addr: aio->cygaud->audio + ESR4_MASK_CLR_OFFSET); |
337 | |
338 | writel(ANY_CAPTURE_IRQ, |
339 | addr: aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET); |
340 | } |
341 | |
342 | } |
343 | |
344 | static void disable_intr(struct snd_pcm_substream *substream) |
345 | { |
346 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
347 | struct cygnus_aio_port *aio; |
348 | u32 set_mask; |
349 | |
350 | aio = cygnus_dai_get_dma_data(substream); |
351 | |
352 | dev_dbg(snd_soc_rtd_to_cpu(rtd, 0)->dev, "%s on port %d\n" , __func__, aio->portnum); |
353 | |
354 | /* The port number maps to the bit position to be set */ |
355 | set_mask = BIT(aio->portnum); |
356 | |
357 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
358 | /* Mask the interrupts of the given port*/ |
359 | writel(val: set_mask, addr: aio->cygaud->audio + ESR0_MASK_SET_OFFSET); |
360 | writel(val: set_mask, addr: aio->cygaud->audio + ESR1_MASK_SET_OFFSET); |
361 | writel(val: set_mask, addr: aio->cygaud->audio + ESR3_MASK_SET_OFFSET); |
362 | } else { |
363 | writel(val: set_mask, addr: aio->cygaud->audio + ESR2_MASK_SET_OFFSET); |
364 | writel(val: set_mask, addr: aio->cygaud->audio + ESR4_MASK_SET_OFFSET); |
365 | } |
366 | |
367 | } |
368 | |
369 | static int cygnus_pcm_trigger(struct snd_soc_component *component, |
370 | struct snd_pcm_substream *substream, int cmd) |
371 | { |
372 | int ret = 0; |
373 | |
374 | switch (cmd) { |
375 | case SNDRV_PCM_TRIGGER_START: |
376 | case SNDRV_PCM_TRIGGER_RESUME: |
377 | enable_intr(substream); |
378 | break; |
379 | |
380 | case SNDRV_PCM_TRIGGER_STOP: |
381 | case SNDRV_PCM_TRIGGER_SUSPEND: |
382 | disable_intr(substream); |
383 | break; |
384 | default: |
385 | ret = -EINVAL; |
386 | } |
387 | |
388 | return ret; |
389 | } |
390 | |
391 | static void cygnus_pcm_period_elapsed(struct snd_pcm_substream *substream) |
392 | { |
393 | struct cygnus_aio_port *aio; |
394 | struct ringbuf_regs *p_rbuf = NULL; |
395 | u32 regval; |
396 | |
397 | aio = cygnus_dai_get_dma_data(substream); |
398 | |
399 | p_rbuf = get_ringbuf(substream); |
400 | |
401 | /* |
402 | * If free/full mark interrupt occurs, provide timestamp |
403 | * to ALSA and update appropriate idx by period_bytes |
404 | */ |
405 | snd_pcm_period_elapsed(substream); |
406 | |
407 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
408 | /* Set the ring buffer to full */ |
409 | regval = readl(addr: aio->cygaud->audio + p_rbuf->rdaddr); |
410 | regval = regval ^ BIT(31); |
411 | writel(val: regval, addr: aio->cygaud->audio + p_rbuf->wraddr); |
412 | } else { |
413 | /* Set the ring buffer to empty */ |
414 | regval = readl(addr: aio->cygaud->audio + p_rbuf->wraddr); |
415 | writel(val: regval, addr: aio->cygaud->audio + p_rbuf->rdaddr); |
416 | } |
417 | } |
418 | |
419 | /* |
420 | * ESR0/1/3 status Description |
421 | * 0x1 I2S0_out port caused interrupt |
422 | * 0x2 I2S1_out port caused interrupt |
423 | * 0x4 I2S2_out port caused interrupt |
424 | * 0x8 SPDIF_out port caused interrupt |
425 | */ |
426 | static void handle_playback_irq(struct cygnus_audio *cygaud) |
427 | { |
428 | void __iomem *audio_io; |
429 | u32 port; |
430 | u32 esr_status0, esr_status1, esr_status3; |
431 | |
432 | audio_io = cygaud->audio; |
433 | |
434 | /* |
435 | * ESR status gets updates with/without interrupts enabled. |
436 | * So, check the ESR mask, which provides interrupt enable/ |
437 | * disable status and use it to determine which ESR status |
438 | * should be serviced. |
439 | */ |
440 | esr_status0 = readl(addr: audio_io + ESR0_STATUS_OFFSET); |
441 | esr_status0 &= ~readl(addr: audio_io + ESR0_MASK_STATUS_OFFSET); |
442 | esr_status1 = readl(addr: audio_io + ESR1_STATUS_OFFSET); |
443 | esr_status1 &= ~readl(addr: audio_io + ESR1_MASK_STATUS_OFFSET); |
444 | esr_status3 = readl(addr: audio_io + ESR3_STATUS_OFFSET); |
445 | esr_status3 &= ~readl(addr: audio_io + ESR3_MASK_STATUS_OFFSET); |
446 | |
447 | for (port = 0; port < CYGNUS_MAX_PLAYBACK_PORTS; port++) { |
448 | u32 esrmask = BIT(port); |
449 | |
450 | /* |
451 | * Ringbuffer or FIFO underflow |
452 | * If we get this interrupt then, it is also true that we have |
453 | * not yet responded to the freemark interrupt. |
454 | * Log a debug message. The freemark handler below will |
455 | * handle getting everything going again. |
456 | */ |
457 | if ((esrmask & esr_status1) || (esrmask & esr_status0)) { |
458 | dev_dbg(cygaud->dev, |
459 | "Underrun: esr0=0x%x, esr1=0x%x esr3=0x%x\n" , |
460 | esr_status0, esr_status1, esr_status3); |
461 | } |
462 | |
463 | /* |
464 | * Freemark is hit. This is the normal interrupt. |
465 | * In typical operation the read and write regs will be equal |
466 | */ |
467 | if (esrmask & esr_status3) { |
468 | struct snd_pcm_substream *playstr; |
469 | |
470 | playstr = cygaud->portinfo[port].play_stream; |
471 | cygnus_pcm_period_elapsed(substream: playstr); |
472 | } |
473 | } |
474 | |
475 | /* Clear ESR interrupt */ |
476 | writel(val: esr_status0, addr: audio_io + ESR0_STATUS_CLR_OFFSET); |
477 | writel(val: esr_status1, addr: audio_io + ESR1_STATUS_CLR_OFFSET); |
478 | writel(val: esr_status3, addr: audio_io + ESR3_STATUS_CLR_OFFSET); |
479 | /* Rearm freemark logic by writing 1 to the correct bit */ |
480 | writel(val: esr_status3, addr: audio_io + BF_REARM_FREE_MARK_OFFSET); |
481 | } |
482 | |
483 | /* |
484 | * ESR2/4 status Description |
485 | * 0x1 I2S0_in port caused interrupt |
486 | * 0x2 I2S1_in port caused interrupt |
487 | * 0x4 I2S2_in port caused interrupt |
488 | */ |
489 | static void handle_capture_irq(struct cygnus_audio *cygaud) |
490 | { |
491 | void __iomem *audio_io; |
492 | u32 port; |
493 | u32 esr_status2, esr_status4; |
494 | |
495 | audio_io = cygaud->audio; |
496 | |
497 | /* |
498 | * ESR status gets updates with/without interrupts enabled. |
499 | * So, check the ESR mask, which provides interrupt enable/ |
500 | * disable status and use it to determine which ESR status |
501 | * should be serviced. |
502 | */ |
503 | esr_status2 = readl(addr: audio_io + ESR2_STATUS_OFFSET); |
504 | esr_status2 &= ~readl(addr: audio_io + ESR2_MASK_STATUS_OFFSET); |
505 | esr_status4 = readl(addr: audio_io + ESR4_STATUS_OFFSET); |
506 | esr_status4 &= ~readl(addr: audio_io + ESR4_MASK_STATUS_OFFSET); |
507 | |
508 | for (port = 0; port < CYGNUS_MAX_CAPTURE_PORTS; port++) { |
509 | u32 esrmask = BIT(port); |
510 | |
511 | /* |
512 | * Ringbuffer or FIFO overflow |
513 | * If we get this interrupt then, it is also true that we have |
514 | * not yet responded to the fullmark interrupt. |
515 | * Log a debug message. The fullmark handler below will |
516 | * handle getting everything going again. |
517 | */ |
518 | if (esrmask & esr_status2) |
519 | dev_dbg(cygaud->dev, |
520 | "Overflow: esr2=0x%x\n" , esr_status2); |
521 | |
522 | if (esrmask & esr_status4) { |
523 | struct snd_pcm_substream *capstr; |
524 | |
525 | capstr = cygaud->portinfo[port].capture_stream; |
526 | cygnus_pcm_period_elapsed(substream: capstr); |
527 | } |
528 | } |
529 | |
530 | writel(val: esr_status2, addr: audio_io + ESR2_STATUS_CLR_OFFSET); |
531 | writel(val: esr_status4, addr: audio_io + ESR4_STATUS_CLR_OFFSET); |
532 | /* Rearm fullmark logic by writing 1 to the correct bit */ |
533 | writel(val: esr_status4, addr: audio_io + BF_REARM_FULL_MARK_OFFSET); |
534 | } |
535 | |
536 | static irqreturn_t cygnus_dma_irq(int irq, void *data) |
537 | { |
538 | u32 r5_status; |
539 | struct cygnus_audio *cygaud = data; |
540 | |
541 | /* |
542 | * R5 status bits Description |
543 | * 0 ESR0 (playback FIFO interrupt) |
544 | * 1 ESR1 (playback rbuf interrupt) |
545 | * 2 ESR2 (capture rbuf interrupt) |
546 | * 3 ESR3 (Freemark play. interrupt) |
547 | * 4 ESR4 (Fullmark capt. interrupt) |
548 | */ |
549 | r5_status = readl(addr: cygaud->audio + INTH_R5F_STATUS_OFFSET); |
550 | |
551 | if (!(r5_status & (ANY_PLAYBACK_IRQ | ANY_CAPTURE_IRQ))) |
552 | return IRQ_NONE; |
553 | |
554 | /* If playback interrupt happened */ |
555 | if (ANY_PLAYBACK_IRQ & r5_status) { |
556 | handle_playback_irq(cygaud); |
557 | writel(ANY_PLAYBACK_IRQ & r5_status, |
558 | addr: cygaud->audio + INTH_R5F_CLEAR_OFFSET); |
559 | } |
560 | |
561 | /* If capture interrupt happened */ |
562 | if (ANY_CAPTURE_IRQ & r5_status) { |
563 | handle_capture_irq(cygaud); |
564 | writel(ANY_CAPTURE_IRQ & r5_status, |
565 | addr: cygaud->audio + INTH_R5F_CLEAR_OFFSET); |
566 | } |
567 | |
568 | return IRQ_HANDLED; |
569 | } |
570 | |
571 | static int cygnus_pcm_open(struct snd_soc_component *component, |
572 | struct snd_pcm_substream *substream) |
573 | { |
574 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
575 | struct snd_pcm_runtime *runtime = substream->runtime; |
576 | struct cygnus_aio_port *aio; |
577 | int ret; |
578 | |
579 | aio = cygnus_dai_get_dma_data(substream); |
580 | if (!aio) |
581 | return -ENODEV; |
582 | |
583 | dev_dbg(snd_soc_rtd_to_cpu(rtd, 0)->dev, "%s port %d\n" , __func__, aio->portnum); |
584 | |
585 | snd_soc_set_runtime_hwparams(substream, hw: &cygnus_pcm_hw); |
586 | |
587 | ret = snd_pcm_hw_constraint_step(runtime, cond: 0, |
588 | SNDRV_PCM_HW_PARAM_PERIOD_BYTES, PERIOD_BYTES_MIN); |
589 | if (ret < 0) |
590 | return ret; |
591 | |
592 | ret = snd_pcm_hw_constraint_step(runtime, cond: 0, |
593 | SNDRV_PCM_HW_PARAM_BUFFER_BYTES, PERIOD_BYTES_MIN); |
594 | if (ret < 0) |
595 | return ret; |
596 | /* |
597 | * Keep track of which substream belongs to which port. |
598 | * This info is needed by snd_pcm_period_elapsed() in irq_handler |
599 | */ |
600 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
601 | aio->play_stream = substream; |
602 | else |
603 | aio->capture_stream = substream; |
604 | |
605 | return 0; |
606 | } |
607 | |
608 | static int cygnus_pcm_close(struct snd_soc_component *component, |
609 | struct snd_pcm_substream *substream) |
610 | { |
611 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
612 | struct cygnus_aio_port *aio; |
613 | |
614 | aio = cygnus_dai_get_dma_data(substream); |
615 | |
616 | dev_dbg(snd_soc_rtd_to_cpu(rtd, 0)->dev, "%s port %d\n" , __func__, aio->portnum); |
617 | |
618 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
619 | aio->play_stream = NULL; |
620 | else |
621 | aio->capture_stream = NULL; |
622 | |
623 | if (!aio->play_stream && !aio->capture_stream) |
624 | dev_dbg(snd_soc_rtd_to_cpu(rtd, 0)->dev, "freed port %d\n" , aio->portnum); |
625 | |
626 | return 0; |
627 | } |
628 | |
629 | static int cygnus_pcm_prepare(struct snd_soc_component *component, |
630 | struct snd_pcm_substream *substream) |
631 | { |
632 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
633 | struct snd_pcm_runtime *runtime = substream->runtime; |
634 | struct cygnus_aio_port *aio; |
635 | unsigned long bufsize, periodsize; |
636 | bool is_play; |
637 | u32 start; |
638 | struct ringbuf_regs *p_rbuf = NULL; |
639 | |
640 | aio = cygnus_dai_get_dma_data(substream); |
641 | dev_dbg(snd_soc_rtd_to_cpu(rtd, 0)->dev, "%s port %d\n" , __func__, aio->portnum); |
642 | |
643 | bufsize = snd_pcm_lib_buffer_bytes(substream); |
644 | periodsize = snd_pcm_lib_period_bytes(substream); |
645 | |
646 | dev_dbg(snd_soc_rtd_to_cpu(rtd, 0)->dev, "%s (buf_size %lu) (period_size %lu)\n" , |
647 | __func__, bufsize, periodsize); |
648 | |
649 | configure_ringbuf_regs(substream); |
650 | |
651 | p_rbuf = get_ringbuf(substream); |
652 | |
653 | start = runtime->dma_addr; |
654 | |
655 | is_play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 1 : 0; |
656 | |
657 | ringbuf_set_initial(audio_io: aio->cygaud->audio, p_rbuf, is_playback: is_play, start, |
658 | periodsize, bufsize); |
659 | |
660 | return 0; |
661 | } |
662 | |
663 | static snd_pcm_uframes_t cygnus_pcm_pointer(struct snd_soc_component *component, |
664 | struct snd_pcm_substream *substream) |
665 | { |
666 | struct cygnus_aio_port *aio; |
667 | unsigned int res = 0, cur = 0, base = 0; |
668 | struct ringbuf_regs *p_rbuf = NULL; |
669 | |
670 | aio = cygnus_dai_get_dma_data(substream); |
671 | |
672 | /* |
673 | * Get the offset of the current read (for playack) or write |
674 | * index (for capture). Report this value back to the asoc framework. |
675 | */ |
676 | p_rbuf = get_ringbuf(substream); |
677 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
678 | cur = readl(addr: aio->cygaud->audio + p_rbuf->rdaddr); |
679 | else |
680 | cur = readl(addr: aio->cygaud->audio + p_rbuf->wraddr); |
681 | |
682 | base = readl(addr: aio->cygaud->audio + p_rbuf->baseaddr); |
683 | |
684 | /* |
685 | * Mask off the MSB of the rdaddr,wraddr and baseaddr |
686 | * since MSB is not part of the address |
687 | */ |
688 | res = (cur & 0x7fffffff) - (base & 0x7fffffff); |
689 | |
690 | return bytes_to_frames(runtime: substream->runtime, size: res); |
691 | } |
692 | |
693 | static int cygnus_dma_new(struct snd_soc_component *component, |
694 | struct snd_soc_pcm_runtime *rtd) |
695 | { |
696 | size_t size = cygnus_pcm_hw.buffer_bytes_max; |
697 | struct snd_card *card = rtd->card->snd_card; |
698 | |
699 | if (!card->dev->dma_mask) |
700 | card->dev->dma_mask = &cygnus_dma_dmamask; |
701 | if (!card->dev->coherent_dma_mask) |
702 | card->dev->coherent_dma_mask = DMA_BIT_MASK(32); |
703 | |
704 | snd_pcm_set_managed_buffer_all(pcm: rtd->pcm, SNDRV_DMA_TYPE_DEV, |
705 | data: card->dev, size, max: size); |
706 | |
707 | return 0; |
708 | } |
709 | |
710 | static struct snd_soc_component_driver cygnus_soc_platform = { |
711 | .open = cygnus_pcm_open, |
712 | .close = cygnus_pcm_close, |
713 | .prepare = cygnus_pcm_prepare, |
714 | .trigger = cygnus_pcm_trigger, |
715 | .pointer = cygnus_pcm_pointer, |
716 | .pcm_construct = cygnus_dma_new, |
717 | }; |
718 | |
719 | int cygnus_soc_platform_register(struct device *dev, |
720 | struct cygnus_audio *cygaud) |
721 | { |
722 | int rc; |
723 | |
724 | dev_dbg(dev, "%s Enter\n" , __func__); |
725 | |
726 | rc = devm_request_irq(dev, irq: cygaud->irq_num, handler: cygnus_dma_irq, |
727 | IRQF_SHARED, devname: "cygnus-audio" , dev_id: cygaud); |
728 | if (rc) { |
729 | dev_err(dev, "%s request_irq error %d\n" , __func__, rc); |
730 | return rc; |
731 | } |
732 | |
733 | rc = devm_snd_soc_register_component(dev, component_driver: &cygnus_soc_platform, |
734 | NULL, num_dai: 0); |
735 | if (rc) { |
736 | dev_err(dev, "%s failed\n" , __func__); |
737 | return rc; |
738 | } |
739 | |
740 | return 0; |
741 | } |
742 | |
743 | int cygnus_soc_platform_unregister(struct device *dev) |
744 | { |
745 | return 0; |
746 | } |
747 | |
748 | MODULE_LICENSE("GPL v2" ); |
749 | MODULE_AUTHOR("Broadcom" ); |
750 | MODULE_DESCRIPTION("Cygnus ASoC PCM module" ); |
751 | |