~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/sound/soc/amd/renoir/acp3x-pdm-dma.c

Version: ~ [ linux-6.11.5 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.58 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.114 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.169 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.228 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.284 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.322 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.9 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  1 // SPDX-License-Identifier: GPL-2.0+
  2 //
  3 // AMD ALSA SoC PDM Driver
  4 //
  5 //Copyright 2020 Advanced Micro Devices, Inc.
  6 
  7 #include <linux/platform_device.h>
  8 #include <linux/module.h>
  9 #include <linux/bitfield.h>
 10 #include <linux/err.h>
 11 #include <linux/io.h>
 12 #include <linux/pm_runtime.h>
 13 #include <sound/pcm_params.h>
 14 #include <sound/soc.h>
 15 #include <sound/soc-dai.h>
 16 
 17 #include "rn_acp3x.h"
 18 
 19 #define DRV_NAME "acp_rn_pdm_dma"
 20 
 21 static int pdm_gain = 3;
 22 module_param(pdm_gain, int, 0644);
 23 MODULE_PARM_DESC(pdm_gain, "Gain control (0-3)");
 24 
 25 static const struct snd_pcm_hardware acp_pdm_hardware_capture = {
 26         .info = SNDRV_PCM_INFO_INTERLEAVED |
 27                 SNDRV_PCM_INFO_BLOCK_TRANSFER |
 28                 SNDRV_PCM_INFO_MMAP |
 29                 SNDRV_PCM_INFO_MMAP_VALID |
 30                 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
 31         .formats = SNDRV_PCM_FMTBIT_S32_LE,
 32         .channels_min = 2,
 33         .channels_max = 2,
 34         .rates = SNDRV_PCM_RATE_48000,
 35         .rate_min = 48000,
 36         .rate_max = 48000,
 37         .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
 38         .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
 39         .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
 40         .periods_min = CAPTURE_MIN_NUM_PERIODS,
 41         .periods_max = CAPTURE_MAX_NUM_PERIODS,
 42 };
 43 
 44 static irqreturn_t pdm_irq_handler(int irq, void *dev_id)
 45 {
 46         struct pdm_dev_data *rn_pdm_data;
 47         u16 cap_flag;
 48         u32 val;
 49 
 50         rn_pdm_data = dev_id;
 51         if (!rn_pdm_data)
 52                 return IRQ_NONE;
 53 
 54         cap_flag = 0;
 55         val = rn_readl(rn_pdm_data->acp_base + ACP_EXTERNAL_INTR_STAT);
 56         if ((val & BIT(PDM_DMA_STAT)) && rn_pdm_data->capture_stream) {
 57                 rn_writel(BIT(PDM_DMA_STAT), rn_pdm_data->acp_base +
 58                           ACP_EXTERNAL_INTR_STAT);
 59                 snd_pcm_period_elapsed(rn_pdm_data->capture_stream);
 60                 cap_flag = 1;
 61         }
 62 
 63         if (cap_flag)
 64                 return IRQ_HANDLED;
 65         else
 66                 return IRQ_NONE;
 67 }
 68 
 69 static void init_pdm_ring_buffer(u32 physical_addr,
 70                                  u32 buffer_size,
 71                                  u32 watermark_size,
 72                                  void __iomem *acp_base)
 73 {
 74         rn_writel(physical_addr, acp_base + ACP_WOV_RX_RINGBUFADDR);
 75         rn_writel(buffer_size, acp_base + ACP_WOV_RX_RINGBUFSIZE);
 76         rn_writel(watermark_size, acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE);
 77         rn_writel(0x01, acp_base + ACPAXI2AXI_ATU_CTRL);
 78 }
 79 
 80 static void enable_pdm_clock(void __iomem *acp_base)
 81 {
 82         u32 pdm_clk_enable, pdm_ctrl;
 83 
 84         pdm_clk_enable = ACP_PDM_CLK_FREQ_MASK;
 85 
 86         rn_writel(pdm_clk_enable, acp_base + ACP_WOV_CLK_CTRL);
 87         pdm_ctrl = rn_readl(acp_base + ACP_WOV_MISC_CTRL);
 88         pdm_ctrl &= ~ACP_WOV_GAIN_CONTROL;
 89         pdm_ctrl |= FIELD_PREP(ACP_WOV_GAIN_CONTROL, clamp(pdm_gain, 0, 3));
 90         rn_writel(pdm_ctrl, acp_base + ACP_WOV_MISC_CTRL);
 91 }
 92 
 93 static void enable_pdm_interrupts(void __iomem *acp_base)
 94 {
 95         u32 ext_int_ctrl;
 96 
 97         ext_int_ctrl = rn_readl(acp_base + ACP_EXTERNAL_INTR_CNTL);
 98         ext_int_ctrl |= PDM_DMA_INTR_MASK;
 99         rn_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL);
100 }
101 
102 static void disable_pdm_interrupts(void __iomem *acp_base)
103 {
104         u32 ext_int_ctrl;
105 
106         ext_int_ctrl = rn_readl(acp_base + ACP_EXTERNAL_INTR_CNTL);
107         ext_int_ctrl |= ~PDM_DMA_INTR_MASK;
108         rn_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL);
109 }
110 
111 static bool check_pdm_dma_status(void __iomem *acp_base)
112 {
113         bool pdm_dma_status;
114         u32 pdm_enable, pdm_dma_enable;
115 
116         pdm_dma_status = false;
117         pdm_enable = rn_readl(acp_base + ACP_WOV_PDM_ENABLE);
118         pdm_dma_enable = rn_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
119         if ((pdm_enable & ACP_PDM_ENABLE) && (pdm_dma_enable &
120              ACP_PDM_DMA_EN_STATUS))
121                 pdm_dma_status = true;
122         return pdm_dma_status;
123 }
124 
125 static int start_pdm_dma(void __iomem *acp_base)
126 {
127         u32 pdm_enable;
128         u32 pdm_dma_enable;
129         int timeout;
130 
131         pdm_enable = 0x01;
132         pdm_dma_enable  = 0x01;
133 
134         enable_pdm_clock(acp_base);
135         rn_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE);
136         rn_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE);
137         timeout = 0;
138         while (++timeout < ACP_COUNTER) {
139                 pdm_dma_enable = rn_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
140                 if ((pdm_dma_enable & 0x02) == ACP_PDM_DMA_EN_STATUS)
141                         return 0;
142                 udelay(DELAY_US);
143         }
144         return -ETIMEDOUT;
145 }
146 
147 static int stop_pdm_dma(void __iomem *acp_base)
148 {
149         u32 pdm_enable, pdm_dma_enable;
150         int timeout;
151 
152         pdm_enable = rn_readl(acp_base + ACP_WOV_PDM_ENABLE);
153         pdm_dma_enable = rn_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
154         if (pdm_dma_enable & 0x01) {
155                 pdm_dma_enable = 0x02;
156                 rn_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE);
157                 timeout = 0;
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)
162                                 break;
163                         udelay(DELAY_US);
164                 }
165                 if (timeout == ACP_COUNTER)
166                         return -ETIMEDOUT;
167         }
168         if (pdm_enable == ACP_PDM_ENABLE) {
169                 pdm_enable = ACP_PDM_DISABLE;
170                 rn_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE);
171         }
172         rn_writel(0x01, acp_base + ACP_WOV_PDM_FIFO_FLUSH);
173         return 0;
174 }
175 
176 static void config_acp_dma(struct pdm_stream_instance *rtd, int direction)
177 {
178         u16 page_idx;
179         u32 low, high, val;
180         dma_addr_t addr;
181 
182         addr = rtd->dma_addr;
183         val = 0;
184 
185         /* Group Enable */
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);
190 
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);
195 
196                 rn_writel(low, rtd->acp_base + ACP_SCRATCH_REG_0 + val);
197                 high |= BIT(31);
198                 rn_writel(high, rtd->acp_base + ACP_SCRATCH_REG_0 + val + 4);
199                 val += 8;
200                 addr += PAGE_SIZE;
201         }
202 }
203 
204 static int acp_pdm_dma_open(struct snd_soc_component *component,
205                             struct snd_pcm_substream *substream)
206 {
207         struct snd_pcm_runtime *runtime;
208         struct pdm_dev_data *adata;
209         struct pdm_stream_instance *pdm_data;
210         int ret;
211 
212         runtime = substream->runtime;
213         adata = dev_get_drvdata(component->dev);
214         pdm_data = kzalloc(sizeof(*pdm_data), GFP_KERNEL);
215         if (!pdm_data)
216                 return -EINVAL;
217 
218         if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
219                 runtime->hw = acp_pdm_hardware_capture;
220 
221         ret = snd_pcm_hw_constraint_integer(runtime,
222                                             SNDRV_PCM_HW_PARAM_PERIODS);
223         if (ret < 0) {
224                 dev_err(component->dev, "set integer constraint failed\n");
225                 kfree(pdm_data);
226                 return ret;
227         }
228 
229         enable_pdm_interrupts(adata->acp_base);
230 
231         if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
232                 adata->capture_stream = substream;
233 
234         pdm_data->acp_base = adata->acp_base;
235         runtime->private_data = pdm_data;
236         return ret;
237 }
238 
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)
242 {
243         struct pdm_stream_instance *rtd;
244         size_t size, period_bytes;
245 
246         rtd = substream->runtime->private_data;
247         if (!rtd)
248                 return -EINVAL;
249         size = params_buffer_bytes(params);
250         period_bytes = params_period_bytes(params);
251         rtd->dma_addr = substream->runtime->dma_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,
255                              rtd->acp_base);
256         return 0;
257 }
258 
259 static u64 acp_pdm_get_byte_count(struct pdm_stream_instance *rtd,
260                                   int direction)
261 {
262         union acp_pdm_dma_count byte_count;
263 
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;
271 }
272 
273 static snd_pcm_uframes_t acp_pdm_dma_pointer(struct snd_soc_component *comp,
274                                              struct snd_pcm_substream *stream)
275 {
276         struct pdm_stream_instance *rtd;
277         u32 pos, buffersize;
278         u64 bytescount;
279 
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);
288 }
289 
290 static int acp_pdm_dma_new(struct snd_soc_component *component,
291                            struct snd_soc_pcm_runtime *rtd)
292 {
293         struct device *parent = component->dev->parent;
294 
295         snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
296                                        parent, MIN_BUFFER, MAX_BUFFER);
297         return 0;
298 }
299 
300 static int acp_pdm_dma_close(struct snd_soc_component *component,
301                              struct snd_pcm_substream *substream)
302 {
303         struct pdm_dev_data *adata = dev_get_drvdata(component->dev);
304 
305         disable_pdm_interrupts(adata->acp_base);
306         adata->capture_stream = NULL;
307         return 0;
308 }
309 
310 static int acp_pdm_dai_trigger(struct snd_pcm_substream *substream,
311                                int cmd, struct snd_soc_dai *dai)
312 {
313         struct pdm_stream_instance *rtd;
314         int ret;
315         bool pdm_status;
316         unsigned int ch_mask;
317 
318         rtd = substream->runtime->private_data;
319         ret = 0;
320         switch (substream->runtime->channels) {
321         case TWO_CH:
322                 ch_mask = 0x00;
323                 break;
324         default:
325                 return -EINVAL;
326         }
327         switch (cmd) {
328         case SNDRV_PCM_TRIGGER_START:
329         case SNDRV_PCM_TRIGGER_RESUME:
330         case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
331                 rn_writel(ch_mask, rtd->acp_base + ACP_WOV_PDM_NO_OF_CHANNELS);
332                 rn_writel(PDM_DECIMATION_FACTOR, rtd->acp_base +
333                           ACP_WOV_PDM_DECIMATION_FACTOR);
334                 rtd->bytescount = acp_pdm_get_byte_count(rtd,
335                                                          substream->stream);
336                 pdm_status = check_pdm_dma_status(rtd->acp_base);
337                 if (!pdm_status)
338                         ret = start_pdm_dma(rtd->acp_base);
339                 break;
340         case SNDRV_PCM_TRIGGER_STOP:
341         case SNDRV_PCM_TRIGGER_SUSPEND:
342         case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
343                 pdm_status = check_pdm_dma_status(rtd->acp_base);
344                 if (pdm_status)
345                         ret = stop_pdm_dma(rtd->acp_base);
346                 break;
347         default:
348                 ret = -EINVAL;
349                 break;
350         }
351         return ret;
352 }
353 
354 static const struct snd_soc_dai_ops acp_pdm_dai_ops = {
355         .trigger   = acp_pdm_dai_trigger,
356 };
357 
358 static struct snd_soc_dai_driver acp_pdm_dai_driver = {
359         .capture = {
360                 .rates = SNDRV_PCM_RATE_48000,
361                 .formats = SNDRV_PCM_FMTBIT_S24_LE |
362                            SNDRV_PCM_FMTBIT_S32_LE,
363                 .channels_min = 2,
364                 .channels_max = 2,
365                 .rate_min = 48000,
366                 .rate_max = 48000,
367         },
368         .ops = &acp_pdm_dai_ops,
369 };
370 
371 static const struct snd_soc_component_driver acp_pdm_component = {
372         .name                   = DRV_NAME,
373         .open                   = acp_pdm_dma_open,
374         .close                  = acp_pdm_dma_close,
375         .hw_params              = acp_pdm_dma_hw_params,
376         .pointer                = acp_pdm_dma_pointer,
377         .pcm_construct          = acp_pdm_dma_new,
378         .legacy_dai_naming      = 1,
379 };
380 
381 static int acp_pdm_audio_probe(struct platform_device *pdev)
382 {
383         struct resource *res;
384         struct pdm_dev_data *adata;
385         unsigned int irqflags;
386         int status;
387 
388         if (!pdev->dev.platform_data) {
389                 dev_err(&pdev->dev, "platform_data not retrieved\n");
390                 return -ENODEV;
391         }
392         irqflags = *((unsigned int *)(pdev->dev.platform_data));
393 
394         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
395         if (!res) {
396                 dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
397                 return -ENODEV;
398         }
399 
400         adata = devm_kzalloc(&pdev->dev, sizeof(*adata), GFP_KERNEL);
401         if (!adata)
402                 return -ENOMEM;
403 
404         adata->acp_base = devm_ioremap(&pdev->dev, res->start,
405                                        resource_size(res));
406         if (!adata->acp_base)
407                 return -ENOMEM;
408 
409         status = platform_get_irq(pdev, 0);
410         if (status < 0)
411                 return status;
412         adata->pdm_irq = status;
413 
414         adata->capture_stream = NULL;
415 
416         dev_set_drvdata(&pdev->dev, adata);
417         status = devm_snd_soc_register_component(&pdev->dev,
418                                                  &acp_pdm_component,
419                                                  &acp_pdm_dai_driver, 1);
420         if (status) {
421                 dev_err(&pdev->dev, "Fail to register acp pdm dai\n");
422 
423                 return -ENODEV;
424         }
425         status = devm_request_irq(&pdev->dev, adata->pdm_irq, pdm_irq_handler,
426                                   irqflags, "ACP_PDM_IRQ", adata);
427         if (status) {
428                 dev_err(&pdev->dev, "ACP PDM IRQ request failed\n");
429                 return -ENODEV;
430         }
431         pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS);
432         pm_runtime_use_autosuspend(&pdev->dev);
433         pm_runtime_mark_last_busy(&pdev->dev);
434         pm_runtime_set_active(&pdev->dev);
435         pm_runtime_enable(&pdev->dev);
436         return 0;
437 }
438 
439 static void acp_pdm_audio_remove(struct platform_device *pdev)
440 {
441         pm_runtime_disable(&pdev->dev);
442 }
443 
444 static int acp_pdm_resume(struct device *dev)
445 {
446         struct pdm_dev_data *adata;
447         struct snd_pcm_runtime *runtime;
448         struct pdm_stream_instance *rtd;
449         u32 period_bytes, buffer_len;
450 
451         adata = dev_get_drvdata(dev);
452         if (adata->capture_stream && adata->capture_stream->runtime) {
453                 runtime = adata->capture_stream->runtime;
454                 rtd = runtime->private_data;
455                 period_bytes = frames_to_bytes(runtime, runtime->period_size);
456                 buffer_len = frames_to_bytes(runtime, runtime->buffer_size);
457                 config_acp_dma(rtd, SNDRV_PCM_STREAM_CAPTURE);
458                 init_pdm_ring_buffer(MEM_WINDOW_START, buffer_len, period_bytes,
459                                      adata->acp_base);
460         }
461         enable_pdm_interrupts(adata->acp_base);
462         return 0;
463 }
464 
465 static int acp_pdm_runtime_suspend(struct device *dev)
466 {
467         struct pdm_dev_data *adata;
468 
469         adata = dev_get_drvdata(dev);
470         disable_pdm_interrupts(adata->acp_base);
471 
472         return 0;
473 }
474 
475 static int acp_pdm_runtime_resume(struct device *dev)
476 {
477         struct pdm_dev_data *adata;
478 
479         adata = dev_get_drvdata(dev);
480         enable_pdm_interrupts(adata->acp_base);
481         return 0;
482 }
483 
484 static const struct dev_pm_ops acp_pdm_pm_ops = {
485         .runtime_suspend = acp_pdm_runtime_suspend,
486         .runtime_resume = acp_pdm_runtime_resume,
487         .resume = acp_pdm_resume,
488 };
489 
490 static struct platform_driver acp_pdm_dma_driver = {
491         .probe = acp_pdm_audio_probe,
492         .remove_new = acp_pdm_audio_remove,
493         .driver = {
494                 .name = "acp_rn_pdm_dma",
495                 .pm = &acp_pdm_pm_ops,
496         },
497 };
498 
499 module_platform_driver(acp_pdm_dma_driver);
500 
501 MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
502 MODULE_DESCRIPTION("AMD ACP3x Renior PDM Driver");
503 MODULE_LICENSE("GPL v2");
504 MODULE_ALIAS("platform:" DRV_NAME);
505 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php