1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * AMD ALSA SoC Pink Sardine PDM Driver |
4 | * |
5 | * Copyright 2022 Advanced Micro Devices, Inc. |
6 | */ |
7 | |
8 | #include <linux/platform_device.h> |
9 | #include <linux/module.h> |
10 | #include <linux/bitfield.h> |
11 | #include <linux/err.h> |
12 | #include <linux/io.h> |
13 | #include <sound/pcm_params.h> |
14 | #include <sound/soc.h> |
15 | #include <sound/soc-dai.h> |
16 | #include <linux/pm_runtime.h> |
17 | |
18 | #include "acp63.h" |
19 | |
20 | #define DRV_NAME "acp_ps_pdm_dma" |
21 | |
22 | static int pdm_gain = 3; |
23 | module_param(pdm_gain, int, 0644); |
24 | MODULE_PARM_DESC(pdm_gain, "Gain control (0-3)" ); |
25 | |
26 | static const struct snd_pcm_hardware acp63_pdm_hardware_capture = { |
27 | .info = SNDRV_PCM_INFO_INTERLEAVED | |
28 | SNDRV_PCM_INFO_BLOCK_TRANSFER | |
29 | SNDRV_PCM_INFO_MMAP | |
30 | SNDRV_PCM_INFO_MMAP_VALID | |
31 | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, |
32 | .formats = SNDRV_PCM_FMTBIT_S32_LE, |
33 | .channels_min = 2, |
34 | .channels_max = 2, |
35 | .rates = SNDRV_PCM_RATE_48000, |
36 | .rate_min = 48000, |
37 | .rate_max = 48000, |
38 | .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE, |
39 | .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, |
40 | .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, |
41 | .periods_min = CAPTURE_MIN_NUM_PERIODS, |
42 | .periods_max = CAPTURE_MAX_NUM_PERIODS, |
43 | }; |
44 | |
45 | static void acp63_init_pdm_ring_buffer(u32 physical_addr, u32 buffer_size, |
46 | u32 watermark_size, void __iomem *acp_base) |
47 | { |
48 | writel(val: physical_addr, addr: acp_base + ACP_WOV_RX_RINGBUFADDR); |
49 | writel(val: buffer_size, addr: acp_base + ACP_WOV_RX_RINGBUFSIZE); |
50 | writel(val: watermark_size, addr: acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE); |
51 | writel(val: 0x01, addr: acp_base + ACPAXI2AXI_ATU_CTRL); |
52 | } |
53 | |
54 | static void acp63_enable_pdm_clock(void __iomem *acp_base) |
55 | { |
56 | u32 pdm_clk_enable, pdm_ctrl; |
57 | |
58 | pdm_clk_enable = ACP_PDM_CLK_FREQ_MASK; |
59 | pdm_ctrl = 0x00; |
60 | |
61 | writel(val: pdm_clk_enable, addr: acp_base + ACP_WOV_CLK_CTRL); |
62 | pdm_ctrl = readl(addr: acp_base + ACP_WOV_MISC_CTRL); |
63 | pdm_ctrl &= ~ACP_WOV_GAIN_CONTROL; |
64 | pdm_ctrl |= FIELD_PREP(ACP_WOV_GAIN_CONTROL, clamp(pdm_gain, 0, 3)); |
65 | writel(val: pdm_ctrl, addr: acp_base + ACP_WOV_MISC_CTRL); |
66 | } |
67 | |
68 | static void acp63_enable_pdm_interrupts(struct pdm_dev_data *adata) |
69 | { |
70 | u32 ext_int_ctrl; |
71 | |
72 | mutex_lock(adata->acp_lock); |
73 | ext_int_ctrl = readl(addr: adata->acp63_base + ACP_EXTERNAL_INTR_CNTL); |
74 | ext_int_ctrl |= PDM_DMA_INTR_MASK; |
75 | writel(val: ext_int_ctrl, addr: adata->acp63_base + ACP_EXTERNAL_INTR_CNTL); |
76 | mutex_unlock(lock: adata->acp_lock); |
77 | } |
78 | |
79 | static void acp63_disable_pdm_interrupts(struct pdm_dev_data *adata) |
80 | { |
81 | u32 ext_int_ctrl; |
82 | |
83 | mutex_lock(adata->acp_lock); |
84 | ext_int_ctrl = readl(addr: adata->acp63_base + ACP_EXTERNAL_INTR_CNTL); |
85 | ext_int_ctrl &= ~PDM_DMA_INTR_MASK; |
86 | writel(val: ext_int_ctrl, addr: adata->acp63_base + ACP_EXTERNAL_INTR_CNTL); |
87 | mutex_unlock(lock: adata->acp_lock); |
88 | } |
89 | |
90 | static bool acp63_check_pdm_dma_status(void __iomem *acp_base) |
91 | { |
92 | bool pdm_dma_status; |
93 | u32 pdm_enable, pdm_dma_enable; |
94 | |
95 | pdm_dma_status = false; |
96 | pdm_enable = readl(addr: acp_base + ACP_WOV_PDM_ENABLE); |
97 | pdm_dma_enable = readl(addr: acp_base + ACP_WOV_PDM_DMA_ENABLE); |
98 | if ((pdm_enable & ACP_PDM_ENABLE) && (pdm_dma_enable & ACP_PDM_DMA_EN_STATUS)) |
99 | pdm_dma_status = true; |
100 | |
101 | return pdm_dma_status; |
102 | } |
103 | |
104 | static int acp63_start_pdm_dma(void __iomem *acp_base) |
105 | { |
106 | u32 pdm_enable; |
107 | u32 pdm_dma_enable; |
108 | int timeout; |
109 | |
110 | pdm_enable = 0x01; |
111 | pdm_dma_enable = 0x01; |
112 | |
113 | acp63_enable_pdm_clock(acp_base); |
114 | writel(val: pdm_enable, addr: acp_base + ACP_WOV_PDM_ENABLE); |
115 | writel(val: pdm_dma_enable, addr: acp_base + ACP_WOV_PDM_DMA_ENABLE); |
116 | timeout = 0; |
117 | while (++timeout < ACP_COUNTER) { |
118 | pdm_dma_enable = readl(addr: acp_base + ACP_WOV_PDM_DMA_ENABLE); |
119 | if ((pdm_dma_enable & 0x02) == ACP_PDM_DMA_EN_STATUS) |
120 | return 0; |
121 | udelay(DELAY_US); |
122 | } |
123 | return -ETIMEDOUT; |
124 | } |
125 | |
126 | static int acp63_stop_pdm_dma(void __iomem *acp_base) |
127 | { |
128 | u32 pdm_enable, pdm_dma_enable; |
129 | int timeout; |
130 | |
131 | pdm_enable = 0x00; |
132 | pdm_dma_enable = 0x00; |
133 | |
134 | pdm_enable = readl(addr: acp_base + ACP_WOV_PDM_ENABLE); |
135 | pdm_dma_enable = readl(addr: acp_base + ACP_WOV_PDM_DMA_ENABLE); |
136 | if (pdm_dma_enable & 0x01) { |
137 | pdm_dma_enable = 0x02; |
138 | writel(val: pdm_dma_enable, addr: acp_base + ACP_WOV_PDM_DMA_ENABLE); |
139 | timeout = 0; |
140 | while (++timeout < ACP_COUNTER) { |
141 | pdm_dma_enable = readl(addr: acp_base + ACP_WOV_PDM_DMA_ENABLE); |
142 | if ((pdm_dma_enable & 0x02) == 0x00) |
143 | break; |
144 | udelay(DELAY_US); |
145 | } |
146 | if (timeout == ACP_COUNTER) |
147 | return -ETIMEDOUT; |
148 | } |
149 | if (pdm_enable == ACP_PDM_ENABLE) { |
150 | pdm_enable = ACP_PDM_DISABLE; |
151 | writel(val: pdm_enable, addr: acp_base + ACP_WOV_PDM_ENABLE); |
152 | } |
153 | writel(val: 0x01, addr: acp_base + ACP_WOV_PDM_FIFO_FLUSH); |
154 | return 0; |
155 | } |
156 | |
157 | static void acp63_config_dma(struct pdm_stream_instance *rtd, int direction) |
158 | { |
159 | u16 page_idx; |
160 | u32 low, high, val; |
161 | dma_addr_t addr; |
162 | |
163 | addr = rtd->dma_addr; |
164 | val = PDM_PTE_OFFSET; |
165 | |
166 | /* Group Enable */ |
167 | writel(ACP_SRAM_PTE_OFFSET | BIT(31), addr: rtd->acp63_base + ACPAXI2AXI_ATU_BASE_ADDR_GRP_1); |
168 | writel(PAGE_SIZE_4K_ENABLE, addr: rtd->acp63_base + ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1); |
169 | for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) { |
170 | /* Load the low address of page int ACP SRAM through SRBM */ |
171 | low = lower_32_bits(addr); |
172 | high = upper_32_bits(addr); |
173 | |
174 | writel(val: low, addr: rtd->acp63_base + ACP_SCRATCH_REG_0 + val); |
175 | high |= BIT(31); |
176 | writel(val: high, addr: rtd->acp63_base + ACP_SCRATCH_REG_0 + val + 4); |
177 | val += 8; |
178 | addr += PAGE_SIZE; |
179 | } |
180 | } |
181 | |
182 | static int acp63_pdm_dma_open(struct snd_soc_component *component, |
183 | struct snd_pcm_substream *substream) |
184 | { |
185 | struct snd_pcm_runtime *runtime; |
186 | struct pdm_dev_data *adata; |
187 | struct pdm_stream_instance *pdm_data; |
188 | int ret; |
189 | |
190 | runtime = substream->runtime; |
191 | adata = dev_get_drvdata(dev: component->dev); |
192 | pdm_data = kzalloc(size: sizeof(*pdm_data), GFP_KERNEL); |
193 | if (!pdm_data) |
194 | return -EINVAL; |
195 | |
196 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
197 | runtime->hw = acp63_pdm_hardware_capture; |
198 | |
199 | ret = snd_pcm_hw_constraint_integer(runtime, |
200 | SNDRV_PCM_HW_PARAM_PERIODS); |
201 | if (ret < 0) { |
202 | dev_err(component->dev, "set integer constraint failed\n" ); |
203 | kfree(objp: pdm_data); |
204 | return ret; |
205 | } |
206 | |
207 | acp63_enable_pdm_interrupts(adata); |
208 | |
209 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
210 | adata->capture_stream = substream; |
211 | |
212 | pdm_data->acp63_base = adata->acp63_base; |
213 | runtime->private_data = pdm_data; |
214 | return ret; |
215 | } |
216 | |
217 | static int acp63_pdm_dma_hw_params(struct snd_soc_component *component, |
218 | struct snd_pcm_substream *substream, |
219 | struct snd_pcm_hw_params *params) |
220 | { |
221 | struct pdm_stream_instance *rtd; |
222 | size_t size, period_bytes; |
223 | |
224 | rtd = substream->runtime->private_data; |
225 | if (!rtd) |
226 | return -EINVAL; |
227 | size = params_buffer_bytes(p: params); |
228 | period_bytes = params_period_bytes(p: params); |
229 | rtd->dma_addr = substream->runtime->dma_addr; |
230 | rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT); |
231 | acp63_config_dma(rtd, direction: substream->stream); |
232 | acp63_init_pdm_ring_buffer(PDM_MEM_WINDOW_START, buffer_size: size, |
233 | watermark_size: period_bytes, acp_base: rtd->acp63_base); |
234 | return 0; |
235 | } |
236 | |
237 | static u64 acp63_pdm_get_byte_count(struct pdm_stream_instance *rtd, |
238 | int direction) |
239 | { |
240 | u32 high, low; |
241 | u64 byte_count; |
242 | |
243 | high = readl(addr: rtd->acp63_base + ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH); |
244 | byte_count = high; |
245 | low = readl(addr: rtd->acp63_base + ACP_WOV_RX_LINEARPOSITIONCNTR_LOW); |
246 | byte_count = (byte_count << 32) | low; |
247 | return byte_count; |
248 | } |
249 | |
250 | static snd_pcm_uframes_t acp63_pdm_dma_pointer(struct snd_soc_component *comp, |
251 | struct snd_pcm_substream *stream) |
252 | { |
253 | struct pdm_stream_instance *rtd; |
254 | u32 pos, buffersize; |
255 | u64 bytescount; |
256 | |
257 | rtd = stream->runtime->private_data; |
258 | buffersize = frames_to_bytes(runtime: stream->runtime, |
259 | size: stream->runtime->buffer_size); |
260 | bytescount = acp63_pdm_get_byte_count(rtd, direction: stream->stream); |
261 | if (bytescount > rtd->bytescount) |
262 | bytescount -= rtd->bytescount; |
263 | pos = do_div(bytescount, buffersize); |
264 | return bytes_to_frames(runtime: stream->runtime, size: pos); |
265 | } |
266 | |
267 | static int acp63_pdm_dma_new(struct snd_soc_component *component, |
268 | struct snd_soc_pcm_runtime *rtd) |
269 | { |
270 | struct device *parent = component->dev->parent; |
271 | |
272 | snd_pcm_set_managed_buffer_all(pcm: rtd->pcm, SNDRV_DMA_TYPE_DEV, |
273 | data: parent, MIN_BUFFER, MAX_BUFFER); |
274 | return 0; |
275 | } |
276 | |
277 | static int acp63_pdm_dma_close(struct snd_soc_component *component, |
278 | struct snd_pcm_substream *substream) |
279 | { |
280 | struct pdm_dev_data *adata = dev_get_drvdata(dev: component->dev); |
281 | struct snd_pcm_runtime *runtime = substream->runtime; |
282 | |
283 | acp63_disable_pdm_interrupts(adata); |
284 | adata->capture_stream = NULL; |
285 | kfree(objp: runtime->private_data); |
286 | return 0; |
287 | } |
288 | |
289 | static int acp63_pdm_dai_trigger(struct snd_pcm_substream *substream, |
290 | int cmd, struct snd_soc_dai *dai) |
291 | { |
292 | struct pdm_stream_instance *rtd; |
293 | int ret; |
294 | bool pdm_status; |
295 | unsigned int ch_mask; |
296 | |
297 | rtd = substream->runtime->private_data; |
298 | ret = 0; |
299 | switch (substream->runtime->channels) { |
300 | case TWO_CH: |
301 | ch_mask = 0x00; |
302 | break; |
303 | default: |
304 | return -EINVAL; |
305 | } |
306 | switch (cmd) { |
307 | case SNDRV_PCM_TRIGGER_START: |
308 | case SNDRV_PCM_TRIGGER_RESUME: |
309 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
310 | writel(val: ch_mask, addr: rtd->acp63_base + ACP_WOV_PDM_NO_OF_CHANNELS); |
311 | writel(PDM_DECIMATION_FACTOR, addr: rtd->acp63_base + ACP_WOV_PDM_DECIMATION_FACTOR); |
312 | rtd->bytescount = acp63_pdm_get_byte_count(rtd, direction: substream->stream); |
313 | pdm_status = acp63_check_pdm_dma_status(acp_base: rtd->acp63_base); |
314 | if (!pdm_status) |
315 | ret = acp63_start_pdm_dma(acp_base: rtd->acp63_base); |
316 | break; |
317 | case SNDRV_PCM_TRIGGER_STOP: |
318 | case SNDRV_PCM_TRIGGER_SUSPEND: |
319 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
320 | pdm_status = acp63_check_pdm_dma_status(acp_base: rtd->acp63_base); |
321 | if (pdm_status) |
322 | ret = acp63_stop_pdm_dma(acp_base: rtd->acp63_base); |
323 | break; |
324 | default: |
325 | ret = -EINVAL; |
326 | break; |
327 | } |
328 | return ret; |
329 | } |
330 | |
331 | static const struct snd_soc_dai_ops acp63_pdm_dai_ops = { |
332 | .trigger = acp63_pdm_dai_trigger, |
333 | }; |
334 | |
335 | static struct snd_soc_dai_driver acp63_pdm_dai_driver = { |
336 | .name = "acp_ps_pdm_dma.0" , |
337 | .capture = { |
338 | .rates = SNDRV_PCM_RATE_48000, |
339 | .formats = SNDRV_PCM_FMTBIT_S32_LE, |
340 | .channels_min = 2, |
341 | .channels_max = 2, |
342 | .rate_min = 48000, |
343 | .rate_max = 48000, |
344 | }, |
345 | .ops = &acp63_pdm_dai_ops, |
346 | }; |
347 | |
348 | static const struct snd_soc_component_driver acp63_pdm_component = { |
349 | .name = DRV_NAME, |
350 | .open = acp63_pdm_dma_open, |
351 | .close = acp63_pdm_dma_close, |
352 | .hw_params = acp63_pdm_dma_hw_params, |
353 | .pointer = acp63_pdm_dma_pointer, |
354 | .pcm_construct = acp63_pdm_dma_new, |
355 | }; |
356 | |
357 | static int acp63_pdm_audio_probe(struct platform_device *pdev) |
358 | { |
359 | struct resource *res; |
360 | struct pdm_dev_data *adata; |
361 | struct acp63_dev_data *acp_data; |
362 | struct device *parent; |
363 | int status; |
364 | |
365 | parent = pdev->dev.parent; |
366 | acp_data = dev_get_drvdata(dev: parent); |
367 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
368 | if (!res) { |
369 | dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n" ); |
370 | return -ENODEV; |
371 | } |
372 | |
373 | adata = devm_kzalloc(dev: &pdev->dev, size: sizeof(*adata), GFP_KERNEL); |
374 | if (!adata) |
375 | return -ENOMEM; |
376 | |
377 | adata->acp63_base = devm_ioremap(dev: &pdev->dev, offset: res->start, size: resource_size(res)); |
378 | if (!adata->acp63_base) |
379 | return -ENOMEM; |
380 | |
381 | adata->capture_stream = NULL; |
382 | adata->acp_lock = &acp_data->acp_lock; |
383 | dev_set_drvdata(dev: &pdev->dev, data: adata); |
384 | status = devm_snd_soc_register_component(dev: &pdev->dev, |
385 | component_driver: &acp63_pdm_component, |
386 | dai_drv: &acp63_pdm_dai_driver, num_dai: 1); |
387 | if (status) { |
388 | dev_err(&pdev->dev, "Fail to register acp pdm dai\n" ); |
389 | |
390 | return -ENODEV; |
391 | } |
392 | pm_runtime_set_autosuspend_delay(dev: &pdev->dev, ACP_SUSPEND_DELAY_MS); |
393 | pm_runtime_use_autosuspend(dev: &pdev->dev); |
394 | pm_runtime_mark_last_busy(dev: &pdev->dev); |
395 | pm_runtime_set_active(dev: &pdev->dev); |
396 | pm_runtime_enable(dev: &pdev->dev); |
397 | return 0; |
398 | } |
399 | |
400 | static void acp63_pdm_audio_remove(struct platform_device *pdev) |
401 | { |
402 | pm_runtime_disable(dev: &pdev->dev); |
403 | } |
404 | |
405 | static int __maybe_unused acp63_pdm_resume(struct device *dev) |
406 | { |
407 | struct pdm_dev_data *adata; |
408 | struct snd_pcm_runtime *runtime; |
409 | struct pdm_stream_instance *rtd; |
410 | u32 period_bytes, buffer_len; |
411 | |
412 | adata = dev_get_drvdata(dev); |
413 | if (adata->capture_stream && adata->capture_stream->runtime) { |
414 | runtime = adata->capture_stream->runtime; |
415 | rtd = runtime->private_data; |
416 | period_bytes = frames_to_bytes(runtime, size: runtime->period_size); |
417 | buffer_len = frames_to_bytes(runtime, size: runtime->buffer_size); |
418 | acp63_config_dma(rtd, direction: SNDRV_PCM_STREAM_CAPTURE); |
419 | acp63_init_pdm_ring_buffer(PDM_MEM_WINDOW_START, buffer_size: buffer_len, |
420 | watermark_size: period_bytes, acp_base: adata->acp63_base); |
421 | } |
422 | acp63_enable_pdm_interrupts(adata); |
423 | return 0; |
424 | } |
425 | |
426 | static int __maybe_unused acp63_pdm_suspend(struct device *dev) |
427 | { |
428 | struct pdm_dev_data *adata; |
429 | |
430 | adata = dev_get_drvdata(dev); |
431 | acp63_disable_pdm_interrupts(adata); |
432 | return 0; |
433 | } |
434 | |
435 | static int __maybe_unused acp63_pdm_runtime_resume(struct device *dev) |
436 | { |
437 | struct pdm_dev_data *adata; |
438 | |
439 | adata = dev_get_drvdata(dev); |
440 | acp63_enable_pdm_interrupts(adata); |
441 | return 0; |
442 | } |
443 | |
444 | static const struct dev_pm_ops acp63_pdm_pm_ops = { |
445 | SET_RUNTIME_PM_OPS(acp63_pdm_suspend, acp63_pdm_runtime_resume, NULL) |
446 | SET_SYSTEM_SLEEP_PM_OPS(acp63_pdm_suspend, acp63_pdm_resume) |
447 | }; |
448 | |
449 | static struct platform_driver acp63_pdm_dma_driver = { |
450 | .probe = acp63_pdm_audio_probe, |
451 | .remove_new = acp63_pdm_audio_remove, |
452 | .driver = { |
453 | .name = "acp_ps_pdm_dma" , |
454 | .pm = &acp63_pdm_pm_ops, |
455 | }, |
456 | }; |
457 | |
458 | module_platform_driver(acp63_pdm_dma_driver); |
459 | |
460 | MODULE_AUTHOR("Syed.SabaKareem@amd.com" ); |
461 | MODULE_DESCRIPTION("AMD PINK SARDINE PDM Driver" ); |
462 | MODULE_LICENSE("GPL v2" ); |
463 | MODULE_ALIAS("platform:" DRV_NAME); |
464 | |