]>
Commit | Line | Data |
---|---|---|
d5a932e5 VM |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // | |
3 | // AMD ALSA SoC PCM Driver | |
4 | // | |
5 | //Copyright 2016 Advanced Micro Devices, Inc. | |
ac289c7e VM |
6 | |
7 | #include <linux/platform_device.h> | |
8 | #include <linux/module.h> | |
9 | #include <linux/err.h> | |
10 | #include <linux/io.h> | |
56e4dd8f | 11 | #include <linux/pm_runtime.h> |
ac289c7e VM |
12 | #include <sound/pcm.h> |
13 | #include <sound/pcm_params.h> | |
14 | #include <sound/soc.h> | |
15 | #include <sound/soc-dai.h> | |
16 | ||
17 | #include "acp3x.h" | |
18 | ||
19 | #define DRV_NAME "acp3x-i2s-audio" | |
20 | ||
21 | struct i2s_dev_data { | |
67aa06ae | 22 | bool tdm_mode; |
32feac95 | 23 | unsigned int i2s_irq; |
67aa06ae | 24 | u32 tdm_fmt; |
ac289c7e VM |
25 | void __iomem *acp3x_base; |
26 | struct snd_pcm_substream *play_stream; | |
27 | struct snd_pcm_substream *capture_stream; | |
28 | }; | |
29 | ||
0b87d6bc VM |
30 | struct i2s_stream_instance { |
31 | u16 num_pages; | |
32 | u16 channels; | |
33 | u32 xfer_resolution; | |
34 | struct page *pg; | |
35 | void __iomem *acp3x_base; | |
36 | }; | |
37 | ||
38 | static const struct snd_pcm_hardware acp3x_pcm_hardware_playback = { | |
39 | .info = SNDRV_PCM_INFO_INTERLEAVED | | |
40 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | |
41 | SNDRV_PCM_INFO_BATCH | | |
42 | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, | |
43 | .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | | |
44 | SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | | |
45 | SNDRV_PCM_FMTBIT_S32_LE, | |
46 | .channels_min = 2, | |
47 | .channels_max = 8, | |
48 | .rates = SNDRV_PCM_RATE_8000_96000, | |
49 | .rate_min = 8000, | |
50 | .rate_max = 96000, | |
51 | .buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE, | |
52 | .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, | |
53 | .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, | |
54 | .periods_min = PLAYBACK_MIN_NUM_PERIODS, | |
55 | .periods_max = PLAYBACK_MAX_NUM_PERIODS, | |
56 | }; | |
57 | ||
58 | static const struct snd_pcm_hardware acp3x_pcm_hardware_capture = { | |
59 | .info = SNDRV_PCM_INFO_INTERLEAVED | | |
60 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | |
61 | SNDRV_PCM_INFO_BATCH | | |
62 | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, | |
63 | .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | | |
64 | SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | | |
65 | SNDRV_PCM_FMTBIT_S32_LE, | |
66 | .channels_min = 2, | |
67 | .channels_max = 2, | |
68 | .rates = SNDRV_PCM_RATE_8000_48000, | |
69 | .rate_min = 8000, | |
70 | .rate_max = 48000, | |
71 | .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE, | |
72 | .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, | |
73 | .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, | |
74 | .periods_min = CAPTURE_MIN_NUM_PERIODS, | |
75 | .periods_max = CAPTURE_MAX_NUM_PERIODS, | |
76 | }; | |
77 | ||
ac289c7e VM |
78 | static int acp3x_power_on(void __iomem *acp3x_base, bool on) |
79 | { | |
80 | u16 val, mask; | |
81 | u32 timeout; | |
82 | ||
83 | if (on == true) { | |
84 | val = 1; | |
85 | mask = ACP3x_POWER_ON; | |
86 | } else { | |
87 | val = 0; | |
88 | mask = ACP3x_POWER_OFF; | |
89 | } | |
90 | ||
91 | rv_writel(val, acp3x_base + mmACP_PGFSM_CONTROL); | |
92 | timeout = 0; | |
93 | while (true) { | |
94 | val = rv_readl(acp3x_base + mmACP_PGFSM_STATUS); | |
95 | if ((val & ACP3x_POWER_OFF_IN_PROGRESS) == mask) | |
96 | break; | |
97 | if (timeout > 100) { | |
98 | pr_err("ACP3x power state change failure\n"); | |
99 | return -ENODEV; | |
100 | } | |
101 | timeout++; | |
102 | cpu_relax(); | |
103 | } | |
104 | return 0; | |
105 | } | |
106 | ||
107 | static int acp3x_reset(void __iomem *acp3x_base) | |
108 | { | |
109 | u32 val, timeout; | |
110 | ||
111 | rv_writel(1, acp3x_base + mmACP_SOFT_RESET); | |
112 | timeout = 0; | |
113 | while (true) { | |
114 | val = rv_readl(acp3x_base + mmACP_SOFT_RESET); | |
115 | if ((val & ACP3x_SOFT_RESET__SoftResetAudDone_MASK) || | |
116 | timeout > 100) { | |
117 | if (val & ACP3x_SOFT_RESET__SoftResetAudDone_MASK) | |
118 | break; | |
119 | return -ENODEV; | |
120 | } | |
121 | timeout++; | |
122 | cpu_relax(); | |
123 | } | |
124 | ||
125 | rv_writel(0, acp3x_base + mmACP_SOFT_RESET); | |
126 | timeout = 0; | |
127 | while (true) { | |
128 | val = rv_readl(acp3x_base + mmACP_SOFT_RESET); | |
129 | if (!val || timeout > 100) { | |
130 | if (!val) | |
131 | break; | |
132 | return -ENODEV; | |
133 | } | |
134 | timeout++; | |
135 | cpu_relax(); | |
136 | } | |
137 | return 0; | |
138 | } | |
139 | ||
140 | static int acp3x_init(void __iomem *acp3x_base) | |
141 | { | |
142 | int ret; | |
143 | ||
144 | /* power on */ | |
145 | ret = acp3x_power_on(acp3x_base, true); | |
146 | if (ret) { | |
147 | pr_err("ACP3x power on failed\n"); | |
148 | return ret; | |
149 | } | |
150 | /* Reset */ | |
151 | ret = acp3x_reset(acp3x_base); | |
152 | if (ret) { | |
153 | pr_err("ACP3x reset failed\n"); | |
154 | return ret; | |
155 | } | |
156 | return 0; | |
157 | } | |
158 | ||
159 | static int acp3x_deinit(void __iomem *acp3x_base) | |
160 | { | |
161 | int ret; | |
162 | ||
163 | /* Reset */ | |
164 | ret = acp3x_reset(acp3x_base); | |
165 | if (ret) { | |
166 | pr_err("ACP3x reset failed\n"); | |
167 | return ret; | |
168 | } | |
169 | /* power off */ | |
170 | ret = acp3x_power_on(acp3x_base, false); | |
171 | if (ret) { | |
172 | pr_err("ACP3x power off failed\n"); | |
173 | return ret; | |
174 | } | |
175 | return 0; | |
176 | } | |
177 | ||
32feac95 VM |
178 | static irqreturn_t i2s_irq_handler(int irq, void *dev_id) |
179 | { | |
180 | u16 play_flag, cap_flag; | |
181 | u32 val; | |
182 | struct i2s_dev_data *rv_i2s_data = dev_id; | |
183 | ||
184 | if (!rv_i2s_data) | |
185 | return IRQ_NONE; | |
186 | ||
187 | play_flag = 0; | |
188 | cap_flag = 0; | |
189 | val = rv_readl(rv_i2s_data->acp3x_base + mmACP_EXTERNAL_INTR_STAT); | |
190 | if ((val & BIT(BT_TX_THRESHOLD)) && rv_i2s_data->play_stream) { | |
191 | rv_writel(BIT(BT_TX_THRESHOLD), rv_i2s_data->acp3x_base + | |
192 | mmACP_EXTERNAL_INTR_STAT); | |
193 | snd_pcm_period_elapsed(rv_i2s_data->play_stream); | |
194 | play_flag = 1; | |
195 | } | |
196 | ||
197 | if ((val & BIT(BT_RX_THRESHOLD)) && rv_i2s_data->capture_stream) { | |
198 | rv_writel(BIT(BT_RX_THRESHOLD), rv_i2s_data->acp3x_base + | |
199 | mmACP_EXTERNAL_INTR_STAT); | |
200 | snd_pcm_period_elapsed(rv_i2s_data->capture_stream); | |
201 | cap_flag = 1; | |
202 | } | |
203 | ||
204 | if (play_flag | cap_flag) | |
205 | return IRQ_HANDLED; | |
206 | else | |
207 | return IRQ_NONE; | |
208 | } | |
209 | ||
0b87d6bc VM |
210 | static void config_acp3x_dma(struct i2s_stream_instance *rtd, int direction) |
211 | { | |
212 | u16 page_idx; | |
213 | u64 addr; | |
214 | u32 low, high, val, acp_fifo_addr; | |
215 | struct page *pg = rtd->pg; | |
216 | ||
217 | /* 8 scratch registers used to map one 64 bit address */ | |
218 | if (direction == SNDRV_PCM_STREAM_PLAYBACK) | |
219 | val = 0; | |
220 | else | |
221 | val = rtd->num_pages * 8; | |
222 | ||
223 | /* Group Enable */ | |
224 | rv_writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp3x_base + | |
225 | mmACPAXI2AXI_ATU_BASE_ADDR_GRP_1); | |
226 | rv_writel(PAGE_SIZE_4K_ENABLE, rtd->acp3x_base + | |
227 | mmACPAXI2AXI_ATU_PAGE_SIZE_GRP_1); | |
228 | ||
229 | for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) { | |
230 | /* Load the low address of page int ACP SRAM through SRBM */ | |
231 | addr = page_to_phys(pg); | |
232 | low = lower_32_bits(addr); | |
233 | high = upper_32_bits(addr); | |
234 | ||
235 | rv_writel(low, rtd->acp3x_base + mmACP_SCRATCH_REG_0 + val); | |
236 | high |= BIT(31); | |
237 | rv_writel(high, rtd->acp3x_base + mmACP_SCRATCH_REG_0 + val | |
238 | + 4); | |
239 | /* Move to next physically contiguos page */ | |
240 | val += 8; | |
241 | pg++; | |
242 | } | |
243 | ||
244 | if (direction == SNDRV_PCM_STREAM_PLAYBACK) { | |
245 | /* Config ringbuffer */ | |
246 | rv_writel(MEM_WINDOW_START, rtd->acp3x_base + | |
247 | mmACP_BT_TX_RINGBUFADDR); | |
248 | rv_writel(MAX_BUFFER, rtd->acp3x_base + | |
249 | mmACP_BT_TX_RINGBUFSIZE); | |
250 | rv_writel(DMA_SIZE, rtd->acp3x_base + mmACP_BT_TX_DMA_SIZE); | |
251 | ||
252 | /* Config audio fifo */ | |
253 | acp_fifo_addr = ACP_SRAM_PTE_OFFSET + (rtd->num_pages * 8) | |
254 | + PLAYBACK_FIFO_ADDR_OFFSET; | |
255 | rv_writel(acp_fifo_addr, rtd->acp3x_base + | |
256 | mmACP_BT_TX_FIFOADDR); | |
257 | rv_writel(FIFO_SIZE, rtd->acp3x_base + mmACP_BT_TX_FIFOSIZE); | |
258 | } else { | |
259 | /* Config ringbuffer */ | |
260 | rv_writel(MEM_WINDOW_START + MAX_BUFFER, rtd->acp3x_base + | |
261 | mmACP_BT_RX_RINGBUFADDR); | |
262 | rv_writel(MAX_BUFFER, rtd->acp3x_base + | |
263 | mmACP_BT_RX_RINGBUFSIZE); | |
264 | rv_writel(DMA_SIZE, rtd->acp3x_base + mmACP_BT_RX_DMA_SIZE); | |
265 | ||
266 | /* Config audio fifo */ | |
267 | acp_fifo_addr = ACP_SRAM_PTE_OFFSET + | |
268 | (rtd->num_pages * 8) + CAPTURE_FIFO_ADDR_OFFSET; | |
269 | rv_writel(acp_fifo_addr, rtd->acp3x_base + | |
270 | mmACP_BT_RX_FIFOADDR); | |
271 | rv_writel(FIFO_SIZE, rtd->acp3x_base + mmACP_BT_RX_FIFOSIZE); | |
272 | } | |
273 | ||
274 | /* Enable watermark/period interrupt to host */ | |
275 | rv_writel(BIT(BT_TX_THRESHOLD) | BIT(BT_RX_THRESHOLD), | |
276 | rtd->acp3x_base + mmACP_EXTERNAL_INTR_CNTL); | |
277 | } | |
278 | ||
279 | static int acp3x_dma_open(struct snd_pcm_substream *substream) | |
280 | { | |
281 | int ret = 0; | |
282 | ||
283 | struct snd_pcm_runtime *runtime = substream->runtime; | |
284 | struct snd_soc_pcm_runtime *prtd = substream->private_data; | |
285 | struct snd_soc_component *component = snd_soc_rtdcom_lookup(prtd, | |
286 | DRV_NAME); | |
287 | struct i2s_dev_data *adata = dev_get_drvdata(component->dev); | |
288 | ||
289 | struct i2s_stream_instance *i2s_data = kzalloc(sizeof(struct i2s_stream_instance), | |
290 | GFP_KERNEL); | |
291 | if (!i2s_data) | |
292 | return -EINVAL; | |
293 | ||
294 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
295 | runtime->hw = acp3x_pcm_hardware_playback; | |
296 | else | |
297 | runtime->hw = acp3x_pcm_hardware_capture; | |
298 | ||
299 | ret = snd_pcm_hw_constraint_integer(runtime, | |
300 | SNDRV_PCM_HW_PARAM_PERIODS); | |
301 | if (ret < 0) { | |
302 | dev_err(component->dev, "set integer constraint failed\n"); | |
46dce404 | 303 | kfree(i2s_data); |
0b87d6bc VM |
304 | return ret; |
305 | } | |
306 | ||
307 | if (!adata->play_stream && !adata->capture_stream) | |
308 | rv_writel(1, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB); | |
309 | ||
310 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
311 | adata->play_stream = substream; | |
312 | else | |
313 | adata->capture_stream = substream; | |
314 | ||
315 | i2s_data->acp3x_base = adata->acp3x_base; | |
316 | runtime->private_data = i2s_data; | |
317 | return 0; | |
318 | } | |
319 | ||
320 | static int acp3x_dma_hw_params(struct snd_pcm_substream *substream, | |
321 | struct snd_pcm_hw_params *params) | |
322 | { | |
323 | int status; | |
324 | u64 size; | |
0b87d6bc VM |
325 | struct page *pg; |
326 | struct snd_pcm_runtime *runtime = substream->runtime; | |
327 | struct i2s_stream_instance *rtd = runtime->private_data; | |
328 | ||
329 | if (!rtd) | |
330 | return -EINVAL; | |
331 | ||
0b87d6bc VM |
332 | size = params_buffer_bytes(params); |
333 | status = snd_pcm_lib_malloc_pages(substream, size); | |
334 | if (status < 0) | |
335 | return status; | |
336 | ||
337 | memset(substream->runtime->dma_area, 0, params_buffer_bytes(params)); | |
338 | pg = virt_to_page(substream->dma_buffer.area); | |
339 | if (pg) { | |
340 | rtd->pg = pg; | |
341 | rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT); | |
342 | config_acp3x_dma(rtd, substream->stream); | |
343 | status = 0; | |
344 | } else { | |
345 | status = -ENOMEM; | |
346 | } | |
347 | return status; | |
348 | } | |
349 | ||
350 | static snd_pcm_uframes_t acp3x_dma_pointer(struct snd_pcm_substream *substream) | |
351 | { | |
352 | u32 pos = 0; | |
353 | struct i2s_stream_instance *rtd = substream->runtime->private_data; | |
354 | ||
355 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
356 | pos = rv_readl(rtd->acp3x_base + | |
357 | mmACP_BT_TX_LINKPOSITIONCNTR); | |
358 | else | |
359 | pos = rv_readl(rtd->acp3x_base + | |
360 | mmACP_BT_RX_LINKPOSITIONCNTR); | |
361 | ||
362 | if (pos >= MAX_BUFFER) | |
363 | pos = 0; | |
364 | ||
365 | return bytes_to_frames(substream->runtime, pos); | |
366 | } | |
367 | ||
368 | static int acp3x_dma_new(struct snd_soc_pcm_runtime *rtd) | |
369 | { | |
370 | return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, | |
371 | SNDRV_DMA_TYPE_DEV, | |
372 | NULL, MIN_BUFFER, | |
373 | MAX_BUFFER); | |
374 | } | |
375 | ||
376 | static int acp3x_dma_hw_free(struct snd_pcm_substream *substream) | |
377 | { | |
378 | return snd_pcm_lib_free_pages(substream); | |
379 | } | |
380 | ||
381 | static int acp3x_dma_mmap(struct snd_pcm_substream *substream, | |
382 | struct vm_area_struct *vma) | |
383 | { | |
384 | return snd_pcm_lib_default_mmap(substream, vma); | |
385 | } | |
386 | ||
387 | static int acp3x_dma_close(struct snd_pcm_substream *substream) | |
388 | { | |
389 | struct snd_soc_pcm_runtime *prtd = substream->private_data; | |
390 | struct i2s_stream_instance *rtd = substream->runtime->private_data; | |
391 | struct snd_soc_component *component = snd_soc_rtdcom_lookup(prtd, | |
392 | DRV_NAME); | |
393 | struct i2s_dev_data *adata = dev_get_drvdata(component->dev); | |
394 | ||
395 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
396 | adata->play_stream = NULL; | |
397 | else | |
398 | adata->capture_stream = NULL; | |
399 | ||
400 | /* Disable ACP irq, when the current stream is being closed and | |
401 | * another stream is also not active. | |
402 | */ | |
403 | if (!adata->play_stream && !adata->capture_stream) | |
404 | rv_writel(0, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB); | |
405 | kfree(rtd); | |
406 | return 0; | |
407 | } | |
408 | ||
ac289c7e | 409 | static struct snd_pcm_ops acp3x_dma_ops = { |
0b87d6bc VM |
410 | .open = acp3x_dma_open, |
411 | .close = acp3x_dma_close, | |
412 | .ioctl = snd_pcm_lib_ioctl, | |
413 | .hw_params = acp3x_dma_hw_params, | |
414 | .hw_free = acp3x_dma_hw_free, | |
415 | .pointer = acp3x_dma_pointer, | |
416 | .mmap = acp3x_dma_mmap, | |
ac289c7e VM |
417 | }; |
418 | ||
67aa06ae VM |
419 | |
420 | static int acp3x_dai_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) | |
421 | { | |
422 | ||
423 | struct i2s_dev_data *adata = snd_soc_dai_get_drvdata(cpu_dai); | |
424 | ||
425 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | |
426 | case SND_SOC_DAIFMT_I2S: | |
427 | adata->tdm_mode = false; | |
428 | break; | |
429 | case SND_SOC_DAIFMT_DSP_A: | |
430 | adata->tdm_mode = true; | |
431 | break; | |
432 | default: | |
433 | return -EINVAL; | |
434 | } | |
435 | ||
436 | return 0; | |
437 | } | |
438 | ||
439 | static int acp3x_dai_set_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask, | |
440 | u32 rx_mask, int slots, int slot_width) | |
441 | { | |
442 | u32 val = 0; | |
443 | u16 slot_len; | |
444 | ||
445 | struct i2s_dev_data *adata = snd_soc_dai_get_drvdata(cpu_dai); | |
446 | ||
447 | switch (slot_width) { | |
448 | case SLOT_WIDTH_8: | |
449 | slot_len = 8; | |
450 | break; | |
451 | case SLOT_WIDTH_16: | |
452 | slot_len = 16; | |
453 | break; | |
454 | case SLOT_WIDTH_24: | |
455 | slot_len = 24; | |
456 | break; | |
457 | case SLOT_WIDTH_32: | |
458 | slot_len = 0; | |
459 | break; | |
460 | default: | |
461 | return -EINVAL; | |
462 | } | |
463 | ||
464 | val = rv_readl(adata->acp3x_base + mmACP_BTTDM_ITER); | |
465 | rv_writel((val | 0x2), adata->acp3x_base + mmACP_BTTDM_ITER); | |
466 | val = rv_readl(adata->acp3x_base + mmACP_BTTDM_IRER); | |
467 | rv_writel((val | 0x2), adata->acp3x_base + mmACP_BTTDM_IRER); | |
468 | ||
469 | val = (FRM_LEN | (slots << 15) | (slot_len << 18)); | |
470 | rv_writel(val, adata->acp3x_base + mmACP_BTTDM_TXFRMT); | |
471 | rv_writel(val, adata->acp3x_base + mmACP_BTTDM_RXFRMT); | |
472 | ||
473 | adata->tdm_fmt = val; | |
474 | return 0; | |
475 | } | |
476 | ||
2b5f290e VM |
477 | static int acp3x_dai_i2s_hwparams(struct snd_pcm_substream *substream, |
478 | struct snd_pcm_hw_params *params, | |
479 | struct snd_soc_dai *dai) | |
480 | { | |
481 | u32 val = 0; | |
482 | struct i2s_stream_instance *rtd = substream->runtime->private_data; | |
483 | ||
484 | switch (params_format(params)) { | |
485 | case SNDRV_PCM_FORMAT_U8: | |
486 | case SNDRV_PCM_FORMAT_S8: | |
487 | rtd->xfer_resolution = 0x0; | |
488 | break; | |
489 | case SNDRV_PCM_FORMAT_S16_LE: | |
490 | rtd->xfer_resolution = 0x02; | |
491 | break; | |
492 | case SNDRV_PCM_FORMAT_S24_LE: | |
493 | rtd->xfer_resolution = 0x04; | |
494 | break; | |
495 | case SNDRV_PCM_FORMAT_S32_LE: | |
496 | rtd->xfer_resolution = 0x05; | |
497 | break; | |
498 | default: | |
499 | return -EINVAL; | |
500 | } | |
501 | val = rv_readl(rtd->acp3x_base + mmACP_BTTDM_ITER); | |
502 | val = val | (rtd->xfer_resolution << 3); | |
503 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
504 | rv_writel(val, rtd->acp3x_base + mmACP_BTTDM_ITER); | |
505 | else | |
506 | rv_writel(val, rtd->acp3x_base + mmACP_BTTDM_IRER); | |
507 | ||
508 | return 0; | |
509 | } | |
510 | ||
511 | static int acp3x_dai_i2s_trigger(struct snd_pcm_substream *substream, | |
512 | int cmd, struct snd_soc_dai *dai) | |
513 | { | |
514 | int ret = 0; | |
515 | struct i2s_stream_instance *rtd = substream->runtime->private_data; | |
516 | u32 val, period_bytes; | |
517 | ||
518 | period_bytes = frames_to_bytes(substream->runtime, | |
519 | substream->runtime->period_size); | |
520 | switch (cmd) { | |
521 | case SNDRV_PCM_TRIGGER_START: | |
522 | case SNDRV_PCM_TRIGGER_RESUME: | |
523 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
524 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
525 | rv_writel(period_bytes, rtd->acp3x_base + | |
526 | mmACP_BT_TX_INTR_WATERMARK_SIZE); | |
527 | val = rv_readl(rtd->acp3x_base + mmACP_BTTDM_ITER); | |
528 | val = val | BIT(0); | |
529 | rv_writel(val, rtd->acp3x_base + mmACP_BTTDM_ITER); | |
530 | } else { | |
531 | rv_writel(period_bytes, rtd->acp3x_base + | |
532 | mmACP_BT_RX_INTR_WATERMARK_SIZE); | |
533 | val = rv_readl(rtd->acp3x_base + mmACP_BTTDM_IRER); | |
534 | val = val | BIT(0); | |
535 | rv_writel(val, rtd->acp3x_base + mmACP_BTTDM_IRER); | |
536 | } | |
537 | rv_writel(1, rtd->acp3x_base + mmACP_BTTDM_IER); | |
538 | break; | |
539 | case SNDRV_PCM_TRIGGER_STOP: | |
540 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
541 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
542 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
543 | val = rv_readl(rtd->acp3x_base + mmACP_BTTDM_ITER); | |
544 | val = val & ~BIT(0); | |
545 | rv_writel(val, rtd->acp3x_base + mmACP_BTTDM_ITER); | |
546 | } else { | |
547 | val = rv_readl(rtd->acp3x_base + mmACP_BTTDM_IRER); | |
548 | val = val & ~BIT(0); | |
549 | rv_writel(val, rtd->acp3x_base + mmACP_BTTDM_IRER); | |
550 | } | |
551 | rv_writel(0, rtd->acp3x_base + mmACP_BTTDM_IER); | |
552 | break; | |
553 | default: | |
554 | ret = -EINVAL; | |
555 | break; | |
556 | } | |
557 | ||
558 | return ret; | |
559 | } | |
560 | ||
ac289c7e | 561 | struct snd_soc_dai_ops acp3x_dai_i2s_ops = { |
2b5f290e VM |
562 | .hw_params = acp3x_dai_i2s_hwparams, |
563 | .trigger = acp3x_dai_i2s_trigger, | |
67aa06ae VM |
564 | .set_fmt = acp3x_dai_i2s_set_fmt, |
565 | .set_tdm_slot = acp3x_dai_set_tdm_slot, | |
ac289c7e VM |
566 | }; |
567 | ||
568 | static struct snd_soc_dai_driver acp3x_i2s_dai_driver = { | |
569 | .playback = { | |
570 | .rates = SNDRV_PCM_RATE_8000_96000, | |
571 | .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | | |
572 | SNDRV_PCM_FMTBIT_U8 | | |
573 | SNDRV_PCM_FMTBIT_S24_LE | | |
574 | SNDRV_PCM_FMTBIT_S32_LE, | |
575 | .channels_min = 2, | |
576 | .channels_max = 8, | |
577 | ||
578 | .rate_min = 8000, | |
579 | .rate_max = 96000, | |
580 | }, | |
581 | .capture = { | |
582 | .rates = SNDRV_PCM_RATE_8000_48000, | |
583 | .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | | |
584 | SNDRV_PCM_FMTBIT_U8 | | |
585 | SNDRV_PCM_FMTBIT_S24_LE | | |
586 | SNDRV_PCM_FMTBIT_S32_LE, | |
587 | .channels_min = 2, | |
588 | .channels_max = 2, | |
589 | .rate_min = 8000, | |
590 | .rate_max = 48000, | |
591 | }, | |
592 | .ops = &acp3x_dai_i2s_ops, | |
593 | }; | |
594 | ||
595 | static const struct snd_soc_component_driver acp3x_i2s_component = { | |
596 | .name = DRV_NAME, | |
597 | .ops = &acp3x_dma_ops, | |
598 | .pcm_new = acp3x_dma_new, | |
599 | }; | |
600 | ||
601 | static int acp3x_audio_probe(struct platform_device *pdev) | |
602 | { | |
603 | int status; | |
604 | struct resource *res; | |
605 | struct i2s_dev_data *adata; | |
606 | unsigned int irqflags; | |
607 | ||
608 | if (!pdev->dev.platform_data) { | |
609 | dev_err(&pdev->dev, "platform_data not retrieved\n"); | |
610 | return -ENODEV; | |
611 | } | |
612 | irqflags = *((unsigned int *)(pdev->dev.platform_data)); | |
613 | ||
ac289c7e VM |
614 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
615 | if (!res) { | |
616 | dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n"); | |
617 | return -ENODEV; | |
618 | } | |
619 | ||
4cb79ef9 GS |
620 | adata = devm_kzalloc(&pdev->dev, sizeof(*adata), GFP_KERNEL); |
621 | if (!adata) | |
622 | return -ENOMEM; | |
623 | ||
ac289c7e VM |
624 | adata->acp3x_base = devm_ioremap(&pdev->dev, res->start, |
625 | resource_size(res)); | |
626 | ||
32feac95 VM |
627 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); |
628 | if (!res) { | |
629 | dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n"); | |
630 | return -ENODEV; | |
631 | } | |
632 | ||
633 | adata->i2s_irq = res->start; | |
ac289c7e VM |
634 | adata->play_stream = NULL; |
635 | adata->capture_stream = NULL; | |
636 | ||
637 | dev_set_drvdata(&pdev->dev, adata); | |
638 | /* Initialize ACP */ | |
639 | status = acp3x_init(adata->acp3x_base); | |
640 | if (status) | |
641 | return -ENODEV; | |
642 | status = devm_snd_soc_register_component(&pdev->dev, | |
643 | &acp3x_i2s_component, | |
644 | &acp3x_i2s_dai_driver, 1); | |
645 | if (status) { | |
646 | dev_err(&pdev->dev, "Fail to register acp i2s dai\n"); | |
647 | goto dev_err; | |
648 | } | |
32feac95 VM |
649 | status = devm_request_irq(&pdev->dev, adata->i2s_irq, i2s_irq_handler, |
650 | irqflags, "ACP3x_I2S_IRQ", adata); | |
651 | if (status) { | |
652 | dev_err(&pdev->dev, "ACP3x I2S IRQ request failed\n"); | |
653 | goto dev_err; | |
654 | } | |
ac289c7e | 655 | |
56e4dd8f VM |
656 | pm_runtime_set_autosuspend_delay(&pdev->dev, 10000); |
657 | pm_runtime_use_autosuspend(&pdev->dev); | |
658 | pm_runtime_enable(&pdev->dev); | |
ac289c7e VM |
659 | return 0; |
660 | dev_err: | |
661 | status = acp3x_deinit(adata->acp3x_base); | |
662 | if (status) | |
663 | dev_err(&pdev->dev, "ACP de-init failed\n"); | |
664 | else | |
665 | dev_info(&pdev->dev, "ACP de-initialized\n"); | |
666 | /*ignore device status and return driver probe error*/ | |
667 | return -ENODEV; | |
668 | } | |
669 | ||
670 | static int acp3x_audio_remove(struct platform_device *pdev) | |
671 | { | |
672 | int ret; | |
673 | struct i2s_dev_data *adata = dev_get_drvdata(&pdev->dev); | |
674 | ||
675 | ret = acp3x_deinit(adata->acp3x_base); | |
676 | if (ret) | |
677 | dev_err(&pdev->dev, "ACP de-init failed\n"); | |
678 | else | |
679 | dev_info(&pdev->dev, "ACP de-initialized\n"); | |
680 | ||
56e4dd8f | 681 | pm_runtime_disable(&pdev->dev); |
ac289c7e VM |
682 | return 0; |
683 | } | |
684 | ||
8de1b5ed VM |
685 | static int acp3x_resume(struct device *dev) |
686 | { | |
687 | int status; | |
688 | u32 val; | |
689 | struct i2s_dev_data *adata = dev_get_drvdata(dev); | |
690 | ||
691 | status = acp3x_init(adata->acp3x_base); | |
692 | if (status) | |
693 | return -ENODEV; | |
694 | ||
695 | if (adata->play_stream && adata->play_stream->runtime) { | |
696 | struct i2s_stream_instance *rtd = | |
697 | adata->play_stream->runtime->private_data; | |
698 | config_acp3x_dma(rtd, SNDRV_PCM_STREAM_PLAYBACK); | |
699 | rv_writel((rtd->xfer_resolution << 3), | |
700 | rtd->acp3x_base + mmACP_BTTDM_ITER); | |
701 | if (adata->tdm_mode == true) { | |
702 | rv_writel(adata->tdm_fmt, adata->acp3x_base + | |
703 | mmACP_BTTDM_TXFRMT); | |
704 | val = rv_readl(adata->acp3x_base + mmACP_BTTDM_ITER); | |
705 | rv_writel((val | 0x2), adata->acp3x_base + | |
706 | mmACP_BTTDM_ITER); | |
707 | } | |
708 | } | |
709 | ||
710 | if (adata->capture_stream && adata->capture_stream->runtime) { | |
711 | struct i2s_stream_instance *rtd = | |
712 | adata->capture_stream->runtime->private_data; | |
713 | config_acp3x_dma(rtd, SNDRV_PCM_STREAM_CAPTURE); | |
714 | rv_writel((rtd->xfer_resolution << 3), | |
715 | rtd->acp3x_base + mmACP_BTTDM_IRER); | |
716 | if (adata->tdm_mode == true) { | |
717 | rv_writel(adata->tdm_fmt, adata->acp3x_base + | |
718 | mmACP_BTTDM_RXFRMT); | |
719 | val = rv_readl(adata->acp3x_base + mmACP_BTTDM_IRER); | |
720 | rv_writel((val | 0x2), adata->acp3x_base + | |
721 | mmACP_BTTDM_IRER); | |
722 | } | |
723 | } | |
724 | ||
725 | rv_writel(1, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB); | |
726 | return 0; | |
727 | } | |
728 | ||
729 | ||
56e4dd8f VM |
730 | static int acp3x_pcm_runtime_suspend(struct device *dev) |
731 | { | |
732 | int status; | |
733 | struct i2s_dev_data *adata = dev_get_drvdata(dev); | |
734 | ||
735 | status = acp3x_deinit(adata->acp3x_base); | |
736 | if (status) | |
737 | dev_err(dev, "ACP de-init failed\n"); | |
738 | else | |
739 | dev_info(dev, "ACP de-initialized\n"); | |
740 | ||
741 | rv_writel(0, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB); | |
742 | ||
743 | return 0; | |
744 | } | |
745 | ||
746 | static int acp3x_pcm_runtime_resume(struct device *dev) | |
747 | { | |
748 | int status; | |
749 | struct i2s_dev_data *adata = dev_get_drvdata(dev); | |
750 | ||
751 | status = acp3x_init(adata->acp3x_base); | |
752 | if (status) | |
753 | return -ENODEV; | |
754 | rv_writel(1, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB); | |
755 | return 0; | |
756 | } | |
757 | ||
758 | static const struct dev_pm_ops acp3x_pm_ops = { | |
759 | .runtime_suspend = acp3x_pcm_runtime_suspend, | |
760 | .runtime_resume = acp3x_pcm_runtime_resume, | |
8de1b5ed | 761 | .resume = acp3x_resume, |
56e4dd8f VM |
762 | }; |
763 | ||
ac289c7e VM |
764 | static struct platform_driver acp3x_dma_driver = { |
765 | .probe = acp3x_audio_probe, | |
766 | .remove = acp3x_audio_remove, | |
767 | .driver = { | |
768 | .name = "acp3x_rv_i2s", | |
56e4dd8f | 769 | .pm = &acp3x_pm_ops, |
ac289c7e VM |
770 | }, |
771 | }; | |
772 | ||
773 | module_platform_driver(acp3x_dma_driver); | |
774 | ||
775 | MODULE_AUTHOR("Maruthi.Bayyavarapu@amd.com"); | |
776 | MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); | |
777 | MODULE_DESCRIPTION("AMD ACP 3.x PCM Driver"); | |
778 | MODULE_LICENSE("GPL v2"); | |
779 | MODULE_ALIAS("platform:" DRV_NAME); |