| From 73cb12d09df8e32161783ae1c8676c730b6ea941 Mon Sep 17 00:00:00 2001 |
| From: Vijendar Mukunda <Vijendar.Mukunda@amd.com> |
| Date: Thu, 15 Apr 2021 13:04:49 +0530 |
| Subject: [PATCH] CHROMIUM: ASoC: amd: acp driver changes to support I2S and |
| dmic |
| |
| This patch includes changes to support I2S and DMIC endpoints |
| |
| - ACP PCI driver changes to support DMIC and I2S endpoints |
| - ACP PDM DMA driver changes |
| 1)PTE entries programming changes |
| 2)Move IRQ handler to ACP PCI Driver |
| 3)Changed runtime pm ops |
| - Add ACP I2S DMA driver |
| - Add ACP I2S CPU DAI driver |
| |
| Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com> |
| Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.corp-partner.google.com> |
| |
| BUG=b:184617007 |
| TEST= Audio use cases should work |
| |
| Change-Id: I397bcb976bdb8a9553597af16055b72bbc9c6215 |
| Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/kernel/+/2862745 |
| Reviewed-by: Yu-Hsuan Hsu <yuhsuan@chromium.org> |
| Commit-Queue: Yu-Hsuan Hsu <yuhsuan@chromium.org> |
| Tested-by: Yu-Hsuan Hsu <yuhsuan@chromium.org> |
| --- |
| sound/soc/amd/renoir/acp3x-rn-i2s.c | 339 +++++++++++++++++ |
| sound/soc/amd/renoir/acp3x-rn-pcm-dma.c | 463 +++++++++++++++++++++++ |
| sound/soc/amd/renoir/acp3x-rn-pdm-dma.c | 467 ++++++++++++++++++++++++ |
| sound/soc/amd/renoir/rn-pci-acp3x.c | 338 +++++++++++------ |
| sound/soc/amd/renoir/rn_acp3x.h | 138 ++++++- |
| 5 files changed, 1629 insertions(+), 116 deletions(-) |
| create mode 100644 sound/soc/amd/renoir/acp3x-rn-i2s.c |
| create mode 100644 sound/soc/amd/renoir/acp3x-rn-pcm-dma.c |
| create mode 100644 sound/soc/amd/renoir/acp3x-rn-pdm-dma.c |
| |
| diff --git a/sound/soc/amd/renoir/acp3x-rn-i2s.c b/sound/soc/amd/renoir/acp3x-rn-i2s.c |
| new file mode 100644 |
| index 000000000000..3dfa8d8434c3 |
| --- /dev/null |
| +++ b/sound/soc/amd/renoir/acp3x-rn-i2s.c |
| @@ -0,0 +1,339 @@ |
| +// SPDX-License-Identifier: GPL-2.0+ |
| +// |
| +// AMD ALSA SoC PCM Driver |
| +// |
| +//Copyright 2016 Advanced Micro Devices, Inc. |
| + |
| +#include <linux/platform_device.h> |
| +#include <linux/module.h> |
| +#include <linux/err.h> |
| +#include <linux/io.h> |
| +#include <sound/pcm_params.h> |
| +#include <sound/soc.h> |
| +#include <sound/soc-dai.h> |
| +#include <linux/dma-mapping.h> |
| + |
| +#include "rn_acp3x.h" |
| + |
| +#define DRV_NAME "acp_i2s_playcap" |
| + |
| +static int acp3x_i2s_set_fmt(struct snd_soc_dai *cpu_dai, |
| + unsigned int fmt) |
| +{ |
| + struct i2s_dev_data *adata; |
| + int mode; |
| + |
| + adata = snd_soc_dai_get_drvdata(cpu_dai); |
| + mode = fmt & SND_SOC_DAIFMT_FORMAT_MASK; |
| + switch (mode) { |
| + case SND_SOC_DAIFMT_I2S: |
| + adata->tdm_mode = TDM_DISABLE; |
| + break; |
| + case SND_SOC_DAIFMT_DSP_A: |
| + adata->tdm_mode = TDM_ENABLE; |
| + break; |
| + default: |
| + return -EINVAL; |
| + } |
| + return 0; |
| +} |
| + |
| +static int acp3x_i2s_set_tdm_slot(struct snd_soc_dai *cpu_dai, |
| + u32 tx_mask, u32 rx_mask, int slots, |
| + int slot_width) |
| +{ |
| + struct i2s_dev_data *adata; |
| + u32 frm_len; |
| + u16 slot_len; |
| + |
| + adata = snd_soc_dai_get_drvdata(cpu_dai); |
| + |
| + /* These values are as per Hardware Spec */ |
| + switch (slot_width) { |
| + case SLOT_WIDTH_8: |
| + slot_len = 8; |
| + break; |
| + case SLOT_WIDTH_16: |
| + slot_len = 16; |
| + break; |
| + case SLOT_WIDTH_24: |
| + slot_len = 24; |
| + break; |
| + case SLOT_WIDTH_32: |
| + slot_len = 0; |
| + break; |
| + default: |
| + return -EINVAL; |
| + } |
| + frm_len = FRM_LEN | (slots << 15) | (slot_len << 18); |
| + adata->tdm_fmt = frm_len; |
| + return 0; |
| +} |
| + |
| +static int acp3x_i2s_hwparams(struct snd_pcm_substream *substream, |
| + struct snd_pcm_hw_params *params, |
| + struct snd_soc_dai *dai) |
| +{ |
| + struct i2s_stream_instance *rtd; |
| + struct snd_soc_pcm_runtime *prtd; |
| + struct snd_soc_card *card; |
| + struct acp3x_platform_info *pinfo; |
| + struct i2s_dev_data *adata; |
| + u32 val; |
| + u32 reg_val, frmt_reg; |
| + |
| + prtd = asoc_substream_to_rtd(substream); |
| + rtd = substream->runtime->private_data; |
| + card = prtd->card; |
| + adata = snd_soc_dai_get_drvdata(dai); |
| + pinfo = snd_soc_card_get_drvdata(card); |
| + if (pinfo) { |
| + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| + rtd->i2s_instance = pinfo->play_i2s_instance; |
| + else |
| + rtd->i2s_instance = pinfo->cap_i2s_instance; |
| + } |
| + |
| + /* These values are as per Hardware Spec */ |
| + switch (params_format(params)) { |
| + case SNDRV_PCM_FORMAT_U8: |
| + case SNDRV_PCM_FORMAT_S8: |
| + rtd->xfer_resolution = 0x0; |
| + break; |
| + case SNDRV_PCM_FORMAT_S16_LE: |
| + rtd->xfer_resolution = 0x02; |
| + break; |
| + case SNDRV_PCM_FORMAT_S24_LE: |
| + rtd->xfer_resolution = 0x04; |
| + break; |
| + case SNDRV_PCM_FORMAT_S32_LE: |
| + rtd->xfer_resolution = 0x05; |
| + break; |
| + default: |
| + return -EINVAL; |
| + } |
| + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + reg_val = ACP_BTTDM_ITER; |
| + frmt_reg = ACP_BTTDM_TXFRMT; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + reg_val = ACP_I2STDM_ITER; |
| + frmt_reg = ACP_I2STDM_TXFRMT; |
| + } |
| + } else { |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + reg_val = ACP_BTTDM_IRER; |
| + frmt_reg = ACP_BTTDM_RXFRMT; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + reg_val = ACP_I2STDM_IRER; |
| + frmt_reg = ACP_I2STDM_RXFRMT; |
| + } |
| + } |
| + if (adata->tdm_mode) { |
| + val = rn_readl(rtd->acp_base + reg_val); |
| + rn_writel(val | 0x2, rtd->acp_base + reg_val); |
| + rn_writel(adata->tdm_fmt, rtd->acp_base + frmt_reg); |
| + } |
| + val = rn_readl(rtd->acp_base + reg_val); |
| + val &= ~ACP3x_ITER_IRER_SAMP_LEN_MASK; |
| + val = val | (rtd->xfer_resolution << 3); |
| + rn_writel(val, rtd->acp_base + reg_val); |
| + return 0; |
| +} |
| + |
| +static int acp3x_i2s_trigger(struct snd_pcm_substream *substream, |
| + int cmd, struct snd_soc_dai *dai) |
| +{ |
| + struct i2s_stream_instance *rtd; |
| + u32 ret, val, period_bytes, reg_val, ier_val, water_val; |
| + u32 buf_size, buf_reg; |
| + |
| + rtd = substream->runtime->private_data; |
| + period_bytes = frames_to_bytes(substream->runtime, |
| + substream->runtime->period_size); |
| + buf_size = frames_to_bytes(substream->runtime, |
| + substream->runtime->buffer_size); |
| + switch (cmd) { |
| + case SNDRV_PCM_TRIGGER_START: |
| + case SNDRV_PCM_TRIGGER_RESUME: |
| + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
| + rtd->bytescount = acp_get_byte_count(rtd, |
| + substream->stream); |
| + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + water_val = |
| + ACP_BT_TX_INTR_WATERMARK_SIZE; |
| + reg_val = ACP_BTTDM_ITER; |
| + ier_val = ACP_BTTDM_IER; |
| + buf_reg = ACP_BT_TX_RINGBUFSIZE; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + water_val = |
| + ACP_I2S_TX_INTR_WATERMARK_SIZE; |
| + reg_val = ACP_I2STDM_ITER; |
| + ier_val = ACP_I2STDM_IER; |
| + buf_reg = ACP_I2S_TX_RINGBUFSIZE; |
| + } |
| + } else { |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + water_val = |
| + ACP_BT_RX_INTR_WATERMARK_SIZE; |
| + reg_val = ACP_BTTDM_IRER; |
| + ier_val = ACP_BTTDM_IER; |
| + buf_reg = ACP_BT_RX_RINGBUFSIZE; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + water_val = |
| + ACP_I2S_RX_INTR_WATERMARK_SIZE; |
| + reg_val = ACP_I2STDM_IRER; |
| + ier_val = ACP_I2STDM_IER; |
| + buf_reg = ACP_I2S_RX_RINGBUFSIZE; |
| + } |
| + } |
| + rn_writel(period_bytes, rtd->acp_base + water_val); |
| + rn_writel(buf_size, rtd->acp_base + buf_reg); |
| + val = rn_readl(rtd->acp_base + reg_val); |
| + val = val | BIT(0); |
| + rn_writel(val, rtd->acp_base + reg_val); |
| + rn_writel(1, rtd->acp_base + ier_val); |
| + ret = 0; |
| + break; |
| + case SNDRV_PCM_TRIGGER_STOP: |
| + case SNDRV_PCM_TRIGGER_SUSPEND: |
| + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
| + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + reg_val = ACP_BTTDM_ITER; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + reg_val = ACP_I2STDM_ITER; |
| + } |
| + |
| + } else { |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + reg_val = ACP_BTTDM_IRER; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + reg_val = ACP_I2STDM_IRER; |
| + } |
| + } |
| + val = rn_readl(rtd->acp_base + reg_val); |
| + val = val & ~BIT(0); |
| + rn_writel(val, rtd->acp_base + reg_val); |
| + if (!(rn_readl(rtd->acp_base + ACP_BTTDM_ITER) & BIT(0)) && |
| + !(rn_readl(rtd->acp_base + ACP_BTTDM_IRER) & BIT(0))) |
| + rn_writel(0, rtd->acp_base + ACP_BTTDM_IER); |
| + if (!(rn_readl(rtd->acp_base + ACP_I2STDM_ITER) & BIT(0)) && |
| + !(rn_readl(rtd->acp_base + ACP_I2STDM_IRER) & BIT(0))) |
| + rn_writel(0, rtd->acp_base + ACP_I2STDM_IER); |
| + ret = 0; |
| + break; |
| + default: |
| + ret = -EINVAL; |
| + break; |
| + } |
| + return ret; |
| +} |
| + |
| +static struct snd_soc_dai_ops acp3x_i2s_dai_ops = { |
| + .hw_params = acp3x_i2s_hwparams, |
| + .trigger = acp3x_i2s_trigger, |
| + .set_fmt = acp3x_i2s_set_fmt, |
| + .set_tdm_slot = acp3x_i2s_set_tdm_slot, |
| +}; |
| + |
| +static const struct snd_soc_component_driver acp3x_dai_component = { |
| + .name = DRV_NAME, |
| +}; |
| + |
| +static struct snd_soc_dai_driver acp3x_i2s_dai = { |
| + .playback = { |
| + .rates = SNDRV_PCM_RATE_8000_96000, |
| + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | |
| + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | |
| + SNDRV_PCM_FMTBIT_S32_LE, |
| + .channels_min = 2, |
| + .channels_max = 8, |
| + .rate_min = 8000, |
| + .rate_max = 96000, |
| + }, |
| + .capture = { |
| + .rates = SNDRV_PCM_RATE_8000_48000, |
| + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | |
| + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | |
| + SNDRV_PCM_FMTBIT_S32_LE, |
| + .channels_min = 2, |
| + .channels_max = 2, |
| + .rate_min = 8000, |
| + .rate_max = 48000, |
| + }, |
| + .ops = &acp3x_i2s_dai_ops, |
| +}; |
| + |
| +static int acp3x_dai_probe(struct platform_device *pdev) |
| +{ |
| + struct resource *res; |
| + struct i2s_dev_data *adata; |
| + int ret; |
| + |
| + adata = devm_kzalloc(&pdev->dev, sizeof(struct i2s_dev_data), |
| + GFP_KERNEL); |
| + if (!adata) |
| + return -ENOMEM; |
| + |
| + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| + if (!res) { |
| + dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n"); |
| + return -ENOMEM; |
| + } |
| + adata->acp_base = devm_ioremap(&pdev->dev, res->start, |
| + resource_size(res)); |
| + if (!adata->acp_base) |
| + return -ENOMEM; |
| + |
| + adata->i2s_irq = res->start; |
| + dev_set_drvdata(&pdev->dev, adata); |
| + ret = devm_snd_soc_register_component(&pdev->dev, |
| + &acp3x_dai_component, |
| + &acp3x_i2s_dai, 1); |
| + if (ret) { |
| + dev_err(&pdev->dev, "Fail to register acp i2s dai\n"); |
| + return -ENODEV; |
| + } |
| + return 0; |
| +} |
| + |
| +static int acp3x_dai_remove(struct platform_device *pdev) |
| +{ |
| + /* As we use devm_ memory alloc there is nothing TBD here */ |
| + return 0; |
| +} |
| + |
| +static struct platform_driver acp3x_dai_driver = { |
| + .probe = acp3x_dai_probe, |
| + .remove = acp3x_dai_remove, |
| + .driver = { |
| + .name = "acp_i2s_playcap", |
| + }, |
| +}; |
| + |
| +module_platform_driver(acp3x_dai_driver); |
| + |
| +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); |
| +MODULE_DESCRIPTION("AMD ACP 3.x PCM Driver"); |
| +MODULE_LICENSE("GPL v2"); |
| +MODULE_ALIAS("platform:" DRV_NAME); |
| diff --git a/sound/soc/amd/renoir/acp3x-rn-pcm-dma.c b/sound/soc/amd/renoir/acp3x-rn-pcm-dma.c |
| new file mode 100644 |
| index 000000000000..718d7c5f879c |
| --- /dev/null |
| +++ b/sound/soc/amd/renoir/acp3x-rn-pcm-dma.c |
| @@ -0,0 +1,463 @@ |
| +// SPDX-License-Identifier: GPL-2.0+ |
| +// |
| +// AMD ALSA SoC PCM Driver |
| +// |
| +//Copyright 2020 Advanced Micro Devices, Inc. |
| + |
| +#include <linux/platform_device.h> |
| +#include <linux/module.h> |
| +#include <linux/err.h> |
| +#include <linux/io.h> |
| +#include <linux/pm_runtime.h> |
| +#include <sound/pcm_params.h> |
| +#include <sound/soc.h> |
| +#include <sound/soc-dai.h> |
| + |
| +#include "rn_acp3x.h" |
| + |
| +#define DRV_NAME "acp_rn_i2s_dma" |
| + |
| +static const struct snd_pcm_hardware acp3x_pcm_hardware_playback = { |
| + .info = SNDRV_PCM_INFO_INTERLEAVED | |
| + SNDRV_PCM_INFO_BLOCK_TRANSFER | |
| + SNDRV_PCM_INFO_BATCH | |
| + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | |
| + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, |
| + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | |
| + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | |
| + SNDRV_PCM_FMTBIT_S32_LE, |
| + .channels_min = 2, |
| + .channels_max = 8, |
| + .rates = SNDRV_PCM_RATE_8000_96000, |
| + .rate_min = 8000, |
| + .rate_max = 96000, |
| + .buffer_bytes_max = I2S_PLAYBACK_MAX_NUM_PERIODS * I2S_PLAYBACK_MAX_PERIOD_SIZE, |
| + .period_bytes_min = I2S_PLAYBACK_MIN_PERIOD_SIZE, |
| + .period_bytes_max = I2S_PLAYBACK_MAX_PERIOD_SIZE, |
| + .periods_min = I2S_PLAYBACK_MIN_NUM_PERIODS, |
| + .periods_max = I2S_PLAYBACK_MAX_NUM_PERIODS, |
| +}; |
| + |
| +static const struct snd_pcm_hardware acp3x_pcm_hardware_capture = { |
| + .info = SNDRV_PCM_INFO_INTERLEAVED | |
| + SNDRV_PCM_INFO_BLOCK_TRANSFER | |
| + SNDRV_PCM_INFO_BATCH | |
| + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | |
| + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, |
| + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | |
| + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | |
| + SNDRV_PCM_FMTBIT_S32_LE, |
| + .channels_min = 2, |
| + .channels_max = 2, |
| + .rates = SNDRV_PCM_RATE_8000_48000, |
| + .rate_min = 8000, |
| + .rate_max = 48000, |
| + .buffer_bytes_max = I2S_CAPTURE_MAX_NUM_PERIODS * I2S_CAPTURE_MAX_PERIOD_SIZE, |
| + .period_bytes_min = I2S_CAPTURE_MIN_PERIOD_SIZE, |
| + .period_bytes_max = I2S_CAPTURE_MAX_PERIOD_SIZE, |
| + .periods_min = I2S_CAPTURE_MIN_NUM_PERIODS, |
| + .periods_max = I2S_CAPTURE_MAX_NUM_PERIODS, |
| +}; |
| + |
| +static void config_acp3x_dma(struct i2s_stream_instance *rtd, int direction) |
| +{ |
| + u16 page_idx; |
| + u32 low, high, val, acp_fifo_addr, reg_fifo_addr; |
| + u32 reg_dma_size, reg_fifo_size; |
| + dma_addr_t addr; |
| + u32 irq_cntl = 0; |
| + |
| + addr = rtd->dma_addr; |
| + |
| + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + val = ACP_SRAM_BT_PB_PTE_OFFSET; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + val = ACP_SRAM_SP_PB_PTE_OFFSET; |
| + } |
| + } else { |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + val = ACP_SRAM_BT_CP_PTE_OFFSET; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + val = ACP_SRAM_SP_CP_PTE_OFFSET; |
| + } |
| + } |
| + /* Group Enable */ |
| + rn_writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp_base + |
| + ACPAXI2AXI_ATU_BASE_ADDR_GRP_1); |
| + rn_writel(PAGE_SIZE_4K_ENABLE, rtd->acp_base + |
| + ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1); |
| + |
| + for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) { |
| + /* Load the low address of page int ACP SRAM through SRBM */ |
| + low = lower_32_bits(addr); |
| + high = upper_32_bits(addr); |
| + |
| + rn_writel(low, rtd->acp_base + ACP_SCRATCH_REG_0 + val); |
| + high |= BIT(31); |
| + rn_writel(high, rtd->acp_base + ACP_SCRATCH_REG_0 + val |
| + + 4); |
| + /* Move to next physically contigous page */ |
| + val += 8; |
| + addr += PAGE_SIZE; |
| + } |
| + |
| + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + reg_dma_size = ACP_BT_TX_DMA_SIZE; |
| + acp_fifo_addr = ACP_SRAM_PTE_OFFSET + |
| + BT_PB_FIFO_ADDR_OFFSET; |
| + reg_fifo_addr = ACP_BT_TX_FIFOADDR; |
| + reg_fifo_size = ACP_BT_TX_FIFOSIZE; |
| + rn_writel(I2S_BT_TX_MEM_WINDOW_START, |
| + rtd->acp_base + ACP_BT_TX_RINGBUFADDR); |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + reg_dma_size = ACP_I2S_TX_DMA_SIZE; |
| + acp_fifo_addr = ACP_SRAM_PTE_OFFSET + |
| + SP_PB_FIFO_ADDR_OFFSET; |
| + reg_fifo_addr = ACP_I2S_TX_FIFOADDR; |
| + reg_fifo_size = ACP_I2S_TX_FIFOSIZE; |
| + rn_writel(I2S_SP_TX_MEM_WINDOW_START, |
| + rtd->acp_base + ACP_I2S_TX_RINGBUFADDR); |
| + } |
| + } else { |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + reg_dma_size = ACP_BT_RX_DMA_SIZE; |
| + acp_fifo_addr = ACP_SRAM_PTE_OFFSET + |
| + BT_CAPT_FIFO_ADDR_OFFSET; |
| + reg_fifo_addr = ACP_BT_RX_FIFOADDR; |
| + reg_fifo_size = ACP_BT_RX_FIFOSIZE; |
| + rn_writel(I2S_BT_RX_MEM_WINDOW_START, |
| + rtd->acp_base + ACP_BT_RX_RINGBUFADDR); |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + reg_dma_size = ACP_I2S_RX_DMA_SIZE; |
| + acp_fifo_addr = ACP_SRAM_PTE_OFFSET + |
| + SP_CAPT_FIFO_ADDR_OFFSET; |
| + reg_fifo_addr = ACP_I2S_RX_FIFOADDR; |
| + reg_fifo_size = ACP_I2S_RX_FIFOSIZE; |
| + rn_writel(I2S_SP_RX_MEM_WINDOW_START, |
| + rtd->acp_base + ACP_I2S_RX_RINGBUFADDR); |
| + } |
| + } |
| + rn_writel(DMA_SIZE, rtd->acp_base + reg_dma_size); |
| + rn_writel(acp_fifo_addr, rtd->acp_base + reg_fifo_addr); |
| + rn_writel(FIFO_SIZE, rtd->acp_base + reg_fifo_size); |
| + irq_cntl = rn_readl(rtd->acp_base + ACP_EXTERNAL_INTR_CNTL); |
| + rn_writel(irq_cntl | BIT(I2S_RX_THRESHOLD) | BIT(BT_RX_THRESHOLD) |
| + | BIT(I2S_TX_THRESHOLD) | BIT(BT_TX_THRESHOLD), |
| + rtd->acp_base + ACP_EXTERNAL_INTR_CNTL); |
| +} |
| + |
| +static int acp3x_dma_open(struct snd_soc_component *component, |
| + struct snd_pcm_substream *substream) |
| +{ |
| + struct snd_pcm_runtime *runtime; |
| + struct snd_soc_pcm_runtime *prtd; |
| + struct i2s_dev_data *adata; |
| + struct i2s_stream_instance *i2s_data; |
| + int ret; |
| + |
| + runtime = substream->runtime; |
| + prtd = asoc_substream_to_rtd(substream); |
| + component = snd_soc_rtdcom_lookup(prtd, DRV_NAME); |
| + adata = dev_get_drvdata(component->dev); |
| + i2s_data = kzalloc(sizeof(*i2s_data), GFP_KERNEL); |
| + if (!i2s_data) |
| + return -EINVAL; |
| + |
| + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| + runtime->hw = acp3x_pcm_hardware_playback; |
| + else |
| + runtime->hw = acp3x_pcm_hardware_capture; |
| + ret = snd_pcm_hw_constraint_integer(runtime, |
| + SNDRV_PCM_HW_PARAM_PERIODS); |
| + if (ret < 0) { |
| + dev_err(component->dev, "set integer constraint failed\n"); |
| + kfree(i2s_data); |
| + return ret; |
| + } |
| + |
| + if (!adata->play_stream && !adata->capture_stream && |
| + !adata->i2ssp_play_stream && !adata->i2ssp_capture_stream) |
| + rn_writel(1, adata->acp_base + ACP_EXTERNAL_INTR_ENB); |
| + |
| + i2s_data->acp_base = adata->acp_base; |
| + runtime->private_data = i2s_data; |
| + return ret; |
| +} |
| + |
| +static int acp3x_dma_hw_params(struct snd_soc_component *component, |
| + struct snd_pcm_substream *substream, |
| + struct snd_pcm_hw_params *params) |
| +{ |
| + struct i2s_stream_instance *rtd; |
| + struct snd_soc_pcm_runtime *prtd; |
| + struct snd_soc_card *card; |
| + struct acp3x_platform_info *pinfo; |
| + struct i2s_dev_data *adata; |
| + u64 size; |
| + |
| + prtd = asoc_substream_to_rtd(substream); |
| + card = prtd->card; |
| + pinfo = snd_soc_card_get_drvdata(card); |
| + adata = dev_get_drvdata(component->dev); |
| + rtd = substream->runtime->private_data; |
| + if (!rtd) |
| + return -EINVAL; |
| + |
| + if (pinfo) { |
| + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| + rtd->i2s_instance = pinfo->play_i2s_instance; |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + adata->play_stream = substream; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + adata->i2ssp_play_stream = substream; |
| + } |
| + } else { |
| + rtd->i2s_instance = pinfo->cap_i2s_instance; |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + adata->capture_stream = substream; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + adata->i2ssp_capture_stream = substream; |
| + } |
| + } |
| + } else { |
| + pr_err("pinfo failed\n"); |
| + } |
| + size = params_buffer_bytes(params); |
| + rtd->dma_addr = substream->dma_buffer.addr; |
| + rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT); |
| + config_acp3x_dma(rtd, substream->stream); |
| + return 0; |
| +} |
| + |
| +static snd_pcm_uframes_t acp3x_dma_pointer(struct snd_soc_component *component, |
| + struct snd_pcm_substream *substream) |
| +{ |
| + struct i2s_stream_instance *rtd; |
| + u32 pos; |
| + u32 buffersize; |
| + u64 bytescount; |
| + |
| + rtd = substream->runtime->private_data; |
| + |
| + buffersize = frames_to_bytes(substream->runtime, |
| + substream->runtime->buffer_size); |
| + bytescount = acp_get_byte_count(rtd, substream->stream); |
| + if (bytescount > rtd->bytescount) |
| + bytescount -= rtd->bytescount; |
| + pos = do_div(bytescount, buffersize); |
| + return bytes_to_frames(substream->runtime, pos); |
| +} |
| + |
| +static int acp3x_dma_new(struct snd_soc_component *component, |
| + struct snd_soc_pcm_runtime *rtd) |
| +{ |
| + struct device *parent = component->dev->parent; |
| + |
| + snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, |
| + parent, I2S_MIN_BUFFER, I2S_MAX_BUFFER); |
| + return 0; |
| +} |
| + |
| +static int acp3x_dma_mmap(struct snd_soc_component *component, |
| + struct snd_pcm_substream *substream, |
| + struct vm_area_struct *vma) |
| +{ |
| + return snd_pcm_lib_default_mmap(substream, vma); |
| +} |
| + |
| +static int acp3x_dma_close(struct snd_soc_component *component, |
| + struct snd_pcm_substream *substream) |
| +{ |
| + struct snd_soc_pcm_runtime *prtd; |
| + struct i2s_dev_data *adata; |
| + struct i2s_stream_instance *ins; |
| + |
| + prtd = asoc_substream_to_rtd(substream); |
| + component = snd_soc_rtdcom_lookup(prtd, DRV_NAME); |
| + adata = dev_get_drvdata(component->dev); |
| + ins = substream->runtime->private_data; |
| + if (!ins) |
| + return -EINVAL; |
| + |
| + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| + switch (ins->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + adata->play_stream = NULL; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + adata->i2ssp_play_stream = NULL; |
| + } |
| + } else { |
| + switch (ins->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + adata->capture_stream = NULL; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + adata->i2ssp_capture_stream = NULL; |
| + } |
| + } |
| + return 0; |
| +} |
| + |
| +static const struct snd_soc_component_driver acp3x_i2s_component = { |
| + .name = DRV_NAME, |
| + .open = acp3x_dma_open, |
| + .close = acp3x_dma_close, |
| + .hw_params = acp3x_dma_hw_params, |
| + .pointer = acp3x_dma_pointer, |
| + .mmap = acp3x_dma_mmap, |
| + .pcm_construct = acp3x_dma_new, |
| +}; |
| + |
| +static int acp3x_audio_probe(struct platform_device *pdev) |
| +{ |
| + struct resource *res; |
| + struct i2s_dev_data *adata; |
| + int status; |
| + |
| + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| + if (!res) { |
| + dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n"); |
| + return -ENODEV; |
| + } |
| + |
| + adata = devm_kzalloc(&pdev->dev, sizeof(*adata), GFP_KERNEL); |
| + if (!adata) |
| + return -ENOMEM; |
| + |
| + adata->acp_base = devm_ioremap(&pdev->dev, res->start, |
| + resource_size(res)); |
| + if (!adata->acp_base) |
| + return -ENOMEM; |
| + |
| + dev_set_drvdata(&pdev->dev, adata); |
| + status = devm_snd_soc_register_component(&pdev->dev, |
| + &acp3x_i2s_component, |
| + NULL, 0); |
| + if (status) { |
| + dev_err(&pdev->dev, "Fail to register acp i2s component\n"); |
| + return -ENODEV; |
| + } |
| + |
| + pm_runtime_set_autosuspend_delay(&pdev->dev, 2000); |
| + pm_runtime_use_autosuspend(&pdev->dev); |
| + pm_runtime_enable(&pdev->dev); |
| + pm_runtime_allow(&pdev->dev); |
| + return 0; |
| +} |
| + |
| +static int acp3x_audio_remove(struct platform_device *pdev) |
| +{ |
| + pm_runtime_disable(&pdev->dev); |
| + return 0; |
| +} |
| + |
| +static int acp3x_resume(struct device *dev) |
| +{ |
| + struct i2s_dev_data *adata; |
| + u32 val, reg_val, frmt_val; |
| + |
| + reg_val = 0; |
| + frmt_val = 0; |
| + adata = dev_get_drvdata(dev); |
| + |
| + if (adata->play_stream && adata->play_stream->runtime) { |
| + struct i2s_stream_instance *rtd = |
| + adata->play_stream->runtime->private_data; |
| + config_acp3x_dma(rtd, SNDRV_PCM_STREAM_PLAYBACK); |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + reg_val = ACP_BTTDM_ITER; |
| + frmt_val = ACP_BTTDM_TXFRMT; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + reg_val = ACP_I2STDM_ITER; |
| + frmt_val = ACP_I2STDM_TXFRMT; |
| + } |
| + rn_writel((rtd->xfer_resolution << 3), |
| + rtd->acp_base + reg_val); |
| + } |
| + if (adata->capture_stream && adata->capture_stream->runtime) { |
| + struct i2s_stream_instance *rtd = |
| + adata->capture_stream->runtime->private_data; |
| + config_acp3x_dma(rtd, SNDRV_PCM_STREAM_CAPTURE); |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + reg_val = ACP_BTTDM_IRER; |
| + frmt_val = ACP_BTTDM_RXFRMT; |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + reg_val = ACP_I2STDM_IRER; |
| + frmt_val = ACP_I2STDM_RXFRMT; |
| + } |
| + rn_writel((rtd->xfer_resolution << 3), |
| + rtd->acp_base + reg_val); |
| + } |
| + if (adata->tdm_mode == TDM_ENABLE) { |
| + rn_writel(adata->tdm_fmt, adata->acp_base + frmt_val); |
| + val = rn_readl(adata->acp_base + reg_val); |
| + rn_writel(val | 0x2, adata->acp_base + reg_val); |
| + } |
| + rn_writel(1, adata->acp_base + ACP_EXTERNAL_INTR_ENB); |
| + return 0; |
| +} |
| + |
| +static int acp3x_pcm_runtime_suspend(struct device *dev) |
| +{ |
| + return 0; |
| +} |
| + |
| +static int acp3x_pcm_runtime_resume(struct device *dev) |
| +{ |
| + struct i2s_dev_data *adata; |
| + |
| + adata = dev_get_drvdata(dev); |
| + rn_writel(1, adata->acp_base + ACP_EXTERNAL_INTR_ENB); |
| + return 0; |
| +} |
| + |
| +static const struct dev_pm_ops acp3x_pm_ops = { |
| + .runtime_suspend = acp3x_pcm_runtime_suspend, |
| + .runtime_resume = acp3x_pcm_runtime_resume, |
| + .resume = acp3x_resume, |
| +}; |
| + |
| +static struct platform_driver acp3x_dma_driver = { |
| + .probe = acp3x_audio_probe, |
| + .remove = acp3x_audio_remove, |
| + .driver = { |
| + .name = "acp_rn_i2s_dma", |
| + .pm = &acp3x_pm_ops, |
| + }, |
| +}; |
| + |
| +module_platform_driver(acp3x_dma_driver); |
| + |
| +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); |
| +MODULE_DESCRIPTION("AMD ACP 3.x PCM Driver"); |
| +MODULE_LICENSE("GPL v2"); |
| +MODULE_ALIAS("platform:" DRV_NAME); |
| diff --git a/sound/soc/amd/renoir/acp3x-rn-pdm-dma.c b/sound/soc/amd/renoir/acp3x-rn-pdm-dma.c |
| new file mode 100644 |
| index 000000000000..6c36c844d5cc |
| --- /dev/null |
| +++ b/sound/soc/amd/renoir/acp3x-rn-pdm-dma.c |
| @@ -0,0 +1,467 @@ |
| +// SPDX-License-Identifier: GPL-2.0+ |
| +// |
| +// AMD ALSA SoC PDM Driver |
| +// |
| +//Copyright 2020 Advanced Micro Devices, Inc. |
| + |
| +#include <linux/platform_device.h> |
| +#include <linux/module.h> |
| +#include <linux/err.h> |
| +#include <linux/io.h> |
| +#include <linux/pm_runtime.h> |
| +#include <sound/pcm_params.h> |
| +#include <sound/soc.h> |
| +#include <sound/soc-dai.h> |
| + |
| +#include "rn_acp3x.h" |
| + |
| +#define DRV_NAME "acp_rn_pdm_dma" |
| + |
| +static const struct snd_pcm_hardware acp_pdm_hardware_capture = { |
| + .info = SNDRV_PCM_INFO_INTERLEAVED | |
| + SNDRV_PCM_INFO_BLOCK_TRANSFER | |
| + SNDRV_PCM_INFO_MMAP | |
| + SNDRV_PCM_INFO_MMAP_VALID | |
| + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, |
| + .formats = SNDRV_PCM_FMTBIT_S32_LE, |
| + .channels_min = 2, |
| + .channels_max = 2, |
| + .rates = SNDRV_PCM_RATE_48000, |
| + .rate_min = 48000, |
| + .rate_max = 48000, |
| + .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE, |
| + .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, |
| + .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, |
| + .periods_min = CAPTURE_MIN_NUM_PERIODS, |
| + .periods_max = CAPTURE_MAX_NUM_PERIODS, |
| +}; |
| + |
| +static void init_pdm_ring_buffer(u32 physical_addr, |
| + u32 buffer_size, |
| + u32 watermark_size, |
| + void __iomem *acp_base) |
| +{ |
| + rn_writel(physical_addr, acp_base + ACP_WOV_RX_RINGBUFADDR); |
| + rn_writel(buffer_size, acp_base + ACP_WOV_RX_RINGBUFSIZE); |
| + rn_writel(watermark_size, acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE); |
| + rn_writel(0x01, acp_base + ACPAXI2AXI_ATU_CTRL); |
| +} |
| + |
| +static void enable_pdm_clock(void __iomem *acp_base) |
| +{ |
| + u32 pdm_clk_enable, pdm_ctrl; |
| + |
| + pdm_clk_enable = ACP_PDM_CLK_FREQ_MASK; |
| + pdm_ctrl = 0x00; |
| + |
| + rn_writel(pdm_clk_enable, acp_base + ACP_WOV_CLK_CTRL); |
| + pdm_ctrl = rn_readl(acp_base + ACP_WOV_MISC_CTRL); |
| + pdm_ctrl |= ACP_WOV_MISC_CTRL_MASK; |
| + rn_writel(pdm_ctrl, acp_base + ACP_WOV_MISC_CTRL); |
| +} |
| + |
| +static void enable_pdm_interrupts(void __iomem *acp_base) |
| +{ |
| + u32 ext_int_ctrl; |
| + |
| + ext_int_ctrl = rn_readl(acp_base + ACP_EXTERNAL_INTR_CNTL); |
| + ext_int_ctrl |= PDM_DMA_INTR_MASK; |
| + rn_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL); |
| +} |
| + |
| +static void disable_pdm_interrupts(void __iomem *acp_base) |
| +{ |
| + u32 ext_int_ctrl; |
| + |
| + ext_int_ctrl = rn_readl(acp_base + ACP_EXTERNAL_INTR_CNTL); |
| + ext_int_ctrl &= ~PDM_DMA_INTR_MASK; |
| + rn_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL); |
| +} |
| + |
| +static bool check_pdm_dma_status(void __iomem *acp_base) |
| +{ |
| + bool pdm_dma_status; |
| + u32 pdm_enable, pdm_dma_enable; |
| + |
| + pdm_dma_status = false; |
| + pdm_enable = rn_readl(acp_base + ACP_WOV_PDM_ENABLE); |
| + pdm_dma_enable = rn_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); |
| + if ((pdm_enable & ACP_PDM_ENABLE) && (pdm_dma_enable & |
| + ACP_PDM_DMA_EN_STATUS)) |
| + pdm_dma_status = true; |
| + return pdm_dma_status; |
| +} |
| + |
| +static int start_pdm_dma(void __iomem *acp_base) |
| +{ |
| + u32 pdm_enable; |
| + u32 pdm_dma_enable; |
| + int timeout; |
| + |
| + pdm_enable = 0x01; |
| + pdm_dma_enable = 0x01; |
| + |
| + enable_pdm_clock(acp_base); |
| + rn_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE); |
| + rn_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE); |
| + pdm_dma_enable = 0x00; |
| + timeout = 0; |
| + while (++timeout < ACP_COUNTER) { |
| + pdm_dma_enable = rn_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); |
| + if ((pdm_dma_enable & 0x02) == ACP_PDM_DMA_EN_STATUS) |
| + return 0; |
| + udelay(DELAY_US); |
| + } |
| + return -ETIMEDOUT; |
| +} |
| + |
| +static int stop_pdm_dma(void __iomem *acp_base) |
| +{ |
| + u32 pdm_enable, pdm_dma_enable; |
| + int timeout; |
| + |
| + pdm_enable = 0x00; |
| + pdm_dma_enable = 0x00; |
| + |
| + pdm_enable = rn_readl(acp_base + ACP_WOV_PDM_ENABLE); |
| + pdm_dma_enable = rn_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); |
| + if (pdm_dma_enable & 0x01) { |
| + pdm_dma_enable = 0x02; |
| + rn_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE); |
| + pdm_dma_enable = 0x00; |
| + timeout = 0; |
| + while (++timeout < ACP_COUNTER) { |
| + pdm_dma_enable = rn_readl(acp_base + |
| + ACP_WOV_PDM_DMA_ENABLE); |
| + if ((pdm_dma_enable & 0x02) == 0x00) |
| + break; |
| + udelay(DELAY_US); |
| + } |
| + if (timeout == ACP_COUNTER) |
| + return -ETIMEDOUT; |
| + } |
| + if (pdm_enable == ACP_PDM_ENABLE) { |
| + pdm_enable = ACP_PDM_DISABLE; |
| + rn_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE); |
| + } |
| + rn_writel(0x01, acp_base + ACP_WOV_PDM_FIFO_FLUSH); |
| + return 0; |
| +} |
| + |
| +static void config_acp_dma(struct pdm_stream_instance *rtd, int direction) |
| +{ |
| + u16 page_idx; |
| + u32 low, high, val; |
| + dma_addr_t addr; |
| + |
| + addr = rtd->dma_addr; |
| + val = PDM_PTE_OFFSET; |
| + |
| + /* Group Enable */ |
| + rn_writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp_base + |
| + ACPAXI2AXI_ATU_BASE_ADDR_GRP_1); |
| + rn_writel(PAGE_SIZE_4K_ENABLE, rtd->acp_base + |
| + ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1); |
| + |
| + for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) { |
| + /* Load the low address of page int ACP SRAM through SRBM */ |
| + low = lower_32_bits(addr); |
| + high = upper_32_bits(addr); |
| + |
| + rn_writel(low, rtd->acp_base + ACP_SCRATCH_REG_0 + val); |
| + high |= BIT(31); |
| + rn_writel(high, rtd->acp_base + ACP_SCRATCH_REG_0 + val + 4); |
| + val += 8; |
| + addr += PAGE_SIZE; |
| + } |
| +} |
| + |
| +static int acp_pdm_dma_open(struct snd_soc_component *component, |
| + struct snd_pcm_substream *substream) |
| +{ |
| + struct snd_pcm_runtime *runtime; |
| + struct pdm_dev_data *adata; |
| + struct pdm_stream_instance *pdm_data; |
| + int ret; |
| + |
| + runtime = substream->runtime; |
| + adata = dev_get_drvdata(component->dev); |
| + pdm_data = kzalloc(sizeof(*pdm_data), GFP_KERNEL); |
| + if (!pdm_data) |
| + return -EINVAL; |
| + |
| + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| + runtime->hw = acp_pdm_hardware_capture; |
| + |
| + ret = snd_pcm_hw_constraint_integer(runtime, |
| + SNDRV_PCM_HW_PARAM_PERIODS); |
| + if (ret < 0) { |
| + dev_err(component->dev, "set integer constraint failed\n"); |
| + kfree(pdm_data); |
| + return ret; |
| + } |
| + |
| + enable_pdm_interrupts(adata->acp_base); |
| + |
| + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| + adata->capture_stream = substream; |
| + |
| + pdm_data->acp_base = adata->acp_base; |
| + runtime->private_data = pdm_data; |
| + return ret; |
| +} |
| + |
| +static int acp_pdm_dma_hw_params(struct snd_soc_component *component, |
| + struct snd_pcm_substream *substream, |
| + struct snd_pcm_hw_params *params) |
| +{ |
| + struct pdm_stream_instance *rtd; |
| + size_t size, period_bytes; |
| + |
| + rtd = substream->runtime->private_data; |
| + if (!rtd) |
| + return -EINVAL; |
| + size = params_buffer_bytes(params); |
| + period_bytes = params_period_bytes(params); |
| + rtd->dma_addr = substream->dma_buffer.addr; |
| + rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT); |
| + config_acp_dma(rtd, substream->stream); |
| + init_pdm_ring_buffer(PDM_MEM_WINDOW_START, size, period_bytes, |
| + rtd->acp_base); |
| + return 0; |
| +} |
| + |
| +static u64 acp_pdm_get_byte_count(struct pdm_stream_instance *rtd, |
| + int direction) |
| +{ |
| + union acp_pdm_dma_count byte_count; |
| + |
| + byte_count.bcount.high = |
| + rn_readl(rtd->acp_base + |
| + ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH); |
| + byte_count.bcount.low = |
| + rn_readl(rtd->acp_base + |
| + ACP_WOV_RX_LINEARPOSITIONCNTR_LOW); |
| + return byte_count.bytescount; |
| +} |
| + |
| +static snd_pcm_uframes_t acp_pdm_dma_pointer(struct snd_soc_component *comp, |
| + struct snd_pcm_substream *stream) |
| +{ |
| + struct pdm_stream_instance *rtd; |
| + u32 pos, buffersize; |
| + u64 bytescount; |
| + |
| + rtd = stream->runtime->private_data; |
| + buffersize = frames_to_bytes(stream->runtime, |
| + stream->runtime->buffer_size); |
| + bytescount = acp_pdm_get_byte_count(rtd, stream->stream); |
| + if (bytescount > rtd->bytescount) |
| + bytescount -= rtd->bytescount; |
| + pos = do_div(bytescount, buffersize); |
| + return bytes_to_frames(stream->runtime, pos); |
| +} |
| + |
| +static int acp_pdm_dma_new(struct snd_soc_component *component, |
| + struct snd_soc_pcm_runtime *rtd) |
| +{ |
| + struct device *parent = component->dev->parent; |
| + |
| + snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, |
| + parent, MIN_BUFFER, MAX_BUFFER); |
| + return 0; |
| +} |
| + |
| +static int acp_pdm_dma_mmap(struct snd_soc_component *component, |
| + struct snd_pcm_substream *substream, |
| + struct vm_area_struct *vma) |
| +{ |
| + return snd_pcm_lib_default_mmap(substream, vma); |
| +} |
| + |
| +static int acp_pdm_dma_close(struct snd_soc_component *component, |
| + struct snd_pcm_substream *substream) |
| +{ |
| + struct pdm_dev_data *adata = dev_get_drvdata(component->dev); |
| + |
| + disable_pdm_interrupts(adata->acp_base); |
| + adata->capture_stream = NULL; |
| + return 0; |
| +} |
| + |
| +static int acp_pdm_dai_trigger(struct snd_pcm_substream *substream, |
| + int cmd, struct snd_soc_dai *dai) |
| +{ |
| + struct pdm_stream_instance *rtd; |
| + int ret; |
| + bool pdm_status; |
| + unsigned int ch_mask; |
| + |
| + rtd = substream->runtime->private_data; |
| + ret = 0; |
| + switch (substream->runtime->channels) { |
| + case TWO_CH: |
| + ch_mask = 0x00; |
| + break; |
| + default: |
| + return -EINVAL; |
| + } |
| + switch (cmd) { |
| + case SNDRV_PCM_TRIGGER_START: |
| + case SNDRV_PCM_TRIGGER_RESUME: |
| + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
| + rn_writel(ch_mask, rtd->acp_base + ACP_WOV_PDM_NO_OF_CHANNELS); |
| + rn_writel(PDM_DECIMATION_FACTOR, rtd->acp_base + |
| + ACP_WOV_PDM_DECIMATION_FACTOR); |
| + rtd->bytescount = acp_pdm_get_byte_count(rtd, |
| + substream->stream); |
| + pdm_status = check_pdm_dma_status(rtd->acp_base); |
| + if (!pdm_status) |
| + ret = start_pdm_dma(rtd->acp_base); |
| + break; |
| + case SNDRV_PCM_TRIGGER_STOP: |
| + case SNDRV_PCM_TRIGGER_SUSPEND: |
| + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
| + pdm_status = check_pdm_dma_status(rtd->acp_base); |
| + if (pdm_status) |
| + ret = stop_pdm_dma(rtd->acp_base); |
| + break; |
| + default: |
| + ret = -EINVAL; |
| + break; |
| + } |
| + return ret; |
| +} |
| + |
| +static struct snd_soc_dai_ops acp_pdm_dai_ops = { |
| + .trigger = acp_pdm_dai_trigger, |
| +}; |
| + |
| +static struct snd_soc_dai_driver acp_pdm_dai_driver = { |
| + .capture = { |
| + .rates = SNDRV_PCM_RATE_48000, |
| + .formats = SNDRV_PCM_FMTBIT_S24_LE | |
| + SNDRV_PCM_FMTBIT_S32_LE, |
| + .channels_min = 2, |
| + .channels_max = 2, |
| + .rate_min = 48000, |
| + .rate_max = 48000, |
| + }, |
| + .ops = &acp_pdm_dai_ops, |
| +}; |
| + |
| +static const struct snd_soc_component_driver acp_pdm_component = { |
| + .name = DRV_NAME, |
| + .open = acp_pdm_dma_open, |
| + .close = acp_pdm_dma_close, |
| + .hw_params = acp_pdm_dma_hw_params, |
| + .pointer = acp_pdm_dma_pointer, |
| + .mmap = acp_pdm_dma_mmap, |
| + .pcm_construct = acp_pdm_dma_new, |
| +}; |
| + |
| +static int acp_pdm_audio_probe(struct platform_device *pdev) |
| +{ |
| + struct resource *res; |
| + struct pdm_dev_data *adata; |
| + int status; |
| + |
| + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| + if (!res) { |
| + dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n"); |
| + return -ENODEV; |
| + } |
| + |
| + adata = devm_kzalloc(&pdev->dev, sizeof(*adata), GFP_KERNEL); |
| + if (!adata) |
| + return -ENOMEM; |
| + |
| + adata->acp_base = devm_ioremap(&pdev->dev, res->start, |
| + resource_size(res)); |
| + if (!adata->acp_base) |
| + return -ENOMEM; |
| + |
| + adata->capture_stream = NULL; |
| + |
| + dev_set_drvdata(&pdev->dev, adata); |
| + status = devm_snd_soc_register_component(&pdev->dev, |
| + &acp_pdm_component, |
| + &acp_pdm_dai_driver, 1); |
| + if (status) { |
| + dev_err(&pdev->dev, "Fail to register acp pdm dai\n"); |
| + |
| + return -ENODEV; |
| + } |
| + pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS); |
| + pm_runtime_use_autosuspend(&pdev->dev); |
| + pm_runtime_enable(&pdev->dev); |
| + pm_runtime_allow(&pdev->dev); |
| + return 0; |
| +} |
| + |
| +static int acp_pdm_audio_remove(struct platform_device *pdev) |
| +{ |
| + pm_runtime_disable(&pdev->dev); |
| + return 0; |
| +} |
| + |
| +static int acp_pdm_resume(struct device *dev) |
| +{ |
| + struct pdm_dev_data *adata; |
| + struct snd_pcm_runtime *runtime; |
| + struct pdm_stream_instance *rtd; |
| + u32 period_bytes, buffer_len; |
| + |
| + adata = dev_get_drvdata(dev); |
| + if (adata->capture_stream && adata->capture_stream->runtime) { |
| + runtime = adata->capture_stream->runtime; |
| + rtd = runtime->private_data; |
| + period_bytes = frames_to_bytes(runtime, runtime->period_size); |
| + buffer_len = frames_to_bytes(runtime, runtime->buffer_size); |
| + config_acp_dma(rtd, SNDRV_PCM_STREAM_CAPTURE); |
| + init_pdm_ring_buffer(MEM_WINDOW_START, buffer_len, period_bytes, |
| + adata->acp_base); |
| + } |
| + enable_pdm_interrupts(adata->acp_base); |
| + return 0; |
| +} |
| + |
| +static int acp_pdm_runtime_suspend(struct device *dev) |
| +{ |
| + struct pdm_dev_data *adata; |
| + |
| + adata = dev_get_drvdata(dev); |
| + disable_pdm_interrupts(adata->acp_base); |
| + return 0; |
| +} |
| + |
| +static int acp_pdm_runtime_resume(struct device *dev) |
| +{ |
| + struct pdm_dev_data *adata; |
| + |
| + adata = dev_get_drvdata(dev); |
| + enable_pdm_interrupts(adata->acp_base); |
| + return 0; |
| +} |
| + |
| +static const struct dev_pm_ops acp_pdm_pm_ops = { |
| + .runtime_suspend = acp_pdm_runtime_suspend, |
| + .runtime_resume = acp_pdm_runtime_resume, |
| + .resume = acp_pdm_resume, |
| +}; |
| + |
| +static struct platform_driver acp_pdm_dma_driver = { |
| + .probe = acp_pdm_audio_probe, |
| + .remove = acp_pdm_audio_remove, |
| + .driver = { |
| + .name = "acp_rn_pdm_dma", |
| + .pm = &acp_pdm_pm_ops, |
| + }, |
| +}; |
| + |
| +module_platform_driver(acp_pdm_dma_driver); |
| + |
| +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); |
| +MODULE_DESCRIPTION("AMD ACP3x Renior PDM Driver"); |
| +MODULE_LICENSE("GPL v2"); |
| +MODULE_ALIAS("platform:" DRV_NAME); |
| diff --git a/sound/soc/amd/renoir/rn-pci-acp3x.c b/sound/soc/amd/renoir/rn-pci-acp3x.c |
| index b3812b70f5f9..5063dcceabb3 100644 |
| --- a/sound/soc/amd/renoir/rn-pci-acp3x.c |
| +++ b/sound/soc/amd/renoir/rn-pci-acp3x.c |
| @@ -6,33 +6,36 @@ |
| |
| #include <linux/pci.h> |
| #include <linux/acpi.h> |
| -#include <linux/dmi.h> |
| #include <linux/module.h> |
| #include <linux/io.h> |
| #include <linux/delay.h> |
| #include <linux/platform_device.h> |
| #include <linux/interrupt.h> |
| #include <linux/pm_runtime.h> |
| - |
| +#include <sound/pcm_params.h> |
| #include "rn_acp3x.h" |
| |
| static int acp_power_gating; |
| module_param(acp_power_gating, int, 0644); |
| MODULE_PARM_DESC(acp_power_gating, "Enable acp power gating"); |
| |
| -/* |
| - * dmic_acpi_check = -1 - Use ACPI/DMI method to detect the DMIC hardware presence at runtime |
| - * = 0 - Skip the DMIC device creation and return probe failure |
| - * = 1 - Force DMIC support |
| +/** |
| + * dmic_acpi_check = -1 - Checks ACPI method to know DMIC hardware status runtime |
| + * = 0 - Skips the DMIC device creation and returns probe failure |
| + * = 1 - Assumes that platform has DMIC support and skips ACPI |
| + * method check |
| */ |
| -static int dmic_acpi_check = ACP_DMIC_AUTO; |
| +//static int dmic_acpi_check = ACP_DMIC_AUTO; |
| +static int dmic_acpi_check = 1; |
| module_param(dmic_acpi_check, bint, 0644); |
| -MODULE_PARM_DESC(dmic_acpi_check, "Digital microphone presence (-1=auto, 0=none, 1=force)"); |
| +MODULE_PARM_DESC(dmic_acpi_check, "checks Dmic hardware runtime"); |
| |
| struct acp_dev_data { |
| void __iomem *acp_base; |
| struct resource *res; |
| + bool acp3x_audio_mode; |
| struct platform_device *pdev[ACP_DEVS]; |
| + u32 pme_en; |
| }; |
| |
| static int rn_acp_power_on(void __iomem *acp_base) |
| @@ -102,18 +105,14 @@ static int rn_acp_reset(void __iomem *acp_base) |
| |
| static void rn_acp_enable_interrupts(void __iomem *acp_base) |
| { |
| - u32 ext_intr_ctrl; |
| - |
| rn_writel(0x01, acp_base + ACP_EXTERNAL_INTR_ENB); |
| - ext_intr_ctrl = rn_readl(acp_base + ACP_EXTERNAL_INTR_CNTL); |
| - ext_intr_ctrl |= ACP_ERROR_MASK; |
| - rn_writel(ext_intr_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL); |
| } |
| |
| static void rn_acp_disable_interrupts(void __iomem *acp_base) |
| { |
| rn_writel(ACP_EXT_INTR_STAT_CLEAR_MASK, acp_base + |
| ACP_EXTERNAL_INTR_STAT); |
| + rn_writel(0x00, acp_base + ACP_EXTERNAL_INTR_CNTL); |
| rn_writel(0x00, acp_base + ACP_EXTERNAL_INTR_ENB); |
| } |
| |
| @@ -163,44 +162,75 @@ static int rn_acp_deinit(void __iomem *acp_base) |
| return 0; |
| } |
| |
| -static const struct dmi_system_id rn_acp_quirk_table[] = { |
| - { |
| - /* Lenovo IdeaPad S340-14API */ |
| - .matches = { |
| - DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "LENOVO"), |
| - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "81NB"), |
| - } |
| - }, |
| - { |
| - /* Lenovo IdeaPad Flex 5 14ARE05 */ |
| - .matches = { |
| - DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "LENOVO"), |
| - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "81X2"), |
| - } |
| - }, |
| - { |
| - /* Lenovo IdeaPad 5 15ARE05 */ |
| - .matches = { |
| - DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "LENOVO"), |
| - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "81YQ"), |
| - } |
| - }, |
| - { |
| - /* Lenovo ThinkPad E14 Gen 2 */ |
| - .matches = { |
| - DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "LENOVO"), |
| - DMI_EXACT_MATCH(DMI_BOARD_NAME, "20T6CTO1WW"), |
| - } |
| - }, |
| - { |
| - /* Lenovo ThinkPad X395 */ |
| - .matches = { |
| - DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "LENOVO"), |
| - DMI_EXACT_MATCH(DMI_BOARD_NAME, "20NLCTO1WW"), |
| - } |
| - }, |
| - {} |
| -}; |
| +static irqreturn_t acp_irq_handler(int irq, void *dev_id) |
| +{ |
| + struct acp_dev_data *adata; |
| + struct i2s_dev_data *rn_i2s_data; |
| + struct pdm_dev_data *rn_pdm_data; |
| + u16 play_flag, cap_flag, err_flag; |
| + u32 val; |
| + u32 err; |
| + |
| + adata = dev_id; |
| + if (!adata) |
| + return IRQ_NONE; |
| + |
| + play_flag = 0; |
| + cap_flag = 0; |
| + err_flag = 0; |
| + err = 0; |
| + val = rn_readl(adata->acp_base + ACP_EXTERNAL_INTR_STAT); |
| + if (val & BIT(BT_TX_THRESHOLD)) { |
| + rn_i2s_data = dev_get_drvdata(&adata->pdev[0]->dev); |
| + rn_writel(BIT(BT_TX_THRESHOLD), adata->acp_base + |
| + ACP_EXTERNAL_INTR_STAT); |
| + if (rn_i2s_data->play_stream) |
| + snd_pcm_period_elapsed(rn_i2s_data->play_stream); |
| + play_flag = 1; |
| + } |
| + if (val & BIT(I2S_TX_THRESHOLD)) { |
| + rn_i2s_data = dev_get_drvdata(&adata->pdev[0]->dev); |
| + rn_writel(BIT(I2S_TX_THRESHOLD), |
| + adata->acp_base + ACP_EXTERNAL_INTR_STAT); |
| + if (rn_i2s_data->i2ssp_play_stream) |
| + snd_pcm_period_elapsed(rn_i2s_data->i2ssp_play_stream); |
| + play_flag = 1; |
| + } |
| + |
| + if (val & BIT(BT_RX_THRESHOLD)) { |
| + rn_i2s_data = dev_get_drvdata(&adata->pdev[0]->dev); |
| + rn_writel(BIT(BT_RX_THRESHOLD), adata->acp_base + |
| + ACP_EXTERNAL_INTR_STAT); |
| + if (rn_i2s_data->capture_stream) |
| + snd_pcm_period_elapsed(rn_i2s_data->capture_stream); |
| + cap_flag = 1; |
| + } |
| + if (val & BIT(I2S_RX_THRESHOLD)) { |
| + rn_i2s_data = dev_get_drvdata(&adata->pdev[0]->dev); |
| + rn_writel(BIT(I2S_RX_THRESHOLD), |
| + adata->acp_base + ACP_EXTERNAL_INTR_STAT); |
| + if (rn_i2s_data->i2ssp_capture_stream) |
| + snd_pcm_period_elapsed(rn_i2s_data->i2ssp_capture_stream); |
| + cap_flag = 1; |
| + } |
| + if (val & BIT(PDM_DMA_STAT)) { |
| + rn_pdm_data = dev_get_drvdata(&adata->pdev[1]->dev); |
| + rn_writel(BIT(PDM_DMA_STAT), adata->acp_base + |
| + ACP_EXTERNAL_INTR_STAT); |
| + if (rn_pdm_data->capture_stream) |
| + snd_pcm_period_elapsed(rn_pdm_data->capture_stream); |
| + cap_flag = 1; |
| + } |
| + if (val & BIT(29)) { |
| + err = rn_readl(adata->acp_base + ACP_SW_I2S_ERROR_REASON); |
| + rn_writel(BIT(29), adata->acp_base + ACP_EXTERNAL_INTR_STAT); |
| + err_flag = 1; |
| + } |
| + if (play_flag | cap_flag | err_flag) |
| + return IRQ_HANDLED; |
| + else |
| + return IRQ_NONE; |
| +} |
| |
| static int snd_rn_acp_probe(struct pci_dev *pci, |
| const struct pci_device_id *pci_id) |
| @@ -211,9 +241,10 @@ static int snd_rn_acp_probe(struct pci_dev *pci, |
| acpi_handle handle; |
| acpi_integer dmic_status; |
| #endif |
| - const struct dmi_system_id *dmi_id; |
| unsigned int irqflags, flag; |
| int ret, index; |
| + int acp_devs = 0x00; |
| + int val = 0x00; |
| u32 addr; |
| |
| /* Return if acp config flag is defined */ |
| @@ -221,10 +252,6 @@ static int snd_rn_acp_probe(struct pci_dev *pci, |
| if (flag) |
| return -ENODEV; |
| |
| - /* Renoir device check */ |
| - if (pci->revision != 0x01) |
| - return -ENODEV; |
| - |
| if (pci_enable_device(pci)) { |
| dev_err(&pci->dev, "pci_enable_device failed\n"); |
| return -ENODEV; |
| @@ -243,27 +270,21 @@ static int snd_rn_acp_probe(struct pci_dev *pci, |
| goto release_regions; |
| } |
| |
| - /* check for msi interrupt support */ |
| - ret = pci_enable_msi(pci); |
| - if (ret) |
| - /* msi is not enabled */ |
| - irqflags = IRQF_SHARED; |
| - else |
| - /* msi is enabled */ |
| - irqflags = 0; |
| + irqflags = IRQF_SHARED; |
| |
| addr = pci_resource_start(pci, 0); |
| adata->acp_base = devm_ioremap(&pci->dev, addr, |
| pci_resource_len(pci, 0)); |
| if (!adata->acp_base) { |
| ret = -ENOMEM; |
| - goto disable_msi; |
| + goto release_regions; |
| } |
| pci_set_master(pci); |
| pci_set_drvdata(pci, adata); |
| + adata->pme_en = rn_readl(adata->acp_base + ACP_PME_EN); |
| ret = rn_acp_init(adata->acp_base); |
| if (ret) |
| - goto disable_msi; |
| + goto release_regions; |
| |
| if (!dmic_acpi_check) { |
| ret = -ENODEV; |
| @@ -281,56 +302,133 @@ static int snd_rn_acp_probe(struct pci_dev *pci, |
| goto de_init; |
| } |
| #endif |
| - dmi_id = dmi_first_match(rn_acp_quirk_table); |
| - if (dmi_id && !dmi_id->driver_data) { |
| - dev_info(&pci->dev, "ACPI settings override using DMI (ACP mic is not present)"); |
| - ret = -ENODEV; |
| + } |
| + rn_writel(0x04, adata->acp_base + ACP_I2S_PIN_CONFIG); |
| + val = rn_readl(adata->acp_base + ACP_I2S_PIN_CONFIG); |
| + pr_info("ACP_I2S_PIN_CONFIG: %d\n", val); |
| + switch (val) { |
| + case I2S_MODE: |
| + adata->res = devm_kzalloc(&pci->dev, |
| + sizeof(struct resource) * 3, |
| + GFP_KERNEL); |
| + if (!adata->res) { |
| + ret = -ENOMEM; |
| goto de_init; |
| } |
| - } |
| - |
| - adata->res = devm_kzalloc(&pci->dev, |
| - sizeof(struct resource) * 2, |
| - GFP_KERNEL); |
| - if (!adata->res) { |
| - ret = -ENOMEM; |
| - goto de_init; |
| - } |
| |
| - adata->res[0].name = "acp_pdm_iomem"; |
| - adata->res[0].flags = IORESOURCE_MEM; |
| - adata->res[0].start = addr; |
| - adata->res[0].end = addr + (ACP_REG_END - ACP_REG_START); |
| - adata->res[1].name = "acp_pdm_irq"; |
| - adata->res[1].flags = IORESOURCE_IRQ; |
| - adata->res[1].start = pci->irq; |
| - adata->res[1].end = pci->irq; |
| - |
| - memset(&pdevinfo, 0, sizeof(pdevinfo)); |
| - pdevinfo[0].name = "acp_rn_pdm_dma"; |
| - pdevinfo[0].id = 0; |
| - pdevinfo[0].parent = &pci->dev; |
| - pdevinfo[0].num_res = 2; |
| - pdevinfo[0].res = adata->res; |
| - pdevinfo[0].data = &irqflags; |
| - pdevinfo[0].size_data = sizeof(irqflags); |
| - |
| - pdevinfo[1].name = "dmic-codec"; |
| - pdevinfo[1].id = 0; |
| - pdevinfo[1].parent = &pci->dev; |
| - pdevinfo[2].name = "acp_pdm_mach"; |
| - pdevinfo[2].id = 0; |
| - pdevinfo[2].parent = &pci->dev; |
| - for (index = 0; index < ACP_DEVS; index++) { |
| - adata->pdev[index] = |
| + adata->res[0].name = "acp_iomem"; |
| + adata->res[0].flags = IORESOURCE_MEM; |
| + adata->res[0].start = addr; |
| + adata->res[0].end = addr + (ACP_REG_END - ACP_REG_START); |
| + |
| + adata->res[1].name = "acp_i2s_sp"; |
| + adata->res[1].flags = IORESOURCE_MEM; |
| + adata->res[1].start = addr + ACP_I2STDM_REG_START; |
| + adata->res[1].end = addr + ACP_I2STDM_REG_END; |
| + |
| + adata->res[2].name = "acp_i2s_bt"; |
| + adata->res[2].flags = IORESOURCE_MEM; |
| + adata->res[2].start = addr + ACP_BT_TDM_REG_START; |
| + adata->res[2].end = addr + ACP_BT_TDM_REG_END; |
| + |
| + adata->acp3x_audio_mode = ACP_I2S_MODE; |
| + |
| + memset(&pdevinfo, 0, sizeof(pdevinfo)); |
| + pdevinfo[0].name = "acp_rn_i2s_dma"; |
| + pdevinfo[0].id = 0; |
| + pdevinfo[0].parent = &pci->dev; |
| + pdevinfo[0].num_res = 3; |
| + pdevinfo[0].res = &adata->res[0]; |
| + |
| + pdevinfo[1].name = "acp_rn_pdm_dma"; |
| + pdevinfo[1].id = 1; |
| + pdevinfo[1].parent = &pci->dev; |
| + pdevinfo[1].num_res = 1; |
| + pdevinfo[1].res = &adata->res[0]; |
| + |
| + pdevinfo[2].name = "acp_i2s_playcap"; |
| + pdevinfo[2].id = 0; |
| + pdevinfo[2].parent = &pci->dev; |
| + pdevinfo[2].num_res = 1; |
| + pdevinfo[2].res = &adata->res[1]; |
| + |
| + pdevinfo[3].name = "acp_i2s_playcap"; |
| + pdevinfo[3].id = 1; |
| + pdevinfo[3].parent = &pci->dev; |
| + pdevinfo[3].num_res = 1; |
| + pdevinfo[3].res = &adata->res[2]; |
| + |
| + pdevinfo[4].name = "dmic-codec"; |
| + pdevinfo[4].id = 0; |
| + pdevinfo[4].parent = &pci->dev; |
| + |
| + pdevinfo[5].name = "acp_rn_mi_mach"; |
| + pdevinfo[5].id = 0; |
| + pdevinfo[5].parent = &pci->dev; |
| + |
| + acp_devs = 6; |
| + for (index = 0; index < acp_devs; index++) { |
| + adata->pdev[index] = |
| platform_device_register_full(&pdevinfo[index]); |
| - if (IS_ERR(adata->pdev[index])) { |
| - dev_err(&pci->dev, "cannot register %s device\n", |
| - pdevinfo[index].name); |
| - ret = PTR_ERR(adata->pdev[index]); |
| - goto unregister_devs; |
| + if (IS_ERR(adata->pdev[index])) { |
| + dev_err(&pci->dev, "cannot register %s device\n", |
| + pdevinfo[index].name); |
| + ret = PTR_ERR(adata->pdev[index]); |
| + goto unregister_devs; |
| + } |
| } |
| + break; |
| + default: |
| + adata->res = devm_kzalloc(&pci->dev, |
| + sizeof(struct resource) * 2, |
| + GFP_KERNEL); |
| + if (!adata->res) { |
| + ret = -ENOMEM; |
| + goto de_init; |
| + } |
| + adata->res[0].name = "acp_pdm_iomem"; |
| + adata->res[0].flags = IORESOURCE_MEM; |
| + adata->res[0].start = addr; |
| + adata->res[0].end = addr + (ACP_REG_END - ACP_REG_START); |
| + adata->res[1].name = "acp_pdm_irq"; |
| + adata->res[1].flags = IORESOURCE_IRQ; |
| + adata->res[1].start = pci->irq; |
| + adata->res[1].end = pci->irq; |
| + |
| + memset(&pdevinfo, 0, sizeof(pdevinfo)); |
| + pdevinfo[0].name = "acp_rn_pdm_dma"; |
| + pdevinfo[0].id = 0; |
| + pdevinfo[0].parent = &pci->dev; |
| + pdevinfo[0].num_res = 2; |
| + pdevinfo[0].res = adata->res; |
| + pdevinfo[0].data = &irqflags; |
| + pdevinfo[0].size_data = sizeof(irqflags); |
| + |
| + pdevinfo[1].name = "dmic-codec"; |
| + pdevinfo[1].id = 0; |
| + pdevinfo[1].parent = &pci->dev; |
| + pdevinfo[2].name = "acp_pdm_mach"; |
| + pdevinfo[2].id = 0; |
| + pdevinfo[2].parent = &pci->dev; |
| + acp_devs = 3; |
| + for (index = 0; index < acp_devs; index++) { |
| + adata->pdev[index] = |
| + platform_device_register_full(&pdevinfo[index]); |
| + if (IS_ERR(adata->pdev[index])) { |
| + dev_err(&pci->dev, "cannot register %s device\n", |
| + pdevinfo[index].name); |
| + ret = PTR_ERR(adata->pdev[index]); |
| + goto unregister_devs; |
| + } |
| + } |
| + } |
| + ret = devm_request_irq(&pci->dev, pci->irq, acp_irq_handler, |
| + irqflags, "ACP_PCI_IRQ", adata); |
| + if (ret) { |
| + dev_err(&pci->dev, "ACP PCI IRQ request failed\n"); |
| + return -ENODEV; |
| } |
| + |
| pm_runtime_set_autosuspend_delay(&pci->dev, ACP_SUSPEND_DELAY_MS); |
| pm_runtime_use_autosuspend(&pci->dev); |
| pm_runtime_put_noidle(&pci->dev); |
| @@ -338,13 +436,11 @@ static int snd_rn_acp_probe(struct pci_dev *pci, |
| return 0; |
| |
| unregister_devs: |
| - for (index = 0; index < ACP_DEVS; index++) |
| + for (index = 0; index < acp_devs; index++) |
| platform_device_unregister(adata->pdev[index]); |
| de_init: |
| if (rn_acp_deinit(adata->acp_base)) |
| dev_err(&pci->dev, "ACP de-init failed\n"); |
| -disable_msi: |
| - pci_disable_msi(pci); |
| release_regions: |
| pci_release_regions(pci); |
| disable_pci: |
| @@ -359,6 +455,7 @@ static int snd_rn_acp_suspend(struct device *dev) |
| struct acp_dev_data *adata; |
| |
| adata = dev_get_drvdata(dev); |
| + pr_info("%s\n", __func__); |
| ret = rn_acp_deinit(adata->acp_base); |
| if (ret) |
| dev_err(dev, "ACP de-init failed\n"); |
| @@ -374,6 +471,7 @@ static int snd_rn_acp_resume(struct device *dev) |
| struct acp_dev_data *adata; |
| |
| adata = dev_get_drvdata(dev); |
| + pr_info("%s\n", __func__); |
| ret = rn_acp_init(adata->acp_base); |
| if (ret) { |
| dev_err(dev, "ACP init failed\n"); |
| @@ -395,16 +493,26 @@ static void snd_rn_acp_remove(struct pci_dev *pci) |
| { |
| struct acp_dev_data *adata; |
| int ret, index; |
| + int acp_devs = 0x00; |
| + u32 val = 0x00; |
| |
| adata = pci_get_drvdata(pci); |
| - for (index = 0; index < ACP_DEVS; index++) |
| + |
| + val = rn_readl(adata->acp_base + ACP_I2S_PIN_CONFIG); |
| + switch (val) { |
| + case I2S_MODE: |
| + acp_devs = 6; |
| + break; |
| + default: |
| + acp_devs = 3; |
| + } |
| + for (index = 0; index < acp_devs; index++) |
| platform_device_unregister(adata->pdev[index]); |
| ret = rn_acp_deinit(adata->acp_base); |
| if (ret) |
| dev_err(&pci->dev, "ACP de-init failed\n"); |
| pm_runtime_forbid(&pci->dev); |
| pm_runtime_get_noresume(&pci->dev); |
| - pci_disable_msi(pci); |
| pci_release_regions(pci); |
| pci_disable_device(pci); |
| } |
| diff --git a/sound/soc/amd/renoir/rn_acp3x.h b/sound/soc/amd/renoir/rn_acp3x.h |
| index ca586603d720..3a19d6992c22 100644 |
| --- a/sound/soc/amd/renoir/rn_acp3x.h |
| +++ b/sound/soc/amd/renoir/rn_acp3x.h |
| @@ -6,11 +6,16 @@ |
| */ |
| |
| #include "rn_chip_offset_byte.h" |
| +#include <sound/pcm.h> |
| |
| -#define ACP_DEVS 3 |
| +#define ACP_DEVS 6 |
| #define ACP_PHY_BASE_ADDRESS 0x1240000 |
| #define ACP_REG_START 0x1240000 |
| #define ACP_REG_END 0x1250200 |
| +#define ACP_I2STDM_REG_START 0x1242400 |
| +#define ACP_I2STDM_REG_END 0x1242410 |
| +#define ACP_BT_TDM_REG_START 0x1242800 |
| +#define ACP_BT_TDM_REG_END 0x1242810 |
| |
| #define ACP_DEVICE_ID 0x15E2 |
| #define ACP_POWER_ON 0x00 |
| @@ -56,6 +61,95 @@ |
| #define MAX_BUFFER (CAPTURE_MAX_PERIOD_SIZE * CAPTURE_MAX_NUM_PERIODS) |
| #define MIN_BUFFER MAX_BUFFER |
| #define ACP_DMIC_AUTO -1 |
| +#define PDM_OFFSET 0x8000 |
| +#define I2S_SP_INSTANCE 0x01 |
| +#define I2S_BT_INSTANCE 0x02 |
| + |
| +#define TDM_ENABLE 1 |
| +#define TDM_DISABLE 0 |
| + |
| +#define ACP_I2S_MODE 0x00 |
| +#define I2S_MODE 0x04 |
| +#define I2S_RX_THRESHOLD 27 |
| +#define I2S_TX_THRESHOLD 28 |
| +#define BT_TX_THRESHOLD 26 |
| +#define BT_RX_THRESHOLD 25 |
| + |
| +#define BT_RX_INTR_MASK 0x2000000 |
| +#define BT_TX_INTR_MASK 0x4000000 |
| +#define I2S_RX_INTR_MASK 0x8000000 |
| +#define I2S_TX_INTR_MASK 0x10000000 |
| + |
| +#define ACP_SRAM_SP_PB_PTE_OFFSET 0x60 |
| +#define ACP_SRAM_SP_CP_PTE_OFFSET 0x100 |
| +#define ACP_SRAM_BT_PB_PTE_OFFSET 0x200 |
| +#define ACP_SRAM_BT_CP_PTE_OFFSET 0x300 |
| +#define PAGE_SIZE_4K_ENABLE 0x2 |
| +#define I2S_SP_TX_MEM_WINDOW_START 0x400C000 |
| +#define I2S_SP_RX_MEM_WINDOW_START 0x4020000 |
| +#define I2S_BT_TX_MEM_WINDOW_START 0x4040000 |
| +#define I2S_BT_RX_MEM_WINDOW_START 0x4060000 |
| + |
| +#define SP_PB_FIFO_ADDR_OFFSET 0x500 |
| +#define SP_CAPT_FIFO_ADDR_OFFSET 0x700 |
| +#define BT_PB_FIFO_ADDR_OFFSET 0x900 |
| +#define BT_CAPT_FIFO_ADDR_OFFSET 0xB00 |
| + |
| +#define I2S_PLAYBACK_MIN_NUM_PERIODS 2 |
| +#define I2S_PLAYBACK_MAX_NUM_PERIODS 8 |
| +#define I2S_PLAYBACK_MAX_PERIOD_SIZE 8192 |
| +#define I2S_PLAYBACK_MIN_PERIOD_SIZE 1024 |
| +#define I2S_CAPTURE_MIN_NUM_PERIODS 2 |
| +#define I2S_CAPTURE_MAX_NUM_PERIODS 8 |
| +#define I2S_CAPTURE_MAX_PERIOD_SIZE 8192 |
| +#define I2S_CAPTURE_MIN_PERIOD_SIZE 1024 |
| + |
| +#define I2S_MAX_BUFFER (I2S_PLAYBACK_MAX_PERIOD_SIZE * I2S_PLAYBACK_MAX_NUM_PERIODS) |
| +#define I2S_MIN_BUFFER I2S_MAX_BUFFER |
| +#define FIFO_SIZE 0x100 |
| +#define DMA_SIZE 0x40 |
| +#define FRM_LEN 0x100 |
| + |
| +#define SLOT_WIDTH_8 0x08 |
| +#define SLOT_WIDTH_16 0x10 |
| +#define SLOT_WIDTH_24 0x18 |
| +#define SLOT_WIDTH_32 0x20 |
| +#define ACP3x_ITER_IRER_SAMP_LEN_MASK 0x38 |
| + |
| +#define PDM_PTE_OFFSET 0x00 |
| +#define PDM_MEM_WINDOW_START 0x4000000 |
| + |
| +struct acp3x_platform_info { |
| + u16 play_i2s_instance; |
| + u16 cap_i2s_instance; |
| + u16 capture_channel; |
| +}; |
| + |
| +struct i2s_dev_data { |
| + bool tdm_mode; |
| + unsigned int i2s_irq; |
| + u16 i2s_instance; |
| + u32 tdm_fmt; |
| + u32 substream_type; |
| + void __iomem *acp_base; |
| + struct snd_pcm_substream *play_stream; |
| + struct snd_pcm_substream *capture_stream; |
| + struct snd_pcm_substream *i2ssp_play_stream; |
| + struct snd_pcm_substream *i2ssp_capture_stream; |
| +}; |
| + |
| +struct i2s_stream_instance { |
| + u16 num_pages; |
| + u16 i2s_instance; |
| + u16 capture_channel; |
| + u16 direction; |
| + u16 channels; |
| + u32 xfer_resolution; |
| + u32 val; |
| + dma_addr_t dma_addr; |
| + u64 bytescount; |
| + void __iomem *acp_base; |
| +}; |
| |
| struct pdm_dev_data { |
| u32 pdm_irq; |
| @@ -91,3 +185,45 @@ static inline void rn_writel(u32 val, void __iomem *base_addr) |
| |
| /* Machine configuration */ |
| int snd_amd_acp_find_config(struct pci_dev *pci); |
| + |
| +static inline u64 acp_get_byte_count(struct i2s_stream_instance *rtd, |
| + int direction) |
| +{ |
| + u64 byte_count; |
| + |
| + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + byte_count = rn_readl(rtd->acp_base + |
| + ACP_BT_TX_LINEARPOSITIONCNTR_HIGH); |
| + byte_count |= rn_readl(rtd->acp_base + |
| + ACP_BT_TX_LINEARPOSITIONCNTR_LOW); |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + byte_count = rn_readl(rtd->acp_base + |
| + ACP_I2S_TX_LINEARPOSITIONCNTR_HIGH); |
| + byte_count |= rn_readl(rtd->acp_base + |
| + ACP_I2S_TX_LINEARPOSITIONCNTR_LOW); |
| + } |
| + } else { |
| + switch (rtd->i2s_instance) { |
| + case I2S_BT_INSTANCE: |
| + byte_count = rn_readl(rtd->acp_base + |
| + ACP_BT_RX_LINEARPOSITIONCNTR_HIGH); |
| + byte_count |= rn_readl(rtd->acp_base + |
| + ACP_BT_RX_LINEARPOSITIONCNTR_LOW); |
| + break; |
| + case I2S_SP_INSTANCE: |
| + default: |
| + byte_count = rn_readl(rtd->acp_base + |
| + ACP_I2S_RX_LINEARPOSITIONCNTR_HIGH); |
| + byte_count |= rn_readl(rtd->acp_base + |
| + ACP_I2S_RX_LINEARPOSITIONCNTR_LOW); |
| + } |
| + } |
| + return byte_count; |
| +} |
| + |
| +void i2sdma_wq(struct i2s_dev_data *dev_data); |
| +void pdmdma_wq(struct pdm_dev_data *dev_data); |
| -- |
| 2.35.0 |
| |