1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // |
3 | // Copyright (C) 2020 Intel Corporation. |
4 | // |
5 | // Intel KeemBay Platform driver. |
6 | // |
7 | |
8 | #include <linux/bitrev.h> |
9 | #include <linux/clk.h> |
10 | #include <linux/dma-mapping.h> |
11 | #include <linux/io.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <sound/dmaengine_pcm.h> |
15 | #include <sound/pcm.h> |
16 | #include <sound/pcm_params.h> |
17 | #include <sound/soc.h> |
18 | #include "kmb_platform.h" |
19 | |
20 | #define PERIODS_MIN 2 |
21 | #define PERIODS_MAX 48 |
22 | #define PERIOD_BYTES_MIN 4096 |
23 | #define BUFFER_BYTES_MAX (PERIODS_MAX * PERIOD_BYTES_MIN) |
24 | #define TDM_OPERATION 5 |
25 | #define I2S_OPERATION 0 |
26 | #define DATA_WIDTH_CONFIG_BIT 6 |
27 | #define TDM_CHANNEL_CONFIG_BIT 3 |
28 | |
29 | static const struct snd_pcm_hardware kmb_pcm_hardware = { |
30 | .info = SNDRV_PCM_INFO_INTERLEAVED | |
31 | SNDRV_PCM_INFO_MMAP | |
32 | SNDRV_PCM_INFO_MMAP_VALID | |
33 | SNDRV_PCM_INFO_BATCH | |
34 | SNDRV_PCM_INFO_BLOCK_TRANSFER, |
35 | .rates = SNDRV_PCM_RATE_8000 | |
36 | SNDRV_PCM_RATE_16000 | |
37 | SNDRV_PCM_RATE_48000, |
38 | .rate_min = 8000, |
39 | .rate_max = 48000, |
40 | .formats = SNDRV_PCM_FMTBIT_S16_LE | |
41 | SNDRV_PCM_FMTBIT_S24_LE | |
42 | SNDRV_PCM_FMTBIT_S32_LE | |
43 | SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, |
44 | .channels_min = 2, |
45 | .channels_max = 2, |
46 | .buffer_bytes_max = BUFFER_BYTES_MAX, |
47 | .period_bytes_min = PERIOD_BYTES_MIN, |
48 | .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, |
49 | .periods_min = PERIODS_MIN, |
50 | .periods_max = PERIODS_MAX, |
51 | .fifo_size = 16, |
52 | }; |
53 | |
54 | /* |
55 | * Convert to ADV7511 HDMI hardware format. |
56 | * ADV7511 HDMI chip need parity bit replaced by block start bit and |
57 | * with the preamble bits left out. |
58 | * ALSA IEC958 subframe format: |
59 | * bit 0-3 = preamble (0x8 = block start) |
60 | * 4-7 = AUX (=0) |
61 | * 8-27 = audio data (without AUX if 24bit sample) |
62 | * 28 = validity |
63 | * 29 = user data |
64 | * 30 = channel status |
65 | * 31 = parity |
66 | * |
67 | * ADV7511 IEC958 subframe format: |
68 | * bit 0-23 = audio data |
69 | * 24 = validity |
70 | * 25 = user data |
71 | * 26 = channel status |
72 | * 27 = block start |
73 | * 28-31 = 0 |
74 | * MSB to LSB bit reverse by software as hardware not supporting it. |
75 | */ |
76 | static void hdmi_reformat_iec958(struct snd_pcm_runtime *runtime, |
77 | struct kmb_i2s_info *kmb_i2s, |
78 | unsigned int tx_ptr) |
79 | { |
80 | u32(*buf)[2] = (void *)runtime->dma_area; |
81 | unsigned long temp; |
82 | u32 i, j, sample; |
83 | |
84 | for (i = 0; i < kmb_i2s->fifo_th; i++) { |
85 | j = 0; |
86 | do { |
87 | temp = buf[tx_ptr][j]; |
88 | /* Replace parity with block start*/ |
89 | assign_bit(nr: 31, addr: &temp, value: (BIT(3) & temp)); |
90 | sample = bitrev32(temp); |
91 | buf[tx_ptr][j] = sample << 4; |
92 | j++; |
93 | } while (j < 2); |
94 | tx_ptr++; |
95 | } |
96 | } |
97 | |
98 | static unsigned int kmb_pcm_tx_fn(struct kmb_i2s_info *kmb_i2s, |
99 | struct snd_pcm_runtime *runtime, |
100 | unsigned int tx_ptr, bool *period_elapsed) |
101 | { |
102 | unsigned int period_pos = tx_ptr % runtime->period_size; |
103 | void __iomem *i2s_base = kmb_i2s->i2s_base; |
104 | void *buf = runtime->dma_area; |
105 | int i; |
106 | |
107 | if (kmb_i2s->iec958_fmt) |
108 | hdmi_reformat_iec958(runtime, kmb_i2s, tx_ptr); |
109 | |
110 | /* KMB i2s uses two separate L/R FIFO */ |
111 | for (i = 0; i < kmb_i2s->fifo_th; i++) { |
112 | if (kmb_i2s->config.data_width == 16) { |
113 | writel(val: ((u16(*)[2])buf)[tx_ptr][0], addr: i2s_base + LRBR_LTHR(0)); |
114 | writel(val: ((u16(*)[2])buf)[tx_ptr][1], addr: i2s_base + RRBR_RTHR(0)); |
115 | } else { |
116 | writel(val: ((u32(*)[2])buf)[tx_ptr][0], addr: i2s_base + LRBR_LTHR(0)); |
117 | writel(val: ((u32(*)[2])buf)[tx_ptr][1], addr: i2s_base + RRBR_RTHR(0)); |
118 | } |
119 | |
120 | period_pos++; |
121 | |
122 | if (++tx_ptr >= runtime->buffer_size) |
123 | tx_ptr = 0; |
124 | } |
125 | |
126 | *period_elapsed = period_pos >= runtime->period_size; |
127 | |
128 | return tx_ptr; |
129 | } |
130 | |
131 | static unsigned int kmb_pcm_rx_fn(struct kmb_i2s_info *kmb_i2s, |
132 | struct snd_pcm_runtime *runtime, |
133 | unsigned int rx_ptr, bool *period_elapsed) |
134 | { |
135 | unsigned int period_pos = rx_ptr % runtime->period_size; |
136 | void __iomem *i2s_base = kmb_i2s->i2s_base; |
137 | int chan = kmb_i2s->config.chan_nr; |
138 | void *buf = runtime->dma_area; |
139 | int i, j; |
140 | |
141 | /* KMB i2s uses two separate L/R FIFO */ |
142 | for (i = 0; i < kmb_i2s->fifo_th; i++) { |
143 | for (j = 0; j < chan / 2; j++) { |
144 | if (kmb_i2s->config.data_width == 16) { |
145 | ((u16 *)buf)[rx_ptr * chan + (j * 2)] = |
146 | readl(addr: i2s_base + LRBR_LTHR(j)); |
147 | ((u16 *)buf)[rx_ptr * chan + ((j * 2) + 1)] = |
148 | readl(addr: i2s_base + RRBR_RTHR(j)); |
149 | } else { |
150 | ((u32 *)buf)[rx_ptr * chan + (j * 2)] = |
151 | readl(addr: i2s_base + LRBR_LTHR(j)); |
152 | ((u32 *)buf)[rx_ptr * chan + ((j * 2) + 1)] = |
153 | readl(addr: i2s_base + RRBR_RTHR(j)); |
154 | } |
155 | } |
156 | period_pos++; |
157 | |
158 | if (++rx_ptr >= runtime->buffer_size) |
159 | rx_ptr = 0; |
160 | } |
161 | |
162 | *period_elapsed = period_pos >= runtime->period_size; |
163 | |
164 | return rx_ptr; |
165 | } |
166 | |
167 | static inline void kmb_i2s_disable_channels(struct kmb_i2s_info *kmb_i2s, |
168 | u32 stream) |
169 | { |
170 | u32 i; |
171 | |
172 | /* Disable all channels regardless of configuration*/ |
173 | if (stream == SNDRV_PCM_STREAM_PLAYBACK) { |
174 | for (i = 0; i < MAX_ISR; i++) |
175 | writel(val: 0, addr: kmb_i2s->i2s_base + TER(i)); |
176 | } else { |
177 | for (i = 0; i < MAX_ISR; i++) |
178 | writel(val: 0, addr: kmb_i2s->i2s_base + RER(i)); |
179 | } |
180 | } |
181 | |
182 | static inline void kmb_i2s_clear_irqs(struct kmb_i2s_info *kmb_i2s, u32 stream) |
183 | { |
184 | struct i2s_clk_config_data *config = &kmb_i2s->config; |
185 | u32 i; |
186 | |
187 | if (stream == SNDRV_PCM_STREAM_PLAYBACK) { |
188 | for (i = 0; i < config->chan_nr / 2; i++) |
189 | readl(addr: kmb_i2s->i2s_base + TOR(i)); |
190 | } else { |
191 | for (i = 0; i < config->chan_nr / 2; i++) |
192 | readl(addr: kmb_i2s->i2s_base + ROR(i)); |
193 | } |
194 | } |
195 | |
196 | static inline void kmb_i2s_irq_trigger(struct kmb_i2s_info *kmb_i2s, |
197 | u32 stream, int chan_nr, bool trigger) |
198 | { |
199 | u32 i, irq; |
200 | u32 flag; |
201 | |
202 | if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
203 | flag = TX_INT_FLAG; |
204 | else |
205 | flag = RX_INT_FLAG; |
206 | |
207 | for (i = 0; i < chan_nr / 2; i++) { |
208 | irq = readl(addr: kmb_i2s->i2s_base + IMR(i)); |
209 | |
210 | if (trigger) |
211 | irq = irq & ~flag; |
212 | else |
213 | irq = irq | flag; |
214 | |
215 | writel(val: irq, addr: kmb_i2s->i2s_base + IMR(i)); |
216 | } |
217 | } |
218 | |
219 | static void kmb_pcm_operation(struct kmb_i2s_info *kmb_i2s, bool playback) |
220 | { |
221 | struct snd_pcm_substream *substream; |
222 | bool period_elapsed; |
223 | unsigned int new_ptr; |
224 | unsigned int ptr; |
225 | |
226 | if (playback) |
227 | substream = kmb_i2s->tx_substream; |
228 | else |
229 | substream = kmb_i2s->rx_substream; |
230 | |
231 | if (!substream || !snd_pcm_running(substream)) |
232 | return; |
233 | |
234 | if (playback) { |
235 | ptr = kmb_i2s->tx_ptr; |
236 | new_ptr = kmb_pcm_tx_fn(kmb_i2s, runtime: substream->runtime, |
237 | tx_ptr: ptr, period_elapsed: &period_elapsed); |
238 | cmpxchg(&kmb_i2s->tx_ptr, ptr, new_ptr); |
239 | } else { |
240 | ptr = kmb_i2s->rx_ptr; |
241 | new_ptr = kmb_pcm_rx_fn(kmb_i2s, runtime: substream->runtime, |
242 | rx_ptr: ptr, period_elapsed: &period_elapsed); |
243 | cmpxchg(&kmb_i2s->rx_ptr, ptr, new_ptr); |
244 | } |
245 | |
246 | if (period_elapsed) |
247 | snd_pcm_period_elapsed(substream); |
248 | } |
249 | |
250 | static int kmb_pcm_open(struct snd_soc_component *component, |
251 | struct snd_pcm_substream *substream) |
252 | { |
253 | struct snd_pcm_runtime *runtime = substream->runtime; |
254 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
255 | struct kmb_i2s_info *kmb_i2s; |
256 | |
257 | kmb_i2s = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); |
258 | snd_soc_set_runtime_hwparams(substream, hw: &kmb_pcm_hardware); |
259 | snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); |
260 | runtime->private_data = kmb_i2s; |
261 | |
262 | return 0; |
263 | } |
264 | |
265 | static int kmb_pcm_trigger(struct snd_soc_component *component, |
266 | struct snd_pcm_substream *substream, int cmd) |
267 | { |
268 | struct snd_pcm_runtime *runtime = substream->runtime; |
269 | struct kmb_i2s_info *kmb_i2s = runtime->private_data; |
270 | |
271 | switch (cmd) { |
272 | case SNDRV_PCM_TRIGGER_START: |
273 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
274 | kmb_i2s->tx_ptr = 0; |
275 | kmb_i2s->tx_substream = substream; |
276 | } else { |
277 | kmb_i2s->rx_ptr = 0; |
278 | kmb_i2s->rx_substream = substream; |
279 | } |
280 | break; |
281 | case SNDRV_PCM_TRIGGER_STOP: |
282 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
283 | kmb_i2s->tx_substream = NULL; |
284 | else |
285 | kmb_i2s->rx_substream = NULL; |
286 | kmb_i2s->iec958_fmt = false; |
287 | break; |
288 | default: |
289 | return -EINVAL; |
290 | } |
291 | |
292 | return 0; |
293 | } |
294 | |
295 | static irqreturn_t kmb_i2s_irq_handler(int irq, void *dev_id) |
296 | { |
297 | struct kmb_i2s_info *kmb_i2s = dev_id; |
298 | struct i2s_clk_config_data *config = &kmb_i2s->config; |
299 | irqreturn_t ret = IRQ_NONE; |
300 | u32 tx_enabled = 0; |
301 | u32 isr[4]; |
302 | int i; |
303 | |
304 | for (i = 0; i < config->chan_nr / 2; i++) |
305 | isr[i] = readl(addr: kmb_i2s->i2s_base + ISR(i)); |
306 | |
307 | kmb_i2s_clear_irqs(kmb_i2s, stream: SNDRV_PCM_STREAM_PLAYBACK); |
308 | kmb_i2s_clear_irqs(kmb_i2s, stream: SNDRV_PCM_STREAM_CAPTURE); |
309 | /* Only check TX interrupt if TX is active */ |
310 | tx_enabled = readl(addr: kmb_i2s->i2s_base + ITER); |
311 | |
312 | /* |
313 | * Data available. Retrieve samples from FIFO |
314 | */ |
315 | |
316 | /* |
317 | * 8 channel audio will have isr[0..2] triggered, |
318 | * reading the specific isr based on the audio configuration, |
319 | * to avoid reading the buffers too early. |
320 | */ |
321 | switch (config->chan_nr) { |
322 | case 2: |
323 | if (isr[0] & ISR_RXDA) |
324 | kmb_pcm_operation(kmb_i2s, playback: false); |
325 | ret = IRQ_HANDLED; |
326 | break; |
327 | case 4: |
328 | if (isr[1] & ISR_RXDA) |
329 | kmb_pcm_operation(kmb_i2s, playback: false); |
330 | ret = IRQ_HANDLED; |
331 | break; |
332 | case 8: |
333 | if (isr[3] & ISR_RXDA) |
334 | kmb_pcm_operation(kmb_i2s, playback: false); |
335 | ret = IRQ_HANDLED; |
336 | break; |
337 | } |
338 | |
339 | for (i = 0; i < config->chan_nr / 2; i++) { |
340 | /* |
341 | * Check if TX fifo is empty. If empty fill FIFO with samples |
342 | */ |
343 | if ((isr[i] & ISR_TXFE) && tx_enabled) { |
344 | kmb_pcm_operation(kmb_i2s, playback: true); |
345 | ret = IRQ_HANDLED; |
346 | } |
347 | |
348 | /* Error Handling: TX */ |
349 | if (isr[i] & ISR_TXFO) { |
350 | dev_dbg(kmb_i2s->dev, "TX overrun (ch_id=%d)\n" , i); |
351 | ret = IRQ_HANDLED; |
352 | } |
353 | /* Error Handling: RX */ |
354 | if (isr[i] & ISR_RXFO) { |
355 | dev_dbg(kmb_i2s->dev, "RX overrun (ch_id=%d)\n" , i); |
356 | ret = IRQ_HANDLED; |
357 | } |
358 | } |
359 | |
360 | return ret; |
361 | } |
362 | |
363 | static int kmb_platform_pcm_new(struct snd_soc_component *component, |
364 | struct snd_soc_pcm_runtime *soc_runtime) |
365 | { |
366 | size_t size = kmb_pcm_hardware.buffer_bytes_max; |
367 | /* Use SNDRV_DMA_TYPE_CONTINUOUS as KMB doesn't use PCI sg buffer */ |
368 | snd_pcm_set_managed_buffer_all(pcm: soc_runtime->pcm, |
369 | SNDRV_DMA_TYPE_CONTINUOUS, |
370 | NULL, size, max: size); |
371 | return 0; |
372 | } |
373 | |
374 | static snd_pcm_uframes_t kmb_pcm_pointer(struct snd_soc_component *component, |
375 | struct snd_pcm_substream *substream) |
376 | { |
377 | struct snd_pcm_runtime *runtime = substream->runtime; |
378 | struct kmb_i2s_info *kmb_i2s = runtime->private_data; |
379 | snd_pcm_uframes_t pos; |
380 | |
381 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
382 | pos = kmb_i2s->tx_ptr; |
383 | else |
384 | pos = kmb_i2s->rx_ptr; |
385 | |
386 | return pos < runtime->buffer_size ? pos : 0; |
387 | } |
388 | |
389 | static const struct snd_soc_component_driver kmb_component = { |
390 | .name = "kmb" , |
391 | .pcm_construct = kmb_platform_pcm_new, |
392 | .open = kmb_pcm_open, |
393 | .trigger = kmb_pcm_trigger, |
394 | .pointer = kmb_pcm_pointer, |
395 | .legacy_dai_naming = 1, |
396 | }; |
397 | |
398 | static const struct snd_soc_component_driver kmb_component_dma = { |
399 | .name = "kmb" , |
400 | .legacy_dai_naming = 1, |
401 | }; |
402 | |
403 | static int kmb_probe(struct snd_soc_dai *cpu_dai) |
404 | { |
405 | struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(dai: cpu_dai); |
406 | |
407 | if (kmb_i2s->use_pio) |
408 | return 0; |
409 | |
410 | snd_soc_dai_init_dma_data(dai: cpu_dai, playback: &kmb_i2s->play_dma_data, |
411 | capture: &kmb_i2s->capture_dma_data); |
412 | |
413 | return 0; |
414 | } |
415 | |
416 | static inline void kmb_i2s_enable_dma(struct kmb_i2s_info *kmb_i2s, u32 stream) |
417 | { |
418 | u32 dma_reg; |
419 | |
420 | dma_reg = readl(addr: kmb_i2s->i2s_base + I2S_DMACR); |
421 | /* Enable DMA handshake for stream */ |
422 | if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
423 | dma_reg |= I2S_DMAEN_TXBLOCK; |
424 | else |
425 | dma_reg |= I2S_DMAEN_RXBLOCK; |
426 | |
427 | writel(val: dma_reg, addr: kmb_i2s->i2s_base + I2S_DMACR); |
428 | } |
429 | |
430 | static inline void kmb_i2s_disable_dma(struct kmb_i2s_info *kmb_i2s, u32 stream) |
431 | { |
432 | u32 dma_reg; |
433 | |
434 | dma_reg = readl(addr: kmb_i2s->i2s_base + I2S_DMACR); |
435 | /* Disable DMA handshake for stream */ |
436 | if (stream == SNDRV_PCM_STREAM_PLAYBACK) { |
437 | dma_reg &= ~I2S_DMAEN_TXBLOCK; |
438 | writel(val: 1, addr: kmb_i2s->i2s_base + I2S_RTXDMA); |
439 | } else { |
440 | dma_reg &= ~I2S_DMAEN_RXBLOCK; |
441 | writel(val: 1, addr: kmb_i2s->i2s_base + I2S_RRXDMA); |
442 | } |
443 | writel(val: dma_reg, addr: kmb_i2s->i2s_base + I2S_DMACR); |
444 | } |
445 | |
446 | static void kmb_i2s_start(struct kmb_i2s_info *kmb_i2s, |
447 | struct snd_pcm_substream *substream) |
448 | { |
449 | struct i2s_clk_config_data *config = &kmb_i2s->config; |
450 | |
451 | /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */ |
452 | writel(val: 1, addr: kmb_i2s->i2s_base + IER); |
453 | |
454 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
455 | writel(val: 1, addr: kmb_i2s->i2s_base + ITER); |
456 | else |
457 | writel(val: 1, addr: kmb_i2s->i2s_base + IRER); |
458 | |
459 | if (kmb_i2s->use_pio) |
460 | kmb_i2s_irq_trigger(kmb_i2s, stream: substream->stream, |
461 | chan_nr: config->chan_nr, trigger: true); |
462 | else |
463 | kmb_i2s_enable_dma(kmb_i2s, stream: substream->stream); |
464 | |
465 | if (kmb_i2s->clock_provider) |
466 | writel(val: 1, addr: kmb_i2s->i2s_base + CER); |
467 | else |
468 | writel(val: 0, addr: kmb_i2s->i2s_base + CER); |
469 | } |
470 | |
471 | static void kmb_i2s_stop(struct kmb_i2s_info *kmb_i2s, |
472 | struct snd_pcm_substream *substream) |
473 | { |
474 | /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */ |
475 | kmb_i2s_clear_irqs(kmb_i2s, stream: substream->stream); |
476 | |
477 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
478 | writel(val: 0, addr: kmb_i2s->i2s_base + ITER); |
479 | else |
480 | writel(val: 0, addr: kmb_i2s->i2s_base + IRER); |
481 | |
482 | kmb_i2s_irq_trigger(kmb_i2s, stream: substream->stream, chan_nr: 8, trigger: false); |
483 | |
484 | if (!kmb_i2s->active) { |
485 | writel(val: 0, addr: kmb_i2s->i2s_base + CER); |
486 | writel(val: 0, addr: kmb_i2s->i2s_base + IER); |
487 | } |
488 | } |
489 | |
490 | static void kmb_disable_clk(void *clk) |
491 | { |
492 | clk_disable_unprepare(clk); |
493 | } |
494 | |
495 | static int kmb_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) |
496 | { |
497 | struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(dai: cpu_dai); |
498 | int ret; |
499 | |
500 | switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
501 | case SND_SOC_DAIFMT_BC_FC: |
502 | kmb_i2s->clock_provider = false; |
503 | ret = 0; |
504 | break; |
505 | case SND_SOC_DAIFMT_BP_FP: |
506 | writel(CLOCK_PROVIDER_MODE, addr: kmb_i2s->pss_base + I2S_GEN_CFG_0); |
507 | |
508 | ret = clk_prepare_enable(clk: kmb_i2s->clk_i2s); |
509 | if (ret < 0) |
510 | return ret; |
511 | |
512 | ret = devm_add_action_or_reset(kmb_i2s->dev, kmb_disable_clk, |
513 | kmb_i2s->clk_i2s); |
514 | if (ret) |
515 | return ret; |
516 | |
517 | kmb_i2s->clock_provider = true; |
518 | break; |
519 | default: |
520 | return -EINVAL; |
521 | } |
522 | |
523 | return ret; |
524 | } |
525 | |
526 | static int kmb_dai_trigger(struct snd_pcm_substream *substream, |
527 | int cmd, struct snd_soc_dai *cpu_dai) |
528 | { |
529 | struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(dai: cpu_dai); |
530 | |
531 | switch (cmd) { |
532 | case SNDRV_PCM_TRIGGER_START: |
533 | /* Keep track of i2s activity before turn off |
534 | * the i2s interface |
535 | */ |
536 | kmb_i2s->active++; |
537 | kmb_i2s_start(kmb_i2s, substream); |
538 | break; |
539 | case SNDRV_PCM_TRIGGER_STOP: |
540 | kmb_i2s->active--; |
541 | if (kmb_i2s->use_pio) |
542 | kmb_i2s_stop(kmb_i2s, substream); |
543 | break; |
544 | default: |
545 | return -EINVAL; |
546 | } |
547 | |
548 | return 0; |
549 | } |
550 | |
551 | static void kmb_i2s_config(struct kmb_i2s_info *kmb_i2s, int stream) |
552 | { |
553 | struct i2s_clk_config_data *config = &kmb_i2s->config; |
554 | u32 ch_reg; |
555 | |
556 | kmb_i2s_disable_channels(kmb_i2s, stream); |
557 | |
558 | for (ch_reg = 0; ch_reg < config->chan_nr / 2; ch_reg++) { |
559 | if (stream == SNDRV_PCM_STREAM_PLAYBACK) { |
560 | writel(val: kmb_i2s->xfer_resolution, |
561 | addr: kmb_i2s->i2s_base + TCR(ch_reg)); |
562 | |
563 | writel(val: kmb_i2s->fifo_th - 1, |
564 | addr: kmb_i2s->i2s_base + TFCR(ch_reg)); |
565 | |
566 | writel(val: 1, addr: kmb_i2s->i2s_base + TER(ch_reg)); |
567 | } else { |
568 | writel(val: kmb_i2s->xfer_resolution, |
569 | addr: kmb_i2s->i2s_base + RCR(ch_reg)); |
570 | |
571 | writel(val: kmb_i2s->fifo_th - 1, |
572 | addr: kmb_i2s->i2s_base + RFCR(ch_reg)); |
573 | |
574 | writel(val: 1, addr: kmb_i2s->i2s_base + RER(ch_reg)); |
575 | } |
576 | } |
577 | } |
578 | |
579 | static int kmb_dai_hw_params(struct snd_pcm_substream *substream, |
580 | struct snd_pcm_hw_params *hw_params, |
581 | struct snd_soc_dai *cpu_dai) |
582 | { |
583 | struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(dai: cpu_dai); |
584 | struct i2s_clk_config_data *config = &kmb_i2s->config; |
585 | u32 write_val; |
586 | int ret; |
587 | |
588 | switch (params_format(p: hw_params)) { |
589 | case SNDRV_PCM_FORMAT_S16_LE: |
590 | config->data_width = 16; |
591 | kmb_i2s->ccr = 0x00; |
592 | kmb_i2s->xfer_resolution = 0x02; |
593 | kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; |
594 | kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; |
595 | break; |
596 | case SNDRV_PCM_FORMAT_S24_LE: |
597 | config->data_width = 32; |
598 | kmb_i2s->ccr = 0x14; |
599 | kmb_i2s->xfer_resolution = 0x05; |
600 | kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
601 | kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
602 | break; |
603 | case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: |
604 | kmb_i2s->iec958_fmt = true; |
605 | fallthrough; |
606 | case SNDRV_PCM_FORMAT_S32_LE: |
607 | config->data_width = 32; |
608 | kmb_i2s->ccr = 0x10; |
609 | kmb_i2s->xfer_resolution = 0x05; |
610 | kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
611 | kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
612 | break; |
613 | default: |
614 | dev_err(kmb_i2s->dev, "kmb: unsupported PCM fmt" ); |
615 | return -EINVAL; |
616 | } |
617 | |
618 | config->chan_nr = params_channels(p: hw_params); |
619 | |
620 | switch (config->chan_nr) { |
621 | case 8: |
622 | case 4: |
623 | /* |
624 | * Platform is not capable of providing clocks for |
625 | * multi channel audio |
626 | */ |
627 | if (kmb_i2s->clock_provider) |
628 | return -EINVAL; |
629 | |
630 | write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) | |
631 | (config->data_width << DATA_WIDTH_CONFIG_BIT) | |
632 | TDM_OPERATION; |
633 | |
634 | writel(val: write_val, addr: kmb_i2s->pss_base + I2S_GEN_CFG_0); |
635 | break; |
636 | case 2: |
637 | /* |
638 | * Platform is only capable of providing clocks need for |
639 | * 2 channel master mode |
640 | */ |
641 | if (!(kmb_i2s->clock_provider)) |
642 | return -EINVAL; |
643 | |
644 | write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) | |
645 | (config->data_width << DATA_WIDTH_CONFIG_BIT) | |
646 | CLOCK_PROVIDER_MODE | I2S_OPERATION; |
647 | |
648 | writel(val: write_val, addr: kmb_i2s->pss_base + I2S_GEN_CFG_0); |
649 | break; |
650 | default: |
651 | dev_dbg(kmb_i2s->dev, "channel not supported\n" ); |
652 | return -EINVAL; |
653 | } |
654 | |
655 | kmb_i2s_config(kmb_i2s, stream: substream->stream); |
656 | |
657 | writel(val: kmb_i2s->ccr, addr: kmb_i2s->i2s_base + CCR); |
658 | |
659 | config->sample_rate = params_rate(p: hw_params); |
660 | |
661 | if (kmb_i2s->clock_provider) { |
662 | /* Only 2 ch supported in Master mode */ |
663 | u32 bitclk = config->sample_rate * config->data_width * 2; |
664 | |
665 | ret = clk_set_rate(clk: kmb_i2s->clk_i2s, rate: bitclk); |
666 | if (ret) { |
667 | dev_err(kmb_i2s->dev, |
668 | "Can't set I2S clock rate: %d\n" , ret); |
669 | return ret; |
670 | } |
671 | } |
672 | |
673 | return 0; |
674 | } |
675 | |
676 | static int kmb_dai_prepare(struct snd_pcm_substream *substream, |
677 | struct snd_soc_dai *cpu_dai) |
678 | { |
679 | struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(dai: cpu_dai); |
680 | |
681 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
682 | writel(val: 1, addr: kmb_i2s->i2s_base + TXFFR); |
683 | else |
684 | writel(val: 1, addr: kmb_i2s->i2s_base + RXFFR); |
685 | |
686 | return 0; |
687 | } |
688 | |
689 | static int kmb_dai_startup(struct snd_pcm_substream *substream, |
690 | struct snd_soc_dai *cpu_dai) |
691 | { |
692 | struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(dai: cpu_dai); |
693 | struct snd_dmaengine_dai_dma_data *dma_data; |
694 | |
695 | if (kmb_i2s->use_pio) |
696 | return 0; |
697 | |
698 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
699 | dma_data = &kmb_i2s->play_dma_data; |
700 | else |
701 | dma_data = &kmb_i2s->capture_dma_data; |
702 | |
703 | snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); |
704 | |
705 | return 0; |
706 | } |
707 | |
708 | static int kmb_dai_hw_free(struct snd_pcm_substream *substream, |
709 | struct snd_soc_dai *cpu_dai) |
710 | { |
711 | struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(dai: cpu_dai); |
712 | /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */ |
713 | if (kmb_i2s->use_pio) |
714 | kmb_i2s_clear_irqs(kmb_i2s, stream: substream->stream); |
715 | |
716 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
717 | writel(val: 0, addr: kmb_i2s->i2s_base + ITER); |
718 | else |
719 | writel(val: 0, addr: kmb_i2s->i2s_base + IRER); |
720 | |
721 | if (kmb_i2s->use_pio) |
722 | kmb_i2s_irq_trigger(kmb_i2s, stream: substream->stream, chan_nr: 8, trigger: false); |
723 | else |
724 | kmb_i2s_disable_dma(kmb_i2s, stream: substream->stream); |
725 | |
726 | if (!kmb_i2s->active) { |
727 | writel(val: 0, addr: kmb_i2s->i2s_base + CER); |
728 | writel(val: 0, addr: kmb_i2s->i2s_base + IER); |
729 | } |
730 | |
731 | return 0; |
732 | } |
733 | |
734 | static const struct snd_soc_dai_ops kmb_dai_ops = { |
735 | .probe = kmb_probe, |
736 | .startup = kmb_dai_startup, |
737 | .trigger = kmb_dai_trigger, |
738 | .hw_params = kmb_dai_hw_params, |
739 | .hw_free = kmb_dai_hw_free, |
740 | .prepare = kmb_dai_prepare, |
741 | .set_fmt = kmb_set_dai_fmt, |
742 | }; |
743 | |
744 | static struct snd_soc_dai_driver intel_kmb_hdmi_dai[] = { |
745 | { |
746 | .name = "intel_kmb_hdmi_i2s" , |
747 | .playback = { |
748 | .channels_min = 2, |
749 | .channels_max = 2, |
750 | .rates = SNDRV_PCM_RATE_48000, |
751 | .rate_min = 48000, |
752 | .rate_max = 48000, |
753 | .formats = (SNDRV_PCM_FMTBIT_S16_LE | |
754 | SNDRV_PCM_FMTBIT_S24_LE | |
755 | SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE), |
756 | }, |
757 | .ops = &kmb_dai_ops, |
758 | }, |
759 | }; |
760 | |
761 | static struct snd_soc_dai_driver intel_kmb_i2s_dai[] = { |
762 | { |
763 | .name = "intel_kmb_i2s" , |
764 | .playback = { |
765 | .channels_min = 2, |
766 | .channels_max = 2, |
767 | .rates = SNDRV_PCM_RATE_8000 | |
768 | SNDRV_PCM_RATE_16000 | |
769 | SNDRV_PCM_RATE_48000, |
770 | .rate_min = 8000, |
771 | .rate_max = 48000, |
772 | .formats = (SNDRV_PCM_FMTBIT_S32_LE | |
773 | SNDRV_PCM_FMTBIT_S24_LE | |
774 | SNDRV_PCM_FMTBIT_S16_LE), |
775 | }, |
776 | .capture = { |
777 | .channels_min = 2, |
778 | .channels_max = 2, |
779 | .rates = SNDRV_PCM_RATE_8000 | |
780 | SNDRV_PCM_RATE_16000 | |
781 | SNDRV_PCM_RATE_48000, |
782 | .rate_min = 8000, |
783 | .rate_max = 48000, |
784 | .formats = (SNDRV_PCM_FMTBIT_S32_LE | |
785 | SNDRV_PCM_FMTBIT_S24_LE | |
786 | SNDRV_PCM_FMTBIT_S16_LE), |
787 | }, |
788 | .ops = &kmb_dai_ops, |
789 | }, |
790 | }; |
791 | |
792 | static struct snd_soc_dai_driver intel_kmb_tdm_dai[] = { |
793 | { |
794 | .name = "intel_kmb_tdm" , |
795 | .capture = { |
796 | .channels_min = 4, |
797 | .channels_max = 8, |
798 | .rates = SNDRV_PCM_RATE_8000 | |
799 | SNDRV_PCM_RATE_16000 | |
800 | SNDRV_PCM_RATE_48000, |
801 | .rate_min = 8000, |
802 | .rate_max = 48000, |
803 | .formats = (SNDRV_PCM_FMTBIT_S32_LE | |
804 | SNDRV_PCM_FMTBIT_S24_LE | |
805 | SNDRV_PCM_FMTBIT_S16_LE), |
806 | }, |
807 | .ops = &kmb_dai_ops, |
808 | }, |
809 | }; |
810 | |
811 | static const struct of_device_id kmb_plat_of_match[] = { |
812 | { .compatible = "intel,keembay-i2s" , .data = &intel_kmb_i2s_dai}, |
813 | { .compatible = "intel,keembay-hdmi-i2s" , .data = &intel_kmb_hdmi_dai}, |
814 | { .compatible = "intel,keembay-tdm" , .data = &intel_kmb_tdm_dai}, |
815 | {} |
816 | }; |
817 | |
818 | static int kmb_plat_dai_probe(struct platform_device *pdev) |
819 | { |
820 | struct device_node *np = pdev->dev.of_node; |
821 | struct snd_soc_dai_driver *kmb_i2s_dai; |
822 | struct device *dev = &pdev->dev; |
823 | struct kmb_i2s_info *kmb_i2s; |
824 | struct resource *res; |
825 | int ret, irq; |
826 | u32 comp1_reg; |
827 | |
828 | kmb_i2s = devm_kzalloc(dev, size: sizeof(*kmb_i2s), GFP_KERNEL); |
829 | if (!kmb_i2s) |
830 | return -ENOMEM; |
831 | |
832 | kmb_i2s_dai = (struct snd_soc_dai_driver *)device_get_match_data(dev: &pdev->dev); |
833 | |
834 | /* Prepare the related clocks */ |
835 | kmb_i2s->clk_apb = devm_clk_get(dev, id: "apb_clk" ); |
836 | if (IS_ERR(ptr: kmb_i2s->clk_apb)) { |
837 | dev_err(dev, "Failed to get apb clock\n" ); |
838 | return PTR_ERR(ptr: kmb_i2s->clk_apb); |
839 | } |
840 | |
841 | ret = clk_prepare_enable(clk: kmb_i2s->clk_apb); |
842 | if (ret < 0) |
843 | return ret; |
844 | |
845 | ret = devm_add_action_or_reset(dev, kmb_disable_clk, kmb_i2s->clk_apb); |
846 | if (ret) { |
847 | dev_err(dev, "Failed to add clk_apb reset action\n" ); |
848 | return ret; |
849 | } |
850 | |
851 | kmb_i2s->clk_i2s = devm_clk_get(dev, id: "osc" ); |
852 | if (IS_ERR(ptr: kmb_i2s->clk_i2s)) { |
853 | dev_err(dev, "Failed to get osc clock\n" ); |
854 | return PTR_ERR(ptr: kmb_i2s->clk_i2s); |
855 | } |
856 | |
857 | kmb_i2s->i2s_base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
858 | if (IS_ERR(ptr: kmb_i2s->i2s_base)) |
859 | return PTR_ERR(ptr: kmb_i2s->i2s_base); |
860 | |
861 | kmb_i2s->pss_base = devm_platform_ioremap_resource(pdev, index: 1); |
862 | if (IS_ERR(ptr: kmb_i2s->pss_base)) |
863 | return PTR_ERR(ptr: kmb_i2s->pss_base); |
864 | |
865 | kmb_i2s->dev = &pdev->dev; |
866 | |
867 | comp1_reg = readl(addr: kmb_i2s->i2s_base + I2S_COMP_PARAM_1); |
868 | |
869 | kmb_i2s->fifo_th = (1 << COMP1_FIFO_DEPTH(comp1_reg)) / 2; |
870 | |
871 | kmb_i2s->use_pio = !(of_property_read_bool(np, propname: "dmas" )); |
872 | |
873 | if (kmb_i2s->use_pio) { |
874 | irq = platform_get_irq_optional(pdev, 0); |
875 | if (irq > 0) { |
876 | ret = devm_request_irq(dev, irq, handler: kmb_i2s_irq_handler, irqflags: 0, |
877 | devname: pdev->name, dev_id: kmb_i2s); |
878 | if (ret < 0) { |
879 | dev_err(dev, "failed to request irq\n" ); |
880 | return ret; |
881 | } |
882 | } |
883 | ret = devm_snd_soc_register_component(dev, component_driver: &kmb_component, |
884 | dai_drv: kmb_i2s_dai, num_dai: 1); |
885 | } else { |
886 | kmb_i2s->play_dma_data.addr = res->start + I2S_TXDMA; |
887 | kmb_i2s->capture_dma_data.addr = res->start + I2S_RXDMA; |
888 | ret = snd_dmaengine_pcm_register(dev: &pdev->dev, |
889 | NULL, flags: 0); |
890 | if (ret) { |
891 | dev_err(&pdev->dev, "could not register dmaengine: %d\n" , |
892 | ret); |
893 | return ret; |
894 | } |
895 | ret = devm_snd_soc_register_component(dev, component_driver: &kmb_component_dma, |
896 | dai_drv: kmb_i2s_dai, num_dai: 1); |
897 | } |
898 | |
899 | if (ret) { |
900 | dev_err(dev, "not able to register dai\n" ); |
901 | return ret; |
902 | } |
903 | |
904 | /* To ensure none of the channels are enabled at boot up */ |
905 | kmb_i2s_disable_channels(kmb_i2s, stream: SNDRV_PCM_STREAM_PLAYBACK); |
906 | kmb_i2s_disable_channels(kmb_i2s, stream: SNDRV_PCM_STREAM_CAPTURE); |
907 | |
908 | dev_set_drvdata(dev, data: kmb_i2s); |
909 | |
910 | return ret; |
911 | } |
912 | |
913 | static struct platform_driver kmb_plat_dai_driver = { |
914 | .driver = { |
915 | .name = "kmb-plat-dai" , |
916 | .of_match_table = kmb_plat_of_match, |
917 | }, |
918 | .probe = kmb_plat_dai_probe, |
919 | }; |
920 | |
921 | module_platform_driver(kmb_plat_dai_driver); |
922 | |
923 | MODULE_DESCRIPTION("ASoC Intel KeemBay Platform driver" ); |
924 | MODULE_AUTHOR("Sia Jee Heng <jee.heng.sia@intel.com>" ); |
925 | MODULE_AUTHOR("Sit, Michael Wei Hong <michael.wei.hong.sit@intel.com>" ); |
926 | MODULE_LICENSE("GPL v2" ); |
927 | MODULE_ALIAS("platform:kmb_platform" ); |
928 | |