1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) 2 // 3 // This file is provided under a dual BSD/GPLv2 license. When using or 4 // redistributing this file, you may do so under either license. 5 // 6 // Copyright(c) 2022 Advanced Micro Devices, Inc. 7 // 8 // Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> 9 // Vijendar Mukunda <Vijendar.Mukunda@amd.com> 10 // 11 12 /* 13 * Generic Hardware interface for ACP Audio PDM controller 14 */ 15 16 #include <linux/err.h> 17 #include <linux/io.h> 18 #include <linux/module.h> 19 #include <linux/platform_device.h> 20 #include <sound/pcm_params.h> 21 #include <sound/soc.h> 22 #include <sound/soc-dai.h> 23 24 #include "amd.h" 25 26 #define DRV_NAME "acp-pdm" 27 28 static int acp_dmic_prepare(struct snd_pcm_substream *substream, 29 struct snd_soc_dai *dai) 30 { 31 struct acp_stream *stream = substream->runtime->private_data; 32 struct device *dev = dai->component->dev; 33 struct acp_dev_data *adata = dev_get_drvdata(dev); 34 u32 physical_addr, size_dmic, period_bytes; 35 unsigned int dmic_ctrl; 36 37 /* Enable default DMIC clk */ 38 writel(PDM_CLK_FREQ_MASK, adata->acp_base + ACP_WOV_CLK_CTRL); 39 dmic_ctrl = readl(adata->acp_base + ACP_WOV_MISC_CTRL); 40 dmic_ctrl |= PDM_MISC_CTRL_MASK; 41 writel(dmic_ctrl, adata->acp_base + ACP_WOV_MISC_CTRL); 42 43 period_bytes = frames_to_bytes(substream->runtime, 44 substream->runtime->period_size); 45 size_dmic = frames_to_bytes(substream->runtime, 46 substream->runtime->buffer_size); 47 48 physical_addr = stream->reg_offset + MEM_WINDOW_START; 49 50 /* Init DMIC Ring buffer */ 51 writel(physical_addr, adata->acp_base + ACP_WOV_RX_RINGBUFADDR); 52 writel(size_dmic, adata->acp_base + ACP_WOV_RX_RINGBUFSIZE); 53 writel(period_bytes, adata->acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE); 54 writel(0x01, adata->acp_base + ACPAXI2AXI_ATU_CTRL); 55 56 return 0; 57 } 58 59 static int acp_dmic_dai_trigger(struct snd_pcm_substream *substream, 60 int cmd, struct snd_soc_dai *dai) 61 { 62 struct device *dev = dai->component->dev; 63 struct acp_dev_data *adata = dev_get_drvdata(dev); 64 unsigned int dma_enable; 65 int ret = 0; 66 67 switch (cmd) { 68 case SNDRV_PCM_TRIGGER_START: 69 case SNDRV_PCM_TRIGGER_RESUME: 70 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 71 dma_enable = readl(adata->acp_base + ACP_WOV_PDM_DMA_ENABLE); 72 if (!(dma_enable & DMA_EN_MASK)) { 73 writel(PDM_ENABLE, adata->acp_base + ACP_WOV_PDM_ENABLE); 74 writel(PDM_ENABLE, adata->acp_base + ACP_WOV_PDM_DMA_ENABLE); 75 } 76 77 ret = readl_poll_timeout_atomic(adata->acp_base + ACP_WOV_PDM_DMA_ENABLE, 78 dma_enable, (dma_enable & DMA_EN_MASK), 79 DELAY_US, PDM_TIMEOUT); 80 break; 81 case SNDRV_PCM_TRIGGER_STOP: 82 case SNDRV_PCM_TRIGGER_SUSPEND: 83 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 84 dma_enable = readl(adata->acp_base + ACP_WOV_PDM_DMA_ENABLE); 85 if ((dma_enable & DMA_EN_MASK)) { 86 writel(PDM_DISABLE, adata->acp_base + ACP_WOV_PDM_ENABLE); 87 writel(PDM_DISABLE, adata->acp_base + ACP_WOV_PDM_DMA_ENABLE); 88 89 } 90 91 ret = readl_poll_timeout_atomic(adata->acp_base + ACP_WOV_PDM_DMA_ENABLE, 92 dma_enable, !(dma_enable & DMA_EN_MASK), 93 DELAY_US, PDM_TIMEOUT); 94 break; 95 default: 96 ret = -EINVAL; 97 break; 98 } 99 100 return ret; 101 } 102 103 static int acp_dmic_hwparams(struct snd_pcm_substream *substream, 104 struct snd_pcm_hw_params *hwparams, struct snd_soc_dai *dai) 105 { 106 struct device *dev = dai->component->dev; 107 struct acp_dev_data *adata = dev_get_drvdata(dev); 108 unsigned int channels, ch_mask; 109 110 channels = params_channels(hwparams); 111 switch (channels) { 112 case 2: 113 ch_mask = 0; 114 break; 115 case 4: 116 ch_mask = 1; 117 break; 118 case 6: 119 ch_mask = 2; 120 break; 121 default: 122 dev_err(dev, "Invalid channels %d\n", channels); 123 return -EINVAL; 124 } 125 126 adata->ch_mask = ch_mask; 127 if (params_format(hwparams) != SNDRV_PCM_FORMAT_S32_LE) { 128 dev_err(dai->dev, "Invalid format:%d\n", params_format(hwparams)); 129 return -EINVAL; 130 } 131 132 writel(ch_mask, adata->acp_base + ACP_WOV_PDM_NO_OF_CHANNELS); 133 writel(PDM_DEC_64, adata->acp_base + ACP_WOV_PDM_DECIMATION_FACTOR); 134 135 return 0; 136 } 137 138 static int acp_dmic_dai_startup(struct snd_pcm_substream *substream, 139 struct snd_soc_dai *dai) 140 { 141 struct acp_stream *stream = substream->runtime->private_data; 142 struct device *dev = dai->component->dev; 143 struct acp_dev_data *adata = dev_get_drvdata(dev); 144 u32 ext_int_ctrl; 145 146 stream->dai_id = DMIC_INSTANCE; 147 stream->irq_bit = BIT(PDM_DMA_STAT); 148 stream->pte_offset = ACP_SRAM_PDM_PTE_OFFSET; 149 stream->reg_offset = ACP_REGION2_OFFSET; 150 151 /* Enable DMIC Interrupts */ 152 ext_int_ctrl = readl(ACP_EXTERNAL_INTR_CNTL(adata, 0)); 153 ext_int_ctrl |= PDM_DMA_INTR_MASK; 154 writel(ext_int_ctrl, ACP_EXTERNAL_INTR_CNTL(adata, 0)); 155 156 return 0; 157 } 158 159 static void acp_dmic_dai_shutdown(struct snd_pcm_substream *substream, 160 struct snd_soc_dai *dai) 161 { 162 struct device *dev = dai->component->dev; 163 struct acp_dev_data *adata = dev_get_drvdata(dev); 164 u32 ext_int_ctrl; 165 166 /* Disable DMIC interrupts */ 167 ext_int_ctrl = readl(ACP_EXTERNAL_INTR_CNTL(adata, 0)); 168 ext_int_ctrl &= ~PDM_DMA_INTR_MASK; 169 writel(ext_int_ctrl, ACP_EXTERNAL_INTR_CNTL(adata, 0)); 170 } 171 172 const struct snd_soc_dai_ops acp_dmic_dai_ops = { 173 .prepare = acp_dmic_prepare, 174 .hw_params = acp_dmic_hwparams, 175 .trigger = acp_dmic_dai_trigger, 176 .startup = acp_dmic_dai_startup, 177 .shutdown = acp_dmic_dai_shutdown, 178 }; 179 EXPORT_SYMBOL_NS_GPL(acp_dmic_dai_ops, SND_SOC_ACP_COMMON); 180 181 MODULE_DESCRIPTION("AMD ACP Audio PDM controller"); 182 MODULE_LICENSE("Dual BSD/GPL"); 183 MODULE_ALIAS(DRV_NAME); 184
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.