1 // SPDX-License-Identifier: GPL-2.0+
3 // AMD ALSA SoC PDM Driver
5 //Copyright 2020 Advanced Micro Devices, Inc.
7 #include <linux/platform_device.h>
8 #include <linux/module.h>
11 #include <linux/pm_runtime.h>
12 #include <sound/pcm_params.h>
13 #include <sound/soc.h>
14 #include <sound/soc-dai.h>
18 #define DRV_NAME "acp_rn_pdm_dma"
20 static const struct snd_pcm_hardware acp_pdm_hardware_capture
= {
21 .info
= SNDRV_PCM_INFO_INTERLEAVED
|
22 SNDRV_PCM_INFO_BLOCK_TRANSFER
|
24 SNDRV_PCM_INFO_MMAP_VALID
|
25 SNDRV_PCM_INFO_PAUSE
| SNDRV_PCM_INFO_RESUME
,
26 .formats
= SNDRV_PCM_FMTBIT_S32_LE
,
29 .rates
= SNDRV_PCM_RATE_48000
,
32 .buffer_bytes_max
= CAPTURE_MAX_NUM_PERIODS
* CAPTURE_MAX_PERIOD_SIZE
,
33 .period_bytes_min
= CAPTURE_MIN_PERIOD_SIZE
,
34 .period_bytes_max
= CAPTURE_MAX_PERIOD_SIZE
,
35 .periods_min
= CAPTURE_MIN_NUM_PERIODS
,
36 .periods_max
= CAPTURE_MAX_NUM_PERIODS
,
39 static irqreturn_t
pdm_irq_handler(int irq
, void *dev_id
)
41 struct pdm_dev_data
*rn_pdm_data
;
50 val
= rn_readl(rn_pdm_data
->acp_base
+ ACP_EXTERNAL_INTR_STAT
);
51 if ((val
& BIT(PDM_DMA_STAT
)) && rn_pdm_data
->capture_stream
) {
52 rn_writel(BIT(PDM_DMA_STAT
), rn_pdm_data
->acp_base
+
53 ACP_EXTERNAL_INTR_STAT
);
54 snd_pcm_period_elapsed(rn_pdm_data
->capture_stream
);
64 static void init_pdm_ring_buffer(u32 physical_addr
,
67 void __iomem
*acp_base
)
69 rn_writel(physical_addr
, acp_base
+ ACP_WOV_RX_RINGBUFADDR
);
70 rn_writel(buffer_size
, acp_base
+ ACP_WOV_RX_RINGBUFSIZE
);
71 rn_writel(watermark_size
, acp_base
+ ACP_WOV_RX_INTR_WATERMARK_SIZE
);
72 rn_writel(0x01, acp_base
+ ACPAXI2AXI_ATU_CTRL
);
75 static void enable_pdm_clock(void __iomem
*acp_base
)
77 u32 pdm_clk_enable
, pdm_ctrl
;
79 pdm_clk_enable
= ACP_PDM_CLK_FREQ_MASK
;
82 rn_writel(pdm_clk_enable
, acp_base
+ ACP_WOV_CLK_CTRL
);
83 pdm_ctrl
= rn_readl(acp_base
+ ACP_WOV_MISC_CTRL
);
84 pdm_ctrl
|= ACP_WOV_MISC_CTRL_MASK
;
85 rn_writel(pdm_ctrl
, acp_base
+ ACP_WOV_MISC_CTRL
);
88 static void enable_pdm_interrupts(void __iomem
*acp_base
)
92 ext_int_ctrl
= rn_readl(acp_base
+ ACP_EXTERNAL_INTR_CNTL
);
93 ext_int_ctrl
|= PDM_DMA_INTR_MASK
;
94 rn_writel(ext_int_ctrl
, acp_base
+ ACP_EXTERNAL_INTR_CNTL
);
97 static void disable_pdm_interrupts(void __iomem
*acp_base
)
101 ext_int_ctrl
= rn_readl(acp_base
+ ACP_EXTERNAL_INTR_CNTL
);
102 ext_int_ctrl
|= ~PDM_DMA_INTR_MASK
;
103 rn_writel(ext_int_ctrl
, acp_base
+ ACP_EXTERNAL_INTR_CNTL
);
106 static bool check_pdm_dma_status(void __iomem
*acp_base
)
109 u32 pdm_enable
, pdm_dma_enable
;
111 pdm_dma_status
= false;
112 pdm_enable
= rn_readl(acp_base
+ ACP_WOV_PDM_ENABLE
);
113 pdm_dma_enable
= rn_readl(acp_base
+ ACP_WOV_PDM_DMA_ENABLE
);
114 if ((pdm_enable
& ACP_PDM_ENABLE
) && (pdm_dma_enable
&
115 ACP_PDM_DMA_EN_STATUS
))
116 pdm_dma_status
= true;
117 return pdm_dma_status
;
120 static int start_pdm_dma(void __iomem
*acp_base
)
127 pdm_dma_enable
= 0x01;
129 enable_pdm_clock(acp_base
);
130 rn_writel(pdm_enable
, acp_base
+ ACP_WOV_PDM_ENABLE
);
131 rn_writel(pdm_dma_enable
, acp_base
+ ACP_WOV_PDM_DMA_ENABLE
);
132 pdm_dma_enable
= 0x00;
134 while (++timeout
< ACP_COUNTER
) {
135 pdm_dma_enable
= rn_readl(acp_base
+ ACP_WOV_PDM_DMA_ENABLE
);
136 if ((pdm_dma_enable
& 0x02) == ACP_PDM_DMA_EN_STATUS
)
143 static int stop_pdm_dma(void __iomem
*acp_base
)
145 u32 pdm_enable
, pdm_dma_enable
;
149 pdm_dma_enable
= 0x00;
151 pdm_enable
= rn_readl(acp_base
+ ACP_WOV_PDM_ENABLE
);
152 pdm_dma_enable
= rn_readl(acp_base
+ ACP_WOV_PDM_DMA_ENABLE
);
153 if (pdm_dma_enable
& 0x01) {
154 pdm_dma_enable
= 0x02;
155 rn_writel(pdm_dma_enable
, acp_base
+ ACP_WOV_PDM_DMA_ENABLE
);
156 pdm_dma_enable
= 0x00;
158 while (++timeout
< ACP_COUNTER
) {
159 pdm_dma_enable
= rn_readl(acp_base
+
160 ACP_WOV_PDM_DMA_ENABLE
);
161 if ((pdm_dma_enable
& 0x02) == 0x00)
165 if (timeout
== ACP_COUNTER
)
168 if (pdm_enable
== ACP_PDM_ENABLE
) {
169 pdm_enable
= ACP_PDM_DISABLE
;
170 rn_writel(pdm_enable
, acp_base
+ ACP_WOV_PDM_ENABLE
);
172 rn_writel(0x01, acp_base
+ ACP_WOV_PDM_FIFO_FLUSH
);
176 static void config_acp_dma(struct pdm_stream_instance
*rtd
, int direction
)
182 addr
= rtd
->dma_addr
;
186 rn_writel(ACP_SRAM_PTE_OFFSET
| BIT(31), rtd
->acp_base
+
187 ACPAXI2AXI_ATU_BASE_ADDR_GRP_1
);
188 rn_writel(PAGE_SIZE_4K_ENABLE
, rtd
->acp_base
+
189 ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1
);
191 for (page_idx
= 0; page_idx
< rtd
->num_pages
; page_idx
++) {
192 /* Load the low address of page int ACP SRAM through SRBM */
193 low
= lower_32_bits(addr
);
194 high
= upper_32_bits(addr
);
196 rn_writel(low
, rtd
->acp_base
+ ACP_SCRATCH_REG_0
+ val
);
198 rn_writel(high
, rtd
->acp_base
+ ACP_SCRATCH_REG_0
+ val
+ 4);
204 static int acp_pdm_dma_open(struct snd_soc_component
*component
,
205 struct snd_pcm_substream
*substream
)
207 struct snd_pcm_runtime
*runtime
;
208 struct pdm_dev_data
*adata
;
209 struct pdm_stream_instance
*pdm_data
;
212 runtime
= substream
->runtime
;
213 adata
= dev_get_drvdata(component
->dev
);
214 pdm_data
= kzalloc(sizeof(*pdm_data
), GFP_KERNEL
);
218 if (substream
->stream
== SNDRV_PCM_STREAM_CAPTURE
)
219 runtime
->hw
= acp_pdm_hardware_capture
;
221 ret
= snd_pcm_hw_constraint_integer(runtime
,
222 SNDRV_PCM_HW_PARAM_PERIODS
);
224 dev_err(component
->dev
, "set integer constraint failed\n");
229 enable_pdm_interrupts(adata
->acp_base
);
231 if (substream
->stream
== SNDRV_PCM_STREAM_CAPTURE
)
232 adata
->capture_stream
= substream
;
234 pdm_data
->acp_base
= adata
->acp_base
;
235 runtime
->private_data
= pdm_data
;
239 static int acp_pdm_dma_hw_params(struct snd_soc_component
*component
,
240 struct snd_pcm_substream
*substream
,
241 struct snd_pcm_hw_params
*params
)
243 struct pdm_stream_instance
*rtd
;
244 size_t size
, period_bytes
;
246 rtd
= substream
->runtime
->private_data
;
249 size
= params_buffer_bytes(params
);
250 period_bytes
= params_period_bytes(params
);
251 rtd
->dma_addr
= substream
->dma_buffer
.addr
;
252 rtd
->num_pages
= (PAGE_ALIGN(size
) >> PAGE_SHIFT
);
253 config_acp_dma(rtd
, substream
->stream
);
254 init_pdm_ring_buffer(MEM_WINDOW_START
, size
, period_bytes
,
259 static u64
acp_pdm_get_byte_count(struct pdm_stream_instance
*rtd
,
262 union acp_pdm_dma_count byte_count
;
264 byte_count
.bcount
.high
=
265 rn_readl(rtd
->acp_base
+
266 ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH
);
267 byte_count
.bcount
.low
=
268 rn_readl(rtd
->acp_base
+
269 ACP_WOV_RX_LINEARPOSITIONCNTR_LOW
);
270 return byte_count
.bytescount
;
273 static snd_pcm_uframes_t
acp_pdm_dma_pointer(struct snd_soc_component
*comp
,
274 struct snd_pcm_substream
*stream
)
276 struct pdm_stream_instance
*rtd
;
280 rtd
= stream
->runtime
->private_data
;
281 buffersize
= frames_to_bytes(stream
->runtime
,
282 stream
->runtime
->buffer_size
);
283 bytescount
= acp_pdm_get_byte_count(rtd
, stream
->stream
);
284 if (bytescount
> rtd
->bytescount
)
285 bytescount
-= rtd
->bytescount
;
286 pos
= do_div(bytescount
, buffersize
);
287 return bytes_to_frames(stream
->runtime
, pos
);
290 static int acp_pdm_dma_new(struct snd_soc_component
*component
,
291 struct snd_soc_pcm_runtime
*rtd
)
293 struct device
*parent
= component
->dev
->parent
;
295 snd_pcm_set_managed_buffer_all(rtd
->pcm
, SNDRV_DMA_TYPE_DEV
,
296 parent
, MIN_BUFFER
, MAX_BUFFER
);
300 static int acp_pdm_dma_mmap(struct snd_soc_component
*component
,
301 struct snd_pcm_substream
*substream
,
302 struct vm_area_struct
*vma
)
304 return snd_pcm_lib_default_mmap(substream
, vma
);
307 static int acp_pdm_dma_close(struct snd_soc_component
*component
,
308 struct snd_pcm_substream
*substream
)
310 struct pdm_dev_data
*adata
= dev_get_drvdata(component
->dev
);
312 disable_pdm_interrupts(adata
->acp_base
);
313 adata
->capture_stream
= NULL
;
317 static int acp_pdm_dai_trigger(struct snd_pcm_substream
*substream
,
318 int cmd
, struct snd_soc_dai
*dai
)
320 struct pdm_stream_instance
*rtd
;
323 unsigned int ch_mask
;
325 rtd
= substream
->runtime
->private_data
;
327 switch (substream
->runtime
->channels
) {
335 case SNDRV_PCM_TRIGGER_START
:
336 case SNDRV_PCM_TRIGGER_RESUME
:
337 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE
:
338 rn_writel(ch_mask
, rtd
->acp_base
+ ACP_WOV_PDM_NO_OF_CHANNELS
);
339 rn_writel(PDM_DECIMATION_FACTOR
, rtd
->acp_base
+
340 ACP_WOV_PDM_DECIMATION_FACTOR
);
341 rtd
->bytescount
= acp_pdm_get_byte_count(rtd
,
343 pdm_status
= check_pdm_dma_status(rtd
->acp_base
);
345 ret
= start_pdm_dma(rtd
->acp_base
);
347 case SNDRV_PCM_TRIGGER_STOP
:
348 case SNDRV_PCM_TRIGGER_SUSPEND
:
349 case SNDRV_PCM_TRIGGER_PAUSE_PUSH
:
350 pdm_status
= check_pdm_dma_status(rtd
->acp_base
);
352 ret
= stop_pdm_dma(rtd
->acp_base
);
361 static struct snd_soc_dai_ops acp_pdm_dai_ops
= {
362 .trigger
= acp_pdm_dai_trigger
,
365 static struct snd_soc_dai_driver acp_pdm_dai_driver
= {
367 .rates
= SNDRV_PCM_RATE_48000
,
368 .formats
= SNDRV_PCM_FMTBIT_S24_LE
|
369 SNDRV_PCM_FMTBIT_S32_LE
,
375 .ops
= &acp_pdm_dai_ops
,
378 static const struct snd_soc_component_driver acp_pdm_component
= {
380 .open
= acp_pdm_dma_open
,
381 .close
= acp_pdm_dma_close
,
382 .hw_params
= acp_pdm_dma_hw_params
,
383 .pointer
= acp_pdm_dma_pointer
,
384 .mmap
= acp_pdm_dma_mmap
,
385 .pcm_construct
= acp_pdm_dma_new
,
388 static int acp_pdm_audio_probe(struct platform_device
*pdev
)
390 struct resource
*res
;
391 struct pdm_dev_data
*adata
;
392 unsigned int irqflags
;
395 if (!pdev
->dev
.platform_data
) {
396 dev_err(&pdev
->dev
, "platform_data not retrieved\n");
399 irqflags
= *((unsigned int *)(pdev
->dev
.platform_data
));
401 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
403 dev_err(&pdev
->dev
, "IORESOURCE_MEM FAILED\n");
407 adata
= devm_kzalloc(&pdev
->dev
, sizeof(*adata
), GFP_KERNEL
);
411 adata
->acp_base
= devm_ioremap(&pdev
->dev
, res
->start
,
413 if (!adata
->acp_base
)
416 res
= platform_get_resource(pdev
, IORESOURCE_IRQ
, 0);
418 dev_err(&pdev
->dev
, "IORESOURCE_IRQ FAILED\n");
422 adata
->pdm_irq
= res
->start
;
423 adata
->capture_stream
= NULL
;
425 dev_set_drvdata(&pdev
->dev
, adata
);
426 status
= devm_snd_soc_register_component(&pdev
->dev
,
428 &acp_pdm_dai_driver
, 1);
430 dev_err(&pdev
->dev
, "Fail to register acp pdm dai\n");
434 status
= devm_request_irq(&pdev
->dev
, adata
->pdm_irq
, pdm_irq_handler
,
435 irqflags
, "ACP_PDM_IRQ", adata
);
437 dev_err(&pdev
->dev
, "ACP PDM IRQ request failed\n");
440 pm_runtime_set_autosuspend_delay(&pdev
->dev
, ACP_SUSPEND_DELAY_MS
);
441 pm_runtime_use_autosuspend(&pdev
->dev
);
442 pm_runtime_enable(&pdev
->dev
);
443 pm_runtime_allow(&pdev
->dev
);
447 static int acp_pdm_audio_remove(struct platform_device
*pdev
)
449 pm_runtime_disable(&pdev
->dev
);
453 static int acp_pdm_resume(struct device
*dev
)
455 struct pdm_dev_data
*adata
;
456 struct snd_pcm_runtime
*runtime
;
457 struct pdm_stream_instance
*rtd
;
458 u32 period_bytes
, buffer_len
;
460 adata
= dev_get_drvdata(dev
);
461 if (adata
->capture_stream
&& adata
->capture_stream
->runtime
) {
462 runtime
= adata
->capture_stream
->runtime
;
463 rtd
= runtime
->private_data
;
464 period_bytes
= frames_to_bytes(runtime
, runtime
->period_size
);
465 buffer_len
= frames_to_bytes(runtime
, runtime
->buffer_size
);
466 config_acp_dma(rtd
, SNDRV_PCM_STREAM_CAPTURE
);
467 init_pdm_ring_buffer(MEM_WINDOW_START
, buffer_len
, period_bytes
,
470 enable_pdm_interrupts(adata
->acp_base
);
474 static int acp_pdm_runtime_suspend(struct device
*dev
)
476 struct pdm_dev_data
*adata
;
478 adata
= dev_get_drvdata(dev
);
479 disable_pdm_interrupts(adata
->acp_base
);
484 static int acp_pdm_runtime_resume(struct device
*dev
)
486 struct pdm_dev_data
*adata
;
488 adata
= dev_get_drvdata(dev
);
489 enable_pdm_interrupts(adata
->acp_base
);
493 static const struct dev_pm_ops acp_pdm_pm_ops
= {
494 .runtime_suspend
= acp_pdm_runtime_suspend
,
495 .runtime_resume
= acp_pdm_runtime_resume
,
496 .resume
= acp_pdm_resume
,
499 static struct platform_driver acp_pdm_dma_driver
= {
500 .probe
= acp_pdm_audio_probe
,
501 .remove
= acp_pdm_audio_remove
,
503 .name
= "acp_rn_pdm_dma",
504 .pm
= &acp_pdm_pm_ops
,
508 module_platform_driver(acp_pdm_dma_driver
);
510 MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
511 MODULE_DESCRIPTION("AMD ACP3x Renior PDM Driver");
512 MODULE_LICENSE("GPL v2");
513 MODULE_ALIAS("platform:" DRV_NAME
);