diff options
Diffstat (limited to 'sound/soc/amd')
28 files changed, 4000 insertions, 11 deletions
diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig index 49ff5e73e9ba..2c6af3f8f296 100644 --- a/sound/soc/amd/Kconfig +++ b/sound/soc/amd/Kconfig @@ -6,6 +6,7 @@ config SND_SOC_AMD_ACP config SND_SOC_AMD_CZ_DA7219MX98357_MACH tristate "AMD CZ support for DA7219, RT5682 and MAX9835" + select CLK_FIXED_FCH select SND_SOC_DA7219 select SND_SOC_RT5682_I2C select SND_SOC_MAX98357A @@ -30,13 +31,14 @@ config SND_SOC_AMD_ACP3x config SND_SOC_AMD_RV_RT5682_MACH tristate "AMD RV support for RT5682" + select CLK_FIXED_FCH select SND_SOC_RT5682_I2C select SND_SOC_MAX98357A select SND_SOC_CROS_EC_CODEC select I2C_CROS_EC_TUNNEL select SND_SOC_RT1015 select SND_SOC_RT1015P - depends on SND_SOC_AMD_ACP3x && I2C && CROS_EC + depends on SND_SOC_AMD_ACP3x && I2C && CROS_EC && GPIOLIB help This option enables machine driver for RT5682 and MAX9835. @@ -49,7 +51,7 @@ config SND_SOC_AMD_RENOIR config SND_SOC_AMD_RENOIR_MACH tristate "AMD Renoir support for DMIC" select SND_SOC_DMIC - depends on SND_SOC_AMD_RENOIR + depends on SND_SOC_AMD_RENOIR && GPIOLIB help This option enables machine driver for DMIC @@ -61,3 +63,37 @@ config SND_SOC_AMD_ACP5x By enabling this flag build will trigger for ACP PCI driver, ACP DMA driver, CPU DAI driver. + +config SND_SOC_AMD_VANGOGH_MACH + tristate "AMD Vangogh support for NAU8821 CS35L41" + select SND_SOC_NAU8821 + select SND_SOC_CS35L41_SPI + depends on SND_SOC_AMD_ACP5x && I2C + help + This option enables machine driver for Vangogh platform + using NAU8821 and CS35L41 codecs. + Say m if you have such a device. + If unsure select "N". + +config SND_SOC_AMD_ACP6x + tristate "AMD Audio Coprocessor-v6.x Yellow Carp support" + depends on X86 && PCI + help + This option enables Audio Coprocessor i.e ACP v6.x support on + AMD Yellow Carp platform. By enabling this flag build will be + triggered for ACP PCI driver, ACP PDM DMA driver. + Say m if you have such a device. + If unsure select "N". + +config SND_SOC_AMD_YC_MACH + tristate "AMD YC support for DMIC" + select SND_SOC_DMIC + depends on SND_SOC_AMD_ACP6x + help + This option enables machine driver for Yellow Carp platform + using dmic. ACP IP has PDM Decoder block with DMA controller. + DMIC can be connected directly to ACP IP. + Say m if you have such a device. + If unsure select "N". + +source "sound/soc/amd/acp/Kconfig" diff --git a/sound/soc/amd/Makefile b/sound/soc/amd/Makefile index 07150d26f315..f1d42bbda709 100644 --- a/sound/soc/amd/Makefile +++ b/sound/soc/amd/Makefile @@ -11,3 +11,5 @@ obj-$(CONFIG_SND_SOC_AMD_ACP3x) += raven/ obj-$(CONFIG_SND_SOC_AMD_RV_RT5682_MACH) += snd-soc-acp-rt5682-mach.o obj-$(CONFIG_SND_SOC_AMD_RENOIR) += renoir/ obj-$(CONFIG_SND_SOC_AMD_ACP5x) += vangogh/ +obj-$(CONFIG_SND_SOC_AMD_ACP6x) += yc/ +obj-$(CONFIG_SND_SOC_AMD_ACP_COMMON) += acp/ diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c index b3df98a9f9f3..b2065f3fe42c 100644 --- a/sound/soc/amd/acp-da7219-max98357a.c +++ b/sound/soc/amd/acp-da7219-max98357a.c @@ -33,7 +33,7 @@ static struct clk *da7219_dai_wclk; static struct clk *da7219_dai_bclk; static struct clk *rt5682_dai_wclk; static struct clk *rt5682_dai_bclk; -extern bool bt_uart_enable; + void *acp_soc_is_rltk_max(struct device *dev); static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd) @@ -760,8 +760,8 @@ static int cz_probe(struct platform_device *pdev) "devm_snd_soc_register_card(%s) failed\n", card->name); } - bt_uart_enable = !device_property_read_bool(&pdev->dev, - "bt-pad-enable"); + acp_bt_uart_enable = !device_property_read_bool(&pdev->dev, + "bt-pad-enable"); return 0; } diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c index 11b3c4f39eba..1f322accd9ea 100644 --- a/sound/soc/amd/acp-pcm-dma.c +++ b/sound/soc/amd/acp-pcm-dma.c @@ -36,8 +36,8 @@ #define ST_MIN_BUFFER ST_MAX_BUFFER #define DRV_NAME "acp_audio_dma" -bool bt_uart_enable = true; -EXPORT_SYMBOL(bt_uart_enable); +bool acp_bt_uart_enable = true; +EXPORT_SYMBOL(acp_bt_uart_enable); static const struct snd_pcm_hardware acp_pcm_hardware_playback = { .info = SNDRV_PCM_INFO_INTERLEAVED | @@ -596,7 +596,7 @@ static int acp_init(void __iomem *acp_mmio, u32 asic_type) acp_reg_write(val, acp_mmio, mmACP_SOFT_RESET); /* For BT instance change pins from UART to BT */ - if (!bt_uart_enable) { + if (!acp_bt_uart_enable) { val = acp_reg_read(acp_mmio, mmACP_BT_UART_PAD_SEL); val |= ACP_BT_UART_PAD_SELECT_MASK; acp_reg_write(val, acp_mmio, mmACP_BT_UART_PAD_SEL); diff --git a/sound/soc/amd/acp-rt5645.c b/sound/soc/amd/acp-rt5645.c index d6ba94677ac2..6d5c547a32de 100644 --- a/sound/soc/amd/acp-rt5645.c +++ b/sound/soc/amd/acp-rt5645.c @@ -91,7 +91,7 @@ static int cz_init(struct snd_soc_pcm_runtime *rtd) return 0; } -static struct snd_soc_ops cz_aif1_ops = { +static const struct snd_soc_ops cz_aif1_ops = { .hw_params = cz_aif1_hw_params, }; diff --git a/sound/soc/amd/acp.h b/sound/soc/amd/acp.h index e5ab6c6040a6..85529ed7e5f5 100644 --- a/sound/soc/amd/acp.h +++ b/sound/soc/amd/acp.h @@ -204,4 +204,6 @@ typedef struct acp_dma_dscr_transfer { u32 reserved; } acp_dma_dscr_transfer_t; +extern bool acp_bt_uart_enable; + #endif /*__ACP_HW_H */ diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig new file mode 100644 index 000000000000..52a1371f9e61 --- /dev/null +++ b/sound/soc/amd/acp/Kconfig @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +# This file is provided under a dual BSD/GPLv2 license. When using or +# redistributing this file, you may do so under either license. +# +# Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. +# + +config SND_SOC_AMD_ACP_COMMON + tristate "AMD Audio ACP Common support" + select SND_AMD_ACP_CONFIG + depends on X86 && PCI + help + This option enables common modules for Audio-Coprocessor i.e. ACP + IP block on AMD platforms. + +if SND_SOC_AMD_ACP_COMMON + +config SND_SOC_AMD_ACP_I2S + tristate + +config SND_SOC_AMD_ACP_PCM + tristate + select SND_SOC_ACPI if ACPI + +config SND_AMD_ASOC_RENOIR + tristate "AMD ACP ASOC Renoir Support" + select SND_SOC_AMD_ACP_PCM + select SND_SOC_AMD_ACP_I2S + depends on X86 && PCI + help + This option enables Renoir I2S support on AMD platform. + +config SND_SOC_AMD_MACH_COMMON + tristate + depends on X86 && PCI && I2C + select CLK_FIXED_FCH + select SND_SOC_RT5682_I2C + select SND_SOC_DMIC + select SND_SOC_RT1019 + select SND_SOC_MAX98357A + select SND_SOC_RT5682S + help + This option enables common Machine driver module for ACP. + +config SND_SOC_AMD_LEGACY_MACH + tristate "AMD Legacy Machine Driver Support" + depends on X86 && PCI && I2C + select SND_SOC_AMD_MACH_COMMON + depends on X86 && PCI && I2C + help + This option enables legacy sound card support for ACP audio. + +config SND_SOC_AMD_SOF_MACH + tristate "AMD SOF Machine Driver Support" + depends on X86 && PCI && I2C + select SND_SOC_AMD_MACH_COMMON + depends on X86 && PCI && I2C + help + This option enables SOF sound card support for ACP audio. + +endif # SND_SOC_AMD_ACP_COMMON diff --git a/sound/soc/amd/acp/Makefile b/sound/soc/amd/acp/Makefile new file mode 100644 index 000000000000..16c144c2965c --- /dev/null +++ b/sound/soc/amd/acp/Makefile @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +# This file is provided under a dual BSD/GPLv2 license. When using or +# redistributing this file, you may do so under either license. +# +# Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + +#common acp driver +snd-acp-pcm-objs := acp-platform.o +snd-acp-i2s-objs := acp-i2s.o + +#platform specific driver +snd-acp-renoir-objs := acp-renoir.o + +#machine specific driver +snd-acp-mach-objs := acp-mach-common.o +snd-acp-legacy-mach-objs := acp-legacy-mach.o +snd-acp-sof-mach-objs := acp-sof-mach.o + +obj-$(CONFIG_SND_SOC_AMD_ACP_PCM) += snd-acp-pcm.o +obj-$(CONFIG_SND_SOC_AMD_ACP_I2S) += snd-acp-i2s.o + +obj-$(CONFIG_SND_AMD_ASOC_RENOIR) += snd-acp-renoir.o + +obj-$(CONFIG_SND_SOC_AMD_MACH_COMMON) += snd-acp-mach.o +obj-$(CONFIG_SND_SOC_AMD_LEGACY_MACH) += snd-acp-legacy-mach.o +obj-$(CONFIG_SND_SOC_AMD_SOF_MACH) += snd-acp-sof-mach.o diff --git a/sound/soc/amd/acp/acp-i2s.c b/sound/soc/amd/acp/acp-i2s.c new file mode 100644 index 000000000000..ce9aca8dd6f5 --- /dev/null +++ b/sound/soc/amd/acp/acp-i2s.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* + * Generic Hardware interface for ACP Audio I2S controller + */ + +#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 "amd.h" + +#define DRV_NAME "acp_i2s_playcap" + +static int acp_i2s_hwparams(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata; + u32 val; + u32 xfer_resolution; + u32 reg_val; + + adata = snd_soc_dai_get_drvdata(dai); + + /* These values are as per Hardware Spec */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U8: + case SNDRV_PCM_FORMAT_S8: + xfer_resolution = 0x0; + break; + case SNDRV_PCM_FORMAT_S16_LE: + xfer_resolution = 0x02; + break; + case SNDRV_PCM_FORMAT_S24_LE: + xfer_resolution = 0x04; + break; + case SNDRV_PCM_FORMAT_S32_LE: + xfer_resolution = 0x05; + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + reg_val = ACP_BTTDM_ITER; + break; + case I2S_SP_INSTANCE: + reg_val = ACP_I2STDM_ITER; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } else { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + reg_val = ACP_BTTDM_IRER; + break; + case I2S_SP_INSTANCE: + reg_val = ACP_I2STDM_IRER; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } + + val = readl(adata->acp_base + reg_val); + val &= ~ACP3x_ITER_IRER_SAMP_LEN_MASK; + val = val | (xfer_resolution << 3); + writel(val, adata->acp_base + reg_val); + + return 0; +} + +static int acp_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) +{ + struct acp_stream *stream = substream->runtime->private_data; + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + u32 val, period_bytes, reg_val, ier_val, water_val, buf_size, buf_reg; + + 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: + stream->bytescount = acp_get_byte_count(adata, stream->dai_id, substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai->driver->id) { + 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: + water_val = ACP_I2S_TX_INTR_WATERMARK_SIZE; + reg_val = ACP_I2STDM_ITER; + ier_val = ACP_I2STDM_IER; + buf_reg = ACP_I2S_TX_RINGBUFSIZE; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } else { + switch (dai->driver->id) { + 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: + water_val = ACP_I2S_RX_INTR_WATERMARK_SIZE; + reg_val = ACP_I2STDM_IRER; + ier_val = ACP_I2STDM_IER; + buf_reg = ACP_I2S_RX_RINGBUFSIZE; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } + writel(period_bytes, adata->acp_base + water_val); + writel(buf_size, adata->acp_base + buf_reg); + val = readl(adata->acp_base + reg_val); + val = val | BIT(0); + writel(val, adata->acp_base + reg_val); + writel(1, adata->acp_base + ier_val); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + reg_val = ACP_BTTDM_ITER; + break; + case I2S_SP_INSTANCE: + reg_val = ACP_I2STDM_ITER; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + + } else { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + reg_val = ACP_BTTDM_IRER; + break; + case I2S_SP_INSTANCE: + reg_val = ACP_I2STDM_IRER; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } + val = readl(adata->acp_base + reg_val); + val = val & ~BIT(0); + writel(val, adata->acp_base + reg_val); + + if (!(readl(adata->acp_base + ACP_BTTDM_ITER) & BIT(0)) && + !(readl(adata->acp_base + ACP_BTTDM_IRER) & BIT(0))) + writel(0, adata->acp_base + ACP_BTTDM_IER); + if (!(readl(adata->acp_base + ACP_I2STDM_ITER) & BIT(0)) && + !(readl(adata->acp_base + ACP_I2STDM_IRER) & BIT(0))) + writel(0, adata->acp_base + ACP_I2STDM_IER); + return 0; + default: + return -EINVAL; + } + + return 0; +} + +static int acp_i2s_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_stream *stream = substream->runtime->private_data; + u32 reg_dma_size = 0, reg_fifo_size = 0, reg_fifo_addr = 0; + u32 phy_addr = 0, acp_fifo_addr = 0, ext_int_ctrl; + unsigned int dir = substream->stream; + + switch (dai->driver->id) { + case I2S_SP_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + 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; + + phy_addr = I2S_SP_TX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_I2S_TX_RINGBUFADDR); + } else { + 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; + phy_addr = I2S_SP_RX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_I2S_RX_RINGBUFADDR); + } + break; + case I2S_BT_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + 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; + + phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_BT_TX_RINGBUFADDR); + } else { + 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; + + phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_BT_RX_RINGBUFADDR); + } + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + + writel(DMA_SIZE, adata->acp_base + reg_dma_size); + writel(acp_fifo_addr, adata->acp_base + reg_fifo_addr); + writel(FIFO_SIZE, adata->acp_base + reg_fifo_size); + + ext_int_ctrl = readl(adata->acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_int_ctrl |= BIT(I2S_RX_THRESHOLD) | BIT(BT_RX_THRESHOLD) + | BIT(I2S_TX_THRESHOLD) | BIT(BT_TX_THRESHOLD); + + writel(ext_int_ctrl, adata->acp_base + ACP_EXTERNAL_INTR_CNTL); + + return 0; +} + +static int acp_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct acp_stream *stream = substream->runtime->private_data; + struct device *dev = dai->component->dev; + unsigned int dir = substream->stream; + unsigned int irq_bit = 0; + + switch (dai->driver->id) { + case I2S_SP_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + irq_bit = BIT(I2S_TX_THRESHOLD); + stream->pte_offset = ACP_SRAM_SP_PB_PTE_OFFSET; + stream->fifo_offset = SP_PB_FIFO_ADDR_OFFSET; + } else { + irq_bit = BIT(I2S_RX_THRESHOLD); + stream->pte_offset = ACP_SRAM_SP_CP_PTE_OFFSET; + stream->fifo_offset = SP_CAPT_FIFO_ADDR_OFFSET; + } + break; + case I2S_BT_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + irq_bit = BIT(BT_TX_THRESHOLD); + stream->pte_offset = ACP_SRAM_BT_PB_PTE_OFFSET; + stream->fifo_offset = BT_PB_FIFO_ADDR_OFFSET; + } else { + irq_bit = BIT(BT_RX_THRESHOLD); + stream->pte_offset = ACP_SRAM_BT_CP_PTE_OFFSET; + stream->fifo_offset = BT_CAPT_FIFO_ADDR_OFFSET; + } + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + + /* Save runtime dai configuration in stream */ + stream->id = dai->driver->id + dir; + stream->dai_id = dai->driver->id; + stream->irq_bit = irq_bit; + + return 0; +} + +const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .startup = acp_i2s_startup, + .hw_params = acp_i2s_hwparams, + .prepare = acp_i2s_prepare, + .trigger = acp_i2s_trigger, +}; +EXPORT_SYMBOL_NS_GPL(asoc_acp_cpu_dai_ops, SND_SOC_ACP_COMMON); + +int asoc_acp_i2s_probe(struct snd_soc_dai *dai) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + unsigned int val; + + if (!adata->acp_base) { + dev_err(dev, "I2S base is NULL\n"); + return -EINVAL; + } + + val = readl(adata->acp_base + ACP_I2S_PIN_CONFIG); + if (val != I2S_MODE) { + dev_err(dev, "I2S Mode not supported val %x\n", val); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(asoc_acp_i2s_probe, SND_SOC_ACP_COMMON); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS(DRV_NAME); diff --git a/sound/soc/amd/acp/acp-legacy-mach.c b/sound/soc/amd/acp/acp-legacy-mach.c new file mode 100644 index 000000000000..de0f8024e2fb --- /dev/null +++ b/sound/soc/amd/acp/acp-legacy-mach.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* + * Machine Driver Legacy Support for ACP HW block + */ + +#include <sound/core.h> +#include <sound/pcm_params.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <linux/module.h> + +#include "acp-mach.h" + +static struct acp_card_drvdata rt5682_rt1019_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = NONE, + .hs_codec_id = RT5682, + .amp_codec_id = RT1019, + .dmic_codec_id = NONE, +}; + +static const struct snd_kcontrol_new acp_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), + +}; + +static const struct snd_soc_dapm_widget acp_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static int acp_asoc_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = NULL; + struct device *dev = &pdev->dev; + int ret; + + if (!pdev->id_entry) + return -EINVAL; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = pdev->id_entry->name; + card->dapm_widgets = acp_widgets; + card->num_dapm_widgets = ARRAY_SIZE(acp_widgets); + card->controls = acp_controls; + card->num_controls = ARRAY_SIZE(acp_controls); + card->drvdata = (struct acp_card_drvdata *)pdev->id_entry->driver_data; + + acp_legacy_dai_links_create(card); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, + "devm_snd_soc_register_card(%s) failed: %d\n", + card->name, ret); + return ret; + } + + return 0; +} + +static const struct platform_device_id board_ids[] = { + { + .name = "rn_rt5682_rt1019", + .driver_data = (kernel_ulong_t)&rt5682_rt1019_data, + }, + { } +}; +static struct platform_driver acp_asoc_audio = { + .driver = { + .name = "acp_mach", + }, + .probe = acp_asoc_probe, + .id_table = board_ids, +}; + +module_platform_driver(acp_asoc_audio); + +MODULE_IMPORT_NS(SND_SOC_AMD_MACH); +MODULE_DESCRIPTION("ACP chrome audio support"); +MODULE_ALIAS("platform:rn_rt5682_rt1019"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp/acp-mach-common.c b/sound/soc/amd/acp/acp-mach-common.c new file mode 100644 index 000000000000..7785f12aa006 --- /dev/null +++ b/sound/soc/amd/acp/acp-mach-common.c @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// Vijendar Mukunda <Vijendar.Mukunda@amd.com> +// + +/* + * Machine Driver Interface for ACP HW block + */ + +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/soc.h> +#include <linux/input.h> +#include <linux/module.h> + +#include "../../codecs/rt5682.h" +#include "../../codecs/rt1019.h" +#include "../../codecs/rt5682s.h" +#include "acp-mach.h" + +#define PCO_PLAT_CLK 48000000 +#define RT5682_PLL_FREQ (48000 * 512) +#define DUAL_CHANNEL 2 +#define FOUR_CHANNEL 4 + +static struct snd_soc_jack pco_jack; + +static const unsigned int channels[] = { + DUAL_CHANNEL, +}; + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int acp_clk_enable(struct acp_card_drvdata *drvdata) +{ + clk_set_rate(drvdata->wclk, 48000); + clk_set_rate(drvdata->bclk, 48000 * 64); + + return clk_prepare_enable(drvdata->wclk); +} + +/* Declare RT5682 codec components */ +SND_SOC_DAILINK_DEF(rt5682, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00", "rt5682-aif1"))); + +static const struct snd_soc_dapm_route rt5682_map[] = { + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + { "IN1P", NULL, "Headset Mic" }, +}; + +/* Define card ops for RT5682 CODEC */ +static int acp_card_rt5682_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + int ret; + + dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name); + + if (drvdata->hs_codec_id != RT5682) + return -EINVAL; + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, RT5682_PLL2, RT5682_PLL2_S_MCLK, + PCO_PLAT_CLK, RT5682_PLL_FREQ); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set codec PLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL2, + RT5682_PLL_FREQ, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set codec SYSCLK: %d\n", ret); + return ret; + } + + /* Set tdm/i2s1 master bclk ratio */ + ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set rt5682 tdm bclk ratio: %d\n", ret); + return ret; + } + + drvdata->wclk = clk_get(component->dev, "rt5682-dai-wclk"); + drvdata->bclk = clk_get(component->dev, "rt5682-dai-bclk"); + + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_LINEOUT | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &pco_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, &pco_jack, NULL); + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return snd_soc_dapm_add_routes(&rtd->card->dapm, rt5682_map, ARRAY_SIZE(rt5682_map)); +} + +static int acp_card_hs_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret); + return ret; + } + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + ret = acp_clk_enable(drvdata); + if (ret < 0) + dev_err(rtd->card->dev, "Failed to enable HS clk: %d\n", ret); + + return ret; +} + +static void acp_card_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + + clk_disable_unprepare(drvdata->wclk); +} + +static const struct snd_soc_ops acp_card_rt5682_ops = { + .startup = acp_card_hs_startup, + .shutdown = acp_card_shutdown, +}; + +/* Define RT5682S CODEC component*/ +SND_SOC_DAILINK_DEF(rt5682s, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-RTL5682:00", "rt5682s-aif1"))); + +static const struct snd_soc_dapm_route rt5682s_map[] = { + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + { "IN1P", NULL, "Headset Mic" }, +}; + +static int acp_card_rt5682s_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + int ret; + + dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name); + + if (drvdata->hs_codec_id != RT5682S) + return -EINVAL; + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, RT5682S_PLL2, RT5682S_PLL_S_MCLK, + PCO_PLAT_CLK, RT5682_PLL_FREQ); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set codec PLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682S_SCLK_S_PLL2, + RT5682_PLL_FREQ, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set codec SYSCLK: %d\n", ret); + return ret; + } + + /* Set tdm/i2s1 master bclk ratio */ + ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set rt5682 tdm bclk ratio: %d\n", ret); + return ret; + } + + drvdata->wclk = clk_get(component->dev, "rt5682-dai-wclk"); + drvdata->bclk = clk_get(component->dev, "rt5682-dai-bclk"); + + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_LINEOUT | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &pco_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, &pco_jack, NULL); + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return snd_soc_dapm_add_routes(&rtd->card->dapm, rt5682s_map, ARRAY_SIZE(rt5682s_map)); +} + +static const struct snd_soc_ops acp_card_rt5682s_ops = { + .startup = acp_card_hs_startup, + .shutdown = acp_card_shutdown, +}; + +/* Declare RT1019 codec components */ +SND_SOC_DAILINK_DEF(rt1019, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC1019:01", "rt1019-aif"), + COMP_CODEC("i2c-10EC1019:02", "rt1019-aif"))); + +static const struct snd_soc_dapm_route rt1019_map_lr[] = { + { "Left Spk", NULL, "Left SPO" }, + { "Right Spk", NULL, "Right SPO" }, +}; + +static struct snd_soc_codec_conf rt1019_conf[] = { + { + .dlc = COMP_CODEC_CONF("i2c-10EC1019:01"), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF("i2c-10EC1019:02"), + .name_prefix = "Right", + }, +}; + +static int acp_card_rt1019_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + + if (drvdata->amp_codec_id != RT1019) + return -EINVAL; + + return snd_soc_dapm_add_routes(&rtd->card->dapm, rt1019_map_lr, + ARRAY_SIZE(rt1019_map_lr)); +} + +static int acp_card_rt1019_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai; + int srate, i, ret = 0; + + srate = params_rate(params); + + if (drvdata->amp_codec_id != RT1019) + return -EINVAL; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + if (strcmp(codec_dai->name, "rt1019-aif")) + continue; + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT1019_PLL_S_BCLK, + 64 * srate, 256 * srate); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT1019_SCLK_S_PLL, + 256 * srate, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + } + + return 0; +} + +static int acp_card_amp_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + int ret; + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + ret = acp_clk_enable(drvdata); + if (ret < 0) + dev_err(rtd->card->dev, "Failed to enable AMP clk: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops acp_card_rt1019_ops = { + .startup = acp_card_amp_startup, + .shutdown = acp_card_shutdown, + .hw_params = acp_card_rt1019_hw_params, +}; + +/* Declare Maxim codec components */ +SND_SOC_DAILINK_DEF(max98360a, + DAILINK_COMP_ARRAY(COMP_CODEC("MX98360A:00", "HiFi"))); + +static const struct snd_soc_dapm_route max98360a_map[] = { + {"Spk", NULL, "Speaker"}, +}; + +static int acp_card_maxim_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + + if (drvdata->amp_codec_id != MAX98360A) + return -EINVAL; + + return snd_soc_dapm_add_routes(&rtd->card->dapm, max98360a_map, + ARRAY_SIZE(max98360a_map)); +} + +static const struct snd_soc_ops acp_card_maxim_ops = { + .startup = acp_card_amp_startup, + .shutdown = acp_card_shutdown, +}; + +/* Declare DMIC codec components */ +SND_SOC_DAILINK_DEF(dmic_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi"))); + +/* Declare ACP CPU components */ +static struct snd_soc_dai_link_component dummy_codec[] = { + { + .name = "snd-soc-dummy", + .dai_name = "snd-soc-dummy-dai", + } +}; + +static struct snd_soc_dai_link_component platform_component[] = { + { + .name = "acp_asoc_renoir.0", + } +}; + +static struct snd_soc_dai_link_component sof_component[] = { + { + .name = "0000:04:00.5", + } +}; + +SND_SOC_DAILINK_DEF(i2s_sp, + DAILINK_COMP_ARRAY(COMP_CPU("acp-i2s-sp"))); +SND_SOC_DAILINK_DEF(sof_sp, + DAILINK_COMP_ARRAY(COMP_CPU("acp-sof-sp"))); +SND_SOC_DAILINK_DEF(sof_dmic, + DAILINK_COMP_ARRAY(COMP_CPU("acp-sof-dmic"))); + +int acp_sofdsp_dai_links_create(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *links; + struct device *dev = card->dev; + struct acp_card_drvdata *drv_data = card->drvdata; + int i = 0, num_links = 0; + + if (drv_data->hs_cpu_id) + num_links++; + if (drv_data->amp_cpu_id) + num_links++; + if (drv_data->dmic_cpu_id) + num_links++; + + links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * num_links, GFP_KERNEL); + if (!links) + return -ENOMEM; + + if (drv_data->hs_cpu_id == I2S_SP) { + links[i].name = "acp-headset-codec"; + links[i].id = HEADSET_BE_ID; + links[i].cpus = sof_sp; + links[i].num_cpus = ARRAY_SIZE(sof_sp); + links[i].platforms = sof_component; + links[i].num_platforms = ARRAY_SIZE(sof_component); + links[i].dpcm_playback = 1; + links[i].dpcm_capture = 1; + links[i].nonatomic = true; + links[i].no_pcm = 1; + if (!drv_data->hs_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->hs_codec_id == RT5682) { + links[i].codecs = rt5682; + links[i].num_codecs = ARRAY_SIZE(rt5682); + links[i].init = acp_card_rt5682_init; + links[i].ops = &acp_card_rt5682_ops; + } + if (drv_data->hs_codec_id == RT5682S) { + links[i].codecs = rt5682s; + links[i].num_codecs = ARRAY_SIZE(rt5682s); + links[i].init = acp_card_rt5682s_init; + links[i].ops = &acp_card_rt5682s_ops; + } + i++; + } + + if (drv_data->amp_cpu_id == I2S_SP) { + links[i].name = "acp-amp-codec"; + links[i].id = AMP_BE_ID; + links[i].cpus = sof_sp; + links[i].num_cpus = ARRAY_SIZE(sof_sp); + links[i].platforms = sof_component; + links[i].num_platforms = ARRAY_SIZE(sof_component); + links[i].dpcm_playback = 1; + links[i].nonatomic = true; + links[i].no_pcm = 1; + if (!drv_data->amp_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->amp_codec_id == RT1019) { + links[i].codecs = rt1019; + links[i].num_codecs = ARRAY_SIZE(rt1019); + links[i].ops = &acp_card_rt1019_ops; + links[i].init = acp_card_rt1019_init; + card->codec_conf = rt1019_conf; + card->num_configs = ARRAY_SIZE(rt1019_conf); + } + if (drv_data->amp_codec_id == MAX98360A) { + links[i].codecs = max98360a; + links[i].num_codecs = ARRAY_SIZE(max98360a); + links[i].ops = &acp_card_maxim_ops; + links[i].init = acp_card_maxim_init; + } + i++; + } + + if (drv_data->dmic_cpu_id == DMIC) { + links[i].name = "acp-dmic-codec"; + links[i].id = DMIC_BE_ID; + links[i].codecs = dmic_codec; + links[i].num_codecs = ARRAY_SIZE(dmic_codec); + links[i].cpus = sof_dmic; + links[i].num_cpus = ARRAY_SIZE(sof_dmic); + links[i].platforms = sof_component; + links[i].num_platforms = ARRAY_SIZE(sof_component); + links[i].dpcm_capture = 1; + links[i].nonatomic = true; + links[i].no_pcm = 1; + } + + card->dai_link = links; + card->num_links = num_links; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_sofdsp_dai_links_create, SND_SOC_AMD_MACH); + +int acp_legacy_dai_links_create(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *links; + struct device *dev = card->dev; + struct acp_card_drvdata *drv_data = card->drvdata; + int i = 0, num_links = 0; + + if (drv_data->hs_cpu_id) + num_links++; + if (drv_data->amp_cpu_id) + num_links++; + if (drv_data->dmic_cpu_id) + num_links++; + + links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * num_links, GFP_KERNEL); + + if (drv_data->hs_cpu_id == I2S_SP) { + links[i].name = "acp-headset-codec"; + links[i].id = HEADSET_BE_ID; + links[i].cpus = i2s_sp; + links[i].num_cpus = ARRAY_SIZE(i2s_sp); + links[i].platforms = platform_component; + links[i].num_platforms = ARRAY_SIZE(platform_component); + links[i].dpcm_playback = 1; + links[i].dpcm_capture = 1; + if (!drv_data->hs_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->hs_codec_id == RT5682) { + links[i].codecs = rt5682; + links[i].num_codecs = ARRAY_SIZE(rt5682); + links[i].init = acp_card_rt5682_init; + links[i].ops = &acp_card_rt5682_ops; + } + if (drv_data->hs_codec_id == RT5682S) { + links[i].codecs = rt5682s; + links[i].num_codecs = ARRAY_SIZE(rt5682s); + links[i].init = acp_card_rt5682s_init; + links[i].ops = &acp_card_rt5682s_ops; + } + i++; + } + + if (drv_data->amp_cpu_id == I2S_SP) { + links[i].name = "acp-amp-codec"; + links[i].id = AMP_BE_ID; + links[i].cpus = i2s_sp; + links[i].num_cpus = ARRAY_SIZE(i2s_sp); + links[i].platforms = platform_component; + links[i].num_platforms = ARRAY_SIZE(platform_component); + links[i].dpcm_playback = 1; + if (!drv_data->amp_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->amp_codec_id == RT1019) { + links[i].codecs = rt1019; + links[i].num_codecs = ARRAY_SIZE(rt1019); + links[i].ops = &acp_card_rt1019_ops; + links[i].init = acp_card_rt1019_init; + card->codec_conf = rt1019_conf; + card->num_configs = ARRAY_SIZE(rt1019_conf); + } + if (drv_data->amp_codec_id == MAX98360A) { + links[i].codecs = max98360a; + links[i].num_codecs = ARRAY_SIZE(max98360a); + links[i].ops = &acp_card_maxim_ops; + links[i].init = acp_card_maxim_init; + } + } + + card->dai_link = links; + card->num_links = num_links; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_legacy_dai_links_create, SND_SOC_AMD_MACH); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp/acp-mach.h b/sound/soc/amd/acp/acp-mach.h new file mode 100644 index 000000000000..5dc47cfbff10 --- /dev/null +++ b/sound/soc/amd/acp/acp-mach.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + */ +#ifndef __ACP_MACH_H +#define __ACP_MACH_H + +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/input.h> +#include <linux/module.h> +#include <sound/soc.h> + +enum be_id { + HEADSET_BE_ID = 0, + AMP_BE_ID, + DMIC_BE_ID, +}; + +enum cpu_endpoints { + NONE = 0, + I2S_SP, + I2S_BT, + DMIC, +}; + +enum codec_endpoints { + DUMMY = 0, + RT5682, + RT1019, + MAX98360A, + RT5682S, +}; + +struct acp_card_drvdata { + unsigned int hs_cpu_id; + unsigned int amp_cpu_id; + unsigned int dmic_cpu_id; + unsigned int hs_codec_id; + unsigned int amp_codec_id; + unsigned int dmic_codec_id; + unsigned int dai_fmt; + struct clk *wclk; + struct clk *bclk; +}; + +int acp_sofdsp_dai_links_create(struct snd_soc_card *card); +int acp_legacy_dai_links_create(struct snd_soc_card *card); + +#endif diff --git a/sound/soc/amd/acp/acp-platform.c b/sound/soc/amd/acp/acp-platform.c new file mode 100644 index 000000000000..65a809e2c29f --- /dev/null +++ b/sound/soc/amd/acp/acp-platform.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/* + * Generic interface for ACP audio blck PCM component + */ + +#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/pm_runtime.h> +#include <linux/dma-mapping.h> + +#include "amd.h" + +#define DRV_NAME "acp_i2s_dma" + +static const struct snd_pcm_hardware acp_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 = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_MIN_NUM_PERIODS, + .periods_max = PLAYBACK_MAX_NUM_PERIODS, +}; + +static const struct snd_pcm_hardware acp_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 = 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, +}; + +int acp_machine_select(struct acp_dev_data *adata) +{ + struct snd_soc_acpi_mach *mach; + int size; + + size = sizeof(*adata->machines); + mach = snd_soc_acpi_find_machine(adata->machines); + if (!mach) { + dev_err(adata->dev, "warning: No matching ASoC machine driver found\n"); + return -EINVAL; + } + + adata->mach_dev = platform_device_register_data(adata->dev, mach->drv_name, + PLATFORM_DEVID_NONE, mach, size); + if (IS_ERR(adata->mach_dev)) + dev_warn(adata->dev, "Unable to register Machine device\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_machine_select, SND_SOC_ACP_COMMON); + +static irqreturn_t i2s_irq_handler(int irq, void *data) +{ + struct acp_dev_data *adata = data; + struct acp_stream *stream; + u16 i2s_flag = 0; + u32 val, i; + + if (!adata) + return IRQ_NONE; + + val = readl(adata->acp_base + ACP_EXTERNAL_INTR_STAT); + + for (i = 0; i < ACP_MAX_STREAM; i++) { + stream = adata->stream[i]; + if (stream && (val & stream->irq_bit)) { + writel(stream->irq_bit, adata->acp_base + ACP_EXTERNAL_INTR_STAT); + snd_pcm_period_elapsed(stream->substream); + i2s_flag = 1; + break; + } + } + + if (i2s_flag) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static void config_pte_for_stream(struct acp_dev_data *adata, struct acp_stream *stream) +{ + u32 pte_reg, pte_size, reg_val; + + /* Use ATU base Group5 */ + pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_5; + pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5; + stream->reg_offset = 0x02000000; + + /* Group Enable */ + reg_val = ACP_SRAM_PTE_OFFSET; + writel(reg_val | BIT(31), adata->acp_base + pte_reg); + writel(PAGE_SIZE_4K_ENABLE, adata->acp_base + pte_size); +} + +static void config_acp_dma(struct acp_dev_data *adata, int cpu_id, int size) +{ + struct acp_stream *stream = adata->stream[cpu_id]; + struct snd_pcm_substream *substream = stream->substream; + dma_addr_t addr = substream->dma_buffer.addr; + int num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT); + u32 low, high, val; + u16 page_idx; + + val = stream->pte_offset; + + for (page_idx = 0; page_idx < 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); + writel(low, adata->acp_base + ACP_SCRATCH_REG_0 + val); + high |= BIT(31); + writel(high, adata->acp_base + ACP_SCRATCH_REG_0 + val + 4); + + /* Move to next physically contiguous page */ + val += 8; + addr += PAGE_SIZE; + } +} + +static int acp_dma_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_stream *stream; + int stream_id = cpu_dai->driver->id * 2 + substream->stream; + int ret; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->substream = substream; + adata->stream[stream_id] = stream; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = acp_pcm_hardware_playback; + else + runtime->hw = acp_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(stream); + return ret; + } + runtime->private_data = stream; + + writel(1, adata->acp_base + ACP_EXTERNAL_INTR_ENB); + + return ret; +} + +static int acp_dma_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct acp_dev_data *adata = snd_soc_component_get_drvdata(component); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct acp_stream *stream = substream->runtime->private_data; + int stream_id = cpu_dai->driver->id * 2 + substream->stream; + u64 size = params_buffer_bytes(params); + + /* Configure ACP DMA block with params */ + config_pte_for_stream(adata, stream); + config_acp_dma(adata, stream_id, size); + + return 0; +} + +static snd_pcm_uframes_t acp_dma_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct device *dev = component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_stream *stream = substream->runtime->private_data; + u32 pos, buffersize; + u64 bytescount; + + buffersize = frames_to_bytes(substream->runtime, + substream->runtime->buffer_size); + + bytescount = acp_get_byte_count(adata, stream->dai_id, substream->stream); + + if (bytescount > stream->bytescount) + bytescount -= stream->bytescount; + + pos = do_div(bytescount, buffersize); + + return bytes_to_frames(substream->runtime, pos); +} + +static int acp_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_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_dma_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct device *dev = component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_stream *stream; + int stream_id = cpu_dai->driver->id * 2 + substream->stream; + + stream = adata->stream[stream_id]; + kfree(stream); + adata->stream[stream_id] = NULL; + + return 0; +} + +static const struct snd_soc_component_driver acp_pcm_component = { + .name = DRV_NAME, + .open = acp_dma_open, + .close = acp_dma_close, + .hw_params = acp_dma_hw_params, + .pointer = acp_dma_pointer, + .mmap = acp_dma_mmap, + .pcm_construct = acp_dma_new, +}; + +int acp_platform_register(struct device *dev) +{ + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct snd_soc_dai_driver; + unsigned int status; + + status = devm_request_irq(dev, adata->i2s_irq, i2s_irq_handler, + IRQF_SHARED, "ACP_I2S_IRQ", adata); + if (status) { + dev_err(dev, "ACP I2S IRQ request failed\n"); + return status; + } + + status = devm_snd_soc_register_component(dev, &acp_pcm_component, + adata->dai_driver, + adata->num_dai); + if (status) { + dev_err(dev, "Fail to register acp i2s component\n"); + return status; + } + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_platform_register, SND_SOC_ACP_COMMON); + +int acp_platform_unregister(struct device *dev) +{ + struct acp_dev_data *adata = dev_get_drvdata(dev); + + if (adata->mach_dev) + platform_device_unregister(adata->mach_dev); + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_platform_unregister, SND_SOC_ACP_COMMON); + +MODULE_DESCRIPTION("AMD ACP PCM Driver"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS(DRV_NAME); diff --git a/sound/soc/amd/acp/acp-renoir.c b/sound/soc/amd/acp/acp-renoir.c new file mode 100644 index 000000000000..9b321a055b52 --- /dev/null +++ b/sound/soc/amd/acp/acp-renoir.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* + * Hardware interface for Renoir ACP block + */ + +#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 "amd.h" + +#define DRV_NAME "acp_asoc_renoir" + +static struct snd_soc_acpi_codecs amp_rt1019 = { + .num_codecs = 1, + .codecs = {"10EC1019"} +}; + +static struct snd_soc_acpi_mach snd_soc_acpi_amd_acp_machines[] = { + { + .id = "10EC5682", + .drv_name = "rn_rt5682_rt1019", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_rt1019, + }, + { + .id = "AMDI1019", + .drv_name = "renoir-acp", + }, + {}, +}; + +static struct snd_soc_dai_driver acp_renoir_dai[] = { +{ + .name = "acp-i2s-sp", + .id = I2S_SP_INSTANCE, + .playback = { + .stream_name = "I2S SP Playback", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .stream_name = "I2S SP Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &asoc_acp_cpu_dai_ops, + .probe = &asoc_acp_i2s_probe, +}, +{ + .name = "acp-i2s-bt", + .id = I2S_BT_INSTANCE, + .playback = { + .stream_name = "I2S BT Playback", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .stream_name = "I2S BT Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &asoc_acp_cpu_dai_ops, + .probe = &asoc_acp_i2s_probe, +}, +}; + +static int renoir_audio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct acp_dev_data *adata; + struct resource *res; + + adata = devm_kzalloc(dev, sizeof(struct acp_dev_data), GFP_KERNEL); + if (!adata) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acp_mem"); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n"); + return -ENODEV; + } + + adata->acp_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!adata->acp_base) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "acp_dai_irq"); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n"); + return -ENODEV; + } + + adata->i2s_irq = res->start; + adata->dev = dev; + adata->dai_driver = acp_renoir_dai; + adata->num_dai = ARRAY_SIZE(acp_renoir_dai); + + adata->machines = snd_soc_acpi_amd_acp_machines; + acp_machine_select(adata); + + dev_set_drvdata(dev, adata); + acp_platform_register(dev); + + return 0; +} + +static int renoir_audio_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + acp_platform_unregister(dev); + return 0; +} + +static struct platform_driver renoir_driver = { + .probe = renoir_audio_probe, + .remove = renoir_audio_remove, + .driver = { + .name = "acp_asoc_renoir", + }, +}; + +module_platform_driver(renoir_driver); + +MODULE_DESCRIPTION("AMD ACP Renoir Driver"); +MODULE_IMPORT_NS(SND_SOC_ACP_COMMON); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amd/acp/acp-sof-mach.c b/sound/soc/amd/acp/acp-sof-mach.c new file mode 100644 index 000000000000..854eb7214cea --- /dev/null +++ b/sound/soc/amd/acp/acp-sof-mach.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* + * SOF Machine Driver Support for ACP HW block + */ + +#include <sound/core.h> +#include <sound/pcm_params.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <linux/module.h> + +#include "acp-mach.h" + +static struct acp_card_drvdata sof_rt5682_rt1019_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682, + .amp_codec_id = RT1019, + .dmic_codec_id = DMIC, +}; + +static struct acp_card_drvdata sof_rt5682_max_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682, + .amp_codec_id = MAX98360A, + .dmic_codec_id = DMIC, +}; + +static struct acp_card_drvdata sof_rt5682s_max_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682S, + .amp_codec_id = MAX98360A, + .dmic_codec_id = DMIC, +}; + +static const struct snd_kcontrol_new acp_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static const struct snd_soc_dapm_widget acp_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static int acp_sof_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = NULL; + struct device *dev = &pdev->dev; + int ret; + + if (!pdev->id_entry) + return -EINVAL; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = pdev->id_entry->name; + card->dapm_widgets = acp_widgets; + card->num_dapm_widgets = ARRAY_SIZE(acp_widgets); + card->controls = acp_controls; + card->num_controls = ARRAY_SIZE(acp_controls); + card->drvdata = (struct acp_card_drvdata *)pdev->id_entry->driver_data; + + acp_sofdsp_dai_links_create(card); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, + "devm_snd_soc_register_card(%s) failed: %d\n", + card->name, ret); + return ret; + } + + return 0; +} + +static const struct platform_device_id board_ids[] = { + { + .name = "rt5682-rt1019", + .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data + }, + { + .name = "rt5682-max", + .driver_data = (kernel_ulong_t)&sof_rt5682_max_data + }, + { + .name = "rt5682s-max", + .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data + }, + { } +}; +static struct platform_driver acp_asoc_audio = { + .driver = { + .name = "sof_mach", + }, + .probe = acp_sof_probe, + .id_table = board_ids, +}; + +module_platform_driver(acp_asoc_audio); + +MODULE_IMPORT_NS(SND_SOC_AMD_MACH); +MODULE_DESCRIPTION("ACP chrome SOF audio support"); +MODULE_ALIAS("platform:rt5682-rt1019"); +MODULE_ALIAS("platform:rt5682-max"); +MODULE_ALIAS("platform:rt5682s-max"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp/amd.h b/sound/soc/amd/acp/amd.h new file mode 100644 index 000000000000..8eee3d34774b --- /dev/null +++ b/sound/soc/amd/acp/amd.h @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + */ + +#ifndef __AMD_ACP_H +#define __AMD_ACP_H + +#include <sound/pcm.h> +#include <sound/soc-acpi.h> +#include "chip_offset_byte.h" + +#define I2S_SP_INSTANCE 0x00 +#define I2S_BT_INSTANCE 0x01 + +#define MEM_WINDOW_START 0x4000000 + +#define ACP_I2S_REG_START 0x1242400 +#define ACP_I2S_REG_END 0x1242810 +#define ACP3x_I2STDM_REG_START 0x1242400 +#define ACP3x_I2STDM_REG_END 0x1242410 +#define ACP3x_BT_TDM_REG_START 0x1242800 +#define ACP3x_BT_TDM_REG_END 0x1242810 +#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 ACP_SRAM_PTE_OFFSET 0x02052800 + +#define ACP_SRAM_SP_PB_PTE_OFFSET 0x0 +#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 0x4000000 +#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 PLAYBACK_MIN_NUM_PERIODS 2 +#define PLAYBACK_MAX_NUM_PERIODS 8 +#define PLAYBACK_MAX_PERIOD_SIZE 8192 +#define PLAYBACK_MIN_PERIOD_SIZE 1024 +#define CAPTURE_MIN_NUM_PERIODS 2 +#define CAPTURE_MAX_NUM_PERIODS 8 +#define CAPTURE_MAX_PERIOD_SIZE 8192 +#define CAPTURE_MIN_PERIOD_SIZE 1024 + +#define MAX_BUFFER 65536 +#define MIN_BUFFER MAX_BUFFER +#define FIFO_SIZE 0x100 +#define DMA_SIZE 0x40 +#define FRM_LEN 0x100 + +#define ACP3x_ITER_IRER_SAMP_LEN_MASK 0x38 + +#define ACP_MAX_STREAM 6 + +struct acp_stream { + struct snd_pcm_substream *substream; + int irq_bit; + int dai_id; + int id; + u64 bytescount; + u32 reg_offset; + u32 pte_offset; + u32 fifo_offset; +}; + +struct acp_dev_data { + char *name; + struct device *dev; + void __iomem *acp_base; + unsigned int i2s_irq; + + /* SOC specific dais */ + struct snd_soc_dai_driver *dai_driver; + int num_dai; + + struct acp_stream *stream[ACP_MAX_STREAM]; + + struct snd_soc_acpi_mach *machines; + struct platform_device *mach_dev; +}; + +extern const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops; + +int asoc_acp_i2s_probe(struct snd_soc_dai *dai); +int acp_platform_register(struct device *dev); +int acp_platform_unregister(struct device *dev); + +int acp_machine_select(struct acp_dev_data *adata); + +static inline u64 acp_get_byte_count(struct acp_dev_data *adata, int dai_id, int direction) +{ + u64 byte_count, low = 0, high = 0; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai_id) { + case I2S_BT_INSTANCE: + high = readl(adata->acp_base + ACP_BT_TX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_BT_TX_LINEARPOSITIONCNTR_LOW); + break; + case I2S_SP_INSTANCE: + high = readl(adata->acp_base + ACP_I2S_TX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_I2S_TX_LINEARPOSITIONCNTR_LOW); + break; + default: + dev_err(adata->dev, "Invalid dai id %x\n", dai_id); + return -EINVAL; + } + } else { + switch (dai_id) { + case I2S_BT_INSTANCE: + high = readl(adata->acp_base + ACP_BT_RX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_BT_RX_LINEARPOSITIONCNTR_LOW); + break; + case I2S_SP_INSTANCE: + high = readl(adata->acp_base + ACP_I2S_RX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_I2S_RX_LINEARPOSITIONCNTR_LOW); + break; + default: + dev_err(adata->dev, "Invalid dai id %x\n", dai_id); + return -EINVAL; + } + } + /* Get 64 bit value from two 32 bit registers */ + byte_count = (high << 32) | low; + + return byte_count; +} + +#endif diff --git a/sound/soc/amd/acp/chip_offset_byte.h b/sound/soc/amd/acp/chip_offset_byte.h new file mode 100644 index 000000000000..c7f77e975dc7 --- /dev/null +++ b/sound/soc/amd/acp/chip_offset_byte.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + */ + +#ifndef _ACP_IP_OFFSET_HEADER +#define _ACP_IP_OFFSET_HEADER + +#define ACPAXI2AXI_ATU_CTRL 0xC40 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5 0xC20 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_5 0xC24 +#define ACP_EXTERNAL_INTR_ENB 0x1800 +#define ACP_EXTERNAL_INTR_CNTL 0x1804 +#define ACP_EXTERNAL_INTR_STAT 0x1808 +#define ACP_I2S_PIN_CONFIG 0x1400 +#define ACP_SCRATCH_REG_0 0x12800 + +/* Registers from ACP_AUDIO_BUFFERS block */ + +#define ACP_I2S_RX_RINGBUFADDR 0x2000 +#define ACP_I2S_RX_RINGBUFSIZE 0x2004 +#define ACP_I2S_RX_LINKPOSITIONCNTR 0x2008 +#define ACP_I2S_RX_FIFOADDR 0x200C +#define ACP_I2S_RX_FIFOSIZE 0x2010 +#define ACP_I2S_RX_DMA_SIZE 0x2014 +#define ACP_I2S_RX_LINEARPOSITIONCNTR_HIGH 0x2018 +#define ACP_I2S_RX_LINEARPOSITIONCNTR_LOW 0x201C +#define ACP_I2S_RX_INTR_WATERMARK_SIZE 0x2020 +#define ACP_I2S_TX_RINGBUFADDR 0x2024 +#define ACP_I2S_TX_RINGBUFSIZE 0x2028 +#define ACP_I2S_TX_LINKPOSITIONCNTR 0x202C +#define ACP_I2S_TX_FIFOADDR 0x2030 +#define ACP_I2S_TX_FIFOSIZE 0x2034 +#define ACP_I2S_TX_DMA_SIZE 0x2038 +#define ACP_I2S_TX_LINEARPOSITIONCNTR_HIGH 0x203C +#define ACP_I2S_TX_LINEARPOSITIONCNTR_LOW 0x2040 +#define ACP_I2S_TX_INTR_WATERMARK_SIZE 0x2044 +#define ACP_BT_RX_RINGBUFADDR 0x2048 +#define ACP_BT_RX_RINGBUFSIZE 0x204C +#define ACP_BT_RX_LINKPOSITIONCNTR 0x2050 +#define ACP_BT_RX_FIFOADDR 0x2054 +#define ACP_BT_RX_FIFOSIZE 0x2058 +#define ACP_BT_RX_DMA_SIZE 0x205C +#define ACP_BT_RX_LINEARPOSITIONCNTR_HIGH 0x2060 +#define ACP_BT_RX_LINEARPOSITIONCNTR_LOW 0x2064 +#define ACP_BT_RX_INTR_WATERMARK_SIZE 0x2068 +#define ACP_BT_TX_RINGBUFADDR 0x206C +#define ACP_BT_TX_RINGBUFSIZE 0x2070 +#define ACP_BT_TX_LINKPOSITIONCNTR 0x2074 +#define ACP_BT_TX_FIFOADDR 0x2078 +#define ACP_BT_TX_FIFOSIZE 0x207C +#define ACP_BT_TX_DMA_SIZE 0x2080 +#define ACP_BT_TX_LINEARPOSITIONCNTR_HIGH 0x2084 +#define ACP_BT_TX_LINEARPOSITIONCNTR_LOW 0x2088 +#define ACP_BT_TX_INTR_WATERMARK_SIZE 0x208C + +#define ACP_I2STDM_IER 0x2400 +#define ACP_I2STDM_IRER 0x2404 +#define ACP_I2STDM_RXFRMT 0x2408 +#define ACP_I2STDM_ITER 0x240C +#define ACP_I2STDM_TXFRMT 0x2410 + +/* Registers from ACP_BT_TDM block */ + +#define ACP_BTTDM_IER 0x2800 +#define ACP_BTTDM_IRER 0x2804 +#define ACP_BTTDM_RXFRMT 0x2808 +#define ACP_BTTDM_ITER 0x280C +#define ACP_BTTDM_TXFRMT 0x2810 + +#endif diff --git a/sound/soc/amd/vangogh/Makefile b/sound/soc/amd/vangogh/Makefile index 3353f93dc610..c9e53e04e247 100644 --- a/sound/soc/amd/vangogh/Makefile +++ b/sound/soc/amd/vangogh/Makefile @@ -3,7 +3,9 @@ snd-pci-acp5x-objs := pci-acp5x.o snd-acp5x-i2s-objs := acp5x-i2s.o snd-acp5x-pcm-dma-objs := acp5x-pcm-dma.o +snd-soc-acp5x-mach-objs := acp5x-mach.o obj-$(CONFIG_SND_SOC_AMD_ACP5x) += snd-pci-acp5x.o obj-$(CONFIG_SND_SOC_AMD_ACP5x) += snd-acp5x-i2s.o obj-$(CONFIG_SND_SOC_AMD_ACP5x) += snd-acp5x-pcm-dma.o +obj-$(CONFIG_SND_SOC_AMD_VANGOGH_MACH) += snd-soc-acp5x-mach.o diff --git a/sound/soc/amd/vangogh/acp5x-i2s.c b/sound/soc/amd/vangogh/acp5x-i2s.c index 2705e57eb713..002db3971ca9 100644 --- a/sound/soc/amd/vangogh/acp5x-i2s.c +++ b/sound/soc/amd/vangogh/acp5x-i2s.c @@ -348,7 +348,7 @@ static int acp5x_i2s_trigger(struct snd_pcm_substream *substream, return ret; } -static struct snd_soc_dai_ops acp5x_i2s_dai_ops = { +static const struct snd_soc_dai_ops acp5x_i2s_dai_ops = { .hw_params = acp5x_i2s_hwparams, .trigger = acp5x_i2s_trigger, .set_fmt = acp5x_i2s_set_fmt, diff --git a/sound/soc/amd/vangogh/acp5x-mach.c b/sound/soc/amd/vangogh/acp5x-mach.c new file mode 100644 index 000000000000..14cf325e4b23 --- /dev/null +++ b/sound/soc/amd/vangogh/acp5x-mach.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Machine driver for AMD Vangogh platform using NAU8821 & CS35L41 + * codecs. + * + * Copyright 2021 Advanced Micro Devices, Inc. + */ + +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/module.h> +#include <linux/io.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include <sound/jack.h> +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/io.h> +#include <linux/acpi.h> +#include <linux/dmi.h> + +#include "../../codecs/nau8821.h" +#include "../../codecs/cs35l41.h" + +#include "acp5x.h" + +#define DRV_NAME "acp5x_mach" +#define DUAL_CHANNEL 2 +#define ACP5X_NUVOTON_CODEC_DAI "nau8821-hifi" +#define VG_JUPITER 1 + +static unsigned long acp5x_machine_id; +static struct snd_soc_jack vg_headset; + +static struct snd_soc_jack_pin acp5x_nau8821_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int acp5x_8821_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_card *card = rtd->card; + struct snd_soc_component *component = + asoc_rtd_to_codec(rtd, 0)->component; + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0, + &vg_headset, acp5x_nau8821_jack_pins, + ARRAY_SIZE(acp5x_nau8821_jack_pins)); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(vg_headset.jack, SND_JACK_BTN_0, KEY_MEDIA); + nau8821_enable_jack_detect(component, &vg_headset); + return ret; +} + +static int acp5x_cs35l41_init(struct snd_soc_pcm_runtime *rtd) +{ + return 0; +} + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const unsigned int channels[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int acp5x_8821_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card); + + machine->play_i2s_instance = I2S_SP_INSTANCE; + machine->cap_i2s_instance = I2S_SP_INSTANCE; + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + return 0; +} + +static int acp5x_nau8821_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = + snd_soc_card_get_codec_dai(card, + ACP5X_NUVOTON_CODEC_DAI); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_FLL_BLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(card->dev, "can't set FS clock %d\n", ret); + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, snd_soc_params_to_bclk(params), + params_rate(params) * 256); + if (ret < 0) + dev_err(card->dev, "can't set FLL: %d\n", ret); + + return ret; +} + +static int acp5x_cs35l41_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card); + + machine->play_i2s_instance = I2S_HS_INSTANCE; + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + return 0; +} + +static int acp5x_cs35l41_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai; + int ret, i; + unsigned int num_codecs = rtd->num_codecs; + unsigned int bclk_val; + + for (i = 0; i < num_codecs; i++) { + codec_dai = asoc_rtd_to_codec(rtd, i); + if ((strcmp(codec_dai->name, "spi-VLV1776:00") == 0) || + (strcmp(codec_dai->name, "spi-VLV1776:01") == 0)) { + switch (params_rate(params)) { + case 48000: + bclk_val = 1536000; + break; + default: + dev_err(card->dev, "Invalid Samplerate:0x%x\n", + params_rate(params)); + return -EINVAL; + } + ret = snd_soc_component_set_sysclk(codec_dai->component, + 0, 0, bclk_val, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "failed to set sysclk for CS35l41 dai\n"); + return ret; + } + } + } + + return ret; +} + +static const struct snd_soc_ops acp5x_8821_ops = { + .startup = acp5x_8821_startup, + .hw_params = acp5x_nau8821_hw_params, +}; + +static const struct snd_soc_ops acp5x_cs35l41_play_ops = { + .startup = acp5x_cs35l41_startup, + .hw_params = acp5x_cs35l41_hw_params, +}; + +static struct snd_soc_codec_conf cs35l41_conf[] = { + { + .dlc = COMP_CODEC_CONF("spi-VLV1776:00"), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF("spi-VLV1776:01"), + .name_prefix = "Right", + }, +}; + +SND_SOC_DAILINK_DEF(acp5x_i2s, + DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.0"))); + +SND_SOC_DAILINK_DEF(acp5x_bt, + DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.1"))); + +SND_SOC_DAILINK_DEF(nau8821, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-NVTN2020:00", + "nau8821-hifi"))); + +SND_SOC_DAILINK_DEF(cs35l41, + DAILINK_COMP_ARRAY(COMP_CODEC("spi-VLV1776:00", "cs35l41-pcm"), + COMP_CODEC("spi-VLV1776:01", "cs35l41-pcm"))); + +SND_SOC_DAILINK_DEF(platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("acp5x_i2s_dma.0"))); + +static struct snd_soc_dai_link acp5x_dai[] = { + { + .name = "acp5x-8825-play", + .stream_name = "Playback/Capture", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &acp5x_8821_ops, + .init = acp5x_8821_init, + SND_SOC_DAILINK_REG(acp5x_i2s, nau8821, platform), + }, + { + .name = "acp5x-CS35L41-Stereo", + .stream_name = "CS35L41 Stereo Playback", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .dpcm_playback = 1, + .playback_only = 1, + .ops = &acp5x_cs35l41_play_ops, + .init = acp5x_cs35l41_init, + SND_SOC_DAILINK_REG(acp5x_bt, cs35l41, platform), + }, +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret = 0; + + codec_dai = snd_soc_card_get_codec_dai(card, ACP5X_NUVOTON_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_INTERNAL, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "set sysclk err = %d\n", ret); + return -EIO; + } + } + return ret; +} + +static const struct snd_kcontrol_new acp5x_8821_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), +}; + +static const struct snd_soc_dapm_widget acp5x_8821_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route acp5x_8821_audio_route[] = { + /* HP jack connectors - unknown if we have jack detection */ + { "Headphone", NULL, "HPOL" }, + { "Headphone", NULL, "HPOR" }, + { "MICL", NULL, "Headset Mic" }, + { "MICR", NULL, "Headset Mic" }, + { "DMIC", NULL, "Int Mic" }, + + { "Headphone", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, + { "Int Mic", NULL, "Platform Clock" }, +}; + +static struct snd_soc_card acp5x_card = { + .name = "acp5x", + .owner = THIS_MODULE, + .dai_link = acp5x_dai, + .num_links = ARRAY_SIZE(acp5x_dai), + .dapm_widgets = acp5x_8821_widgets, + .num_dapm_widgets = ARRAY_SIZE(acp5x_8821_widgets), + .dapm_routes = acp5x_8821_audio_route, + .num_dapm_routes = ARRAY_SIZE(acp5x_8821_audio_route), + .codec_conf = cs35l41_conf, + .num_configs = ARRAY_SIZE(cs35l41_conf), + .controls = acp5x_8821_controls, + .num_controls = ARRAY_SIZE(acp5x_8821_controls), +}; + + +static int acp5x_vg_quirk_cb(const struct dmi_system_id *id) +{ + acp5x_machine_id = VG_JUPITER; + return 1; +} + +static const struct dmi_system_id acp5x_vg_quirk_table[] = { + { + .callback = acp5x_vg_quirk_cb, + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Valve"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jupiter"), + } + }, + {} +}; + +static int acp5x_probe(struct platform_device *pdev) +{ + int ret; + struct acp5x_platform_info *machine; + struct snd_soc_card *card; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct acp5x_platform_info), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + + dmi_check_system(acp5x_vg_quirk_table); + switch(acp5x_machine_id) { + case VG_JUPITER: + card = &acp5x_card; + acp5x_card.dev = &pdev->dev; + break; + default: + return -ENODEV; + } + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + return dev_err_probe(&pdev->dev, ret, + "snd_soc_register_card(%s) failed\n", + acp5x_card.name); + } + return 0; +} + +static struct platform_driver acp5x_mach_driver = { + .driver = { + .name = "acp5x_mach", + .pm = &snd_soc_pm_ops, + }, + .probe = acp5x_probe, +}; + +module_platform_driver(acp5x_mach_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("NAU8821 & CS35L41 audio support"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amd/vangogh/acp5x.h b/sound/soc/amd/vangogh/acp5x.h index a808635f9740..fe5e1fa98974 100644 --- a/sound/soc/amd/vangogh/acp5x.h +++ b/sound/soc/amd/vangogh/acp5x.h @@ -23,7 +23,7 @@ #define ACP_ERR_INTR_MASK 0x20000000 #define ACP_EXT_INTR_STAT_CLEAR_MASK 0xFFFFFFFF -#define ACP5x_DEVS 3 +#define ACP5x_DEVS 4 #define ACP5x_REG_START 0x1240000 #define ACP5x_REG_END 0x1250200 #define ACP5x_I2STDM_REG_START 0x1242400 diff --git a/sound/soc/amd/vangogh/pci-acp5x.c b/sound/soc/amd/vangogh/pci-acp5x.c index a57b762d9f2e..2b6b9edc36e2 100644 --- a/sound/soc/amd/vangogh/pci-acp5x.c +++ b/sound/soc/amd/vangogh/pci-acp5x.c @@ -213,6 +213,9 @@ static int snd_acp5x_probe(struct pci_dev *pci, pdevinfo[2].num_res = 1; pdevinfo[2].res = &adata->res[2]; + pdevinfo[3].name = "acp5x_mach"; + pdevinfo[3].id = 0; + pdevinfo[3].parent = &pci->dev; for (i = 0; i < ACP5x_DEVS; i++) { adata->pdev[i] = platform_device_register_full(&pdevinfo[i]); diff --git a/sound/soc/amd/yc/Makefile b/sound/soc/amd/yc/Makefile new file mode 100644 index 000000000000..dc2974440388 --- /dev/null +++ b/sound/soc/amd/yc/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Yellow Carp platform Support +snd-pci-acp6x-objs := pci-acp6x.o +snd-acp6x-pdm-dma-objs := acp6x-pdm-dma.o +snd-soc-acp6x-mach-objs := acp6x-mach.o + +obj-$(CONFIG_SND_SOC_AMD_ACP6x) += snd-pci-acp6x.o +obj-$(CONFIG_SND_SOC_AMD_ACP6x) += snd-acp6x-pdm-dma.o +obj-$(CONFIG_SND_SOC_AMD_YC_MACH) += snd-soc-acp6x-mach.o diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c new file mode 100644 index 000000000000..9a767f47b89f --- /dev/null +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Machine driver for AMD Yellow Carp platform using DMIC + * + * Copyright 2021 Advanced Micro Devices, Inc. + */ + +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <linux/io.h> +#include <linux/dmi.h> + +#include "acp6x.h" + +#define DRV_NAME "acp_yc_mach" + +SND_SOC_DAILINK_DEF(acp6x_pdm, + DAILINK_COMP_ARRAY(COMP_CPU("acp_yc_pdm_dma.0"))); + +SND_SOC_DAILINK_DEF(dmic_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec.0", + "dmic-hifi"))); + +SND_SOC_DAILINK_DEF(pdm_platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("acp_yc_pdm_dma.0"))); + +static struct snd_soc_dai_link acp6x_dai_pdm[] = { + { + .name = "acp6x-dmic-capture", + .stream_name = "DMIC capture", + .capture_only = 1, + SND_SOC_DAILINK_REG(acp6x_pdm, dmic_codec, pdm_platform), + }, +}; + +static struct snd_soc_card acp6x_card = { + .name = "acp6x", + .owner = THIS_MODULE, + .dai_link = acp6x_dai_pdm, + .num_links = 1, +}; + +static const struct dmi_system_id yc_acp_quirk_table[] = { + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D2"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D3"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D4"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D5"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CF"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CG"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CQ"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CR"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21AW"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21AX"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21BN"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21BQ"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CH"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CJ"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CK"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CL"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D8"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D9"), + } + }, + {} +}; + +static int acp6x_probe(struct platform_device *pdev) +{ + struct acp6x_pdm *machine = NULL; + struct snd_soc_card *card; + int ret; + const struct dmi_system_id *dmi_id; + + dmi_id = dmi_first_match(yc_acp_quirk_table); + if (!dmi_id) + return -ENODEV; + card = &acp6x_card; + acp6x_card.dev = &pdev->dev; + + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + return dev_err_probe(&pdev->dev, ret, + "snd_soc_register_card(%s) failed\n", + card->name); + } + return 0; +} + +static struct platform_driver acp6x_mach_driver = { + .driver = { + .name = "acp_yc_mach", + .pm = &snd_soc_pm_ops, + }, + .probe = acp6x_probe, +}; + +module_platform_driver(acp6x_mach_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amd/yc/acp6x-pdm-dma.c b/sound/soc/amd/yc/acp6x-pdm-dma.c new file mode 100644 index 000000000000..e604f4ea524f --- /dev/null +++ b/sound/soc/amd/yc/acp6x-pdm-dma.c @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AMD ALSA SoC Yellow Carp PDM Driver + * + * Copyright 2021 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/pm_runtime.h> + +#include "acp6x.h" + +#define DRV_NAME "acp_yc_pdm_dma" + +static const struct snd_pcm_hardware acp6x_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 acp6x_init_pdm_ring_buffer(u32 physical_addr, u32 buffer_size, + u32 watermark_size, void __iomem *acp_base) +{ + acp6x_writel(physical_addr, acp_base + ACP_WOV_RX_RINGBUFADDR); + acp6x_writel(buffer_size, acp_base + ACP_WOV_RX_RINGBUFSIZE); + acp6x_writel(watermark_size, acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE); + acp6x_writel(0x01, acp_base + ACPAXI2AXI_ATU_CTRL); +} + +static void acp6x_enable_pdm_clock(void __iomem *acp_base) +{ + u32 pdm_clk_enable, pdm_ctrl; + + pdm_clk_enable = ACP_PDM_CLK_FREQ_MASK; + pdm_ctrl = 0x00; + + acp6x_writel(pdm_clk_enable, acp_base + ACP_WOV_CLK_CTRL); + pdm_ctrl = acp6x_readl(acp_base + ACP_WOV_MISC_CTRL); + pdm_ctrl |= ACP_WOV_MISC_CTRL_MASK; + acp6x_writel(pdm_ctrl, acp_base + ACP_WOV_MISC_CTRL); +} + +static void acp6x_enable_pdm_interrupts(void __iomem *acp_base) +{ + u32 ext_int_ctrl; + + ext_int_ctrl = acp6x_readl(acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_int_ctrl |= PDM_DMA_INTR_MASK; + acp6x_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL); +} + +static void acp6x_disable_pdm_interrupts(void __iomem *acp_base) +{ + u32 ext_int_ctrl; + + ext_int_ctrl = acp6x_readl(acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_int_ctrl &= ~PDM_DMA_INTR_MASK; + acp6x_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL); +} + +static bool acp6x_check_pdm_dma_status(void __iomem *acp_base) +{ + bool pdm_dma_status; + u32 pdm_enable, pdm_dma_enable; + + pdm_dma_status = false; + pdm_enable = acp6x_readl(acp_base + ACP_WOV_PDM_ENABLE); + pdm_dma_enable = acp6x_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 acp6x_start_pdm_dma(void __iomem *acp_base) +{ + u32 pdm_enable; + u32 pdm_dma_enable; + int timeout; + + pdm_enable = 0x01; + pdm_dma_enable = 0x01; + + acp6x_enable_pdm_clock(acp_base); + acp6x_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE); + acp6x_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE); + timeout = 0; + while (++timeout < ACP_COUNTER) { + pdm_dma_enable = acp6x_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 acp6x_stop_pdm_dma(void __iomem *acp_base) +{ + u32 pdm_enable, pdm_dma_enable; + int timeout; + + pdm_enable = 0x00; + pdm_dma_enable = 0x00; + + pdm_enable = acp6x_readl(acp_base + ACP_WOV_PDM_ENABLE); + pdm_dma_enable = acp6x_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); + if (pdm_dma_enable & 0x01) { + pdm_dma_enable = 0x02; + acp6x_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE); + timeout = 0; + while (++timeout < ACP_COUNTER) { + pdm_dma_enable = acp6x_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; + acp6x_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE); + } + acp6x_writel(0x01, acp_base + ACP_WOV_PDM_FIFO_FLUSH); + return 0; +} + +static void acp6x_config_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 */ + acp6x_writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp6x_base + + ACPAXI2AXI_ATU_BASE_ADDR_GRP_1); + acp6x_writel(PAGE_SIZE_4K_ENABLE, rtd->acp6x_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); + + acp6x_writel(low, rtd->acp6x_base + ACP_SCRATCH_REG_0 + val); + high |= BIT(31); + acp6x_writel(high, rtd->acp6x_base + ACP_SCRATCH_REG_0 + val + 4); + val += 8; + addr += PAGE_SIZE; + } +} + +static int acp6x_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 = acp6x_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; + } + + acp6x_enable_pdm_interrupts(adata->acp6x_base); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + adata->capture_stream = substream; + + pdm_data->acp6x_base = adata->acp6x_base; + runtime->private_data = pdm_data; + return ret; +} + +static int acp6x_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->runtime->dma_addr; + rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT); + acp6x_config_dma(rtd, substream->stream); + acp6x_init_pdm_ring_buffer(PDM_MEM_WINDOW_START, size, + period_bytes, rtd->acp6x_base); + return 0; +} + +static u64 acp6x_pdm_get_byte_count(struct pdm_stream_instance *rtd, + int direction) +{ + union acp_pdm_dma_count byte_count; + + byte_count.bcount.high = + acp6x_readl(rtd->acp6x_base + ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH); + byte_count.bcount.low = + acp6x_readl(rtd->acp6x_base + ACP_WOV_RX_LINEARPOSITIONCNTR_LOW); + return byte_count.bytescount; +} + +static snd_pcm_uframes_t acp6x_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 = acp6x_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 acp6x_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 acp6x_pdm_dma_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct pdm_dev_data *adata = dev_get_drvdata(component->dev); + + acp6x_disable_pdm_interrupts(adata->acp6x_base); + adata->capture_stream = NULL; + return 0; +} + +static int acp6x_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: + acp6x_writel(ch_mask, rtd->acp6x_base + ACP_WOV_PDM_NO_OF_CHANNELS); + acp6x_writel(PDM_DECIMATION_FACTOR, rtd->acp6x_base + + ACP_WOV_PDM_DECIMATION_FACTOR); + rtd->bytescount = acp6x_pdm_get_byte_count(rtd, substream->stream); + pdm_status = acp6x_check_pdm_dma_status(rtd->acp6x_base); + if (!pdm_status) + ret = acp6x_start_pdm_dma(rtd->acp6x_base); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pdm_status = acp6x_check_pdm_dma_status(rtd->acp6x_base); + if (pdm_status) + ret = acp6x_stop_pdm_dma(rtd->acp6x_base); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static struct snd_soc_dai_ops acp6x_pdm_dai_ops = { + .trigger = acp6x_pdm_dai_trigger, +}; + +static struct snd_soc_dai_driver acp6x_pdm_dai_driver = { + .capture = { + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 48000, + .rate_max = 48000, + }, + .ops = &acp6x_pdm_dai_ops, +}; + +static const struct snd_soc_component_driver acp6x_pdm_component = { + .name = DRV_NAME, + .open = acp6x_pdm_dma_open, + .close = acp6x_pdm_dma_close, + .hw_params = acp6x_pdm_dma_hw_params, + .pointer = acp6x_pdm_dma_pointer, + .pcm_construct = acp6x_pdm_dma_new, +}; + +static int acp6x_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->acp6x_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!adata->acp6x_base) + return -ENOMEM; + + adata->capture_stream = NULL; + + dev_set_drvdata(&pdev->dev, adata); + status = devm_snd_soc_register_component(&pdev->dev, + &acp6x_pdm_component, + &acp6x_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 acp6x_pdm_audio_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + return 0; +} + +static int __maybe_unused acp6x_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); + acp6x_config_dma(rtd, SNDRV_PCM_STREAM_CAPTURE); + acp6x_init_pdm_ring_buffer(PDM_MEM_WINDOW_START, buffer_len, + period_bytes, adata->acp6x_base); + } + acp6x_enable_pdm_interrupts(adata->acp6x_base); + return 0; +} + +static int __maybe_unused acp6x_pdm_suspend(struct device *dev) +{ + struct pdm_dev_data *adata; + + adata = dev_get_drvdata(dev); + acp6x_disable_pdm_interrupts(adata->acp6x_base); + return 0; +} + +static int __maybe_unused acp6x_pdm_runtime_resume(struct device *dev) +{ + struct pdm_dev_data *adata; + + adata = dev_get_drvdata(dev); + acp6x_enable_pdm_interrupts(adata->acp6x_base); + return 0; +} + +static const struct dev_pm_ops acp6x_pdm_pm_ops = { + SET_RUNTIME_PM_OPS(acp6x_pdm_suspend, acp6x_pdm_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(acp6x_pdm_suspend, acp6x_pdm_resume) +}; + +static struct platform_driver acp6x_pdm_dma_driver = { + .probe = acp6x_pdm_audio_probe, + .remove = acp6x_pdm_audio_remove, + .driver = { + .name = "acp_yc_pdm_dma", + .pm = &acp6x_pdm_pm_ops, + }, +}; + +module_platform_driver(acp6x_pdm_dma_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD ACP6x YC PDM Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amd/yc/acp6x.h b/sound/soc/amd/yc/acp6x.h new file mode 100644 index 000000000000..74b596e6807a --- /dev/null +++ b/sound/soc/amd/yc/acp6x.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * AMD ALSA SoC PDM Driver + * + * Copyright (C) 2021 Advanced Micro Devices, Inc. All rights reserved. + */ + +#include "acp6x_chip_offset_byte.h" + +#define ACP_DEVICE_ID 0x15E2 +#define ACP6x_PHY_BASE_ADDRESS 0x1240000 +#define ACP6x_REG_START 0x1240000 +#define ACP6x_REG_END 0x1250200 +#define ACP6x_DEVS 3 +#define ACP6x_PDM_MODE 1 + +#define ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK 0x00010001 +#define ACP_PGFSM_CNTL_POWER_ON_MASK 1 +#define ACP_PGFSM_CNTL_POWER_OFF_MASK 0 +#define ACP_PGFSM_STATUS_MASK 3 +#define ACP_POWERED_ON 0 +#define ACP_POWER_ON_IN_PROGRESS 1 +#define ACP_POWERED_OFF 2 +#define ACP_POWER_OFF_IN_PROGRESS 3 + +#define ACP_ERROR_MASK 0x20000000 +#define ACP_EXT_INTR_STAT_CLEAR_MASK 0xFFFFFFFF +#define PDM_DMA_STAT 0x10 + +#define PDM_DMA_INTR_MASK 0x10000 +#define ACP_ERROR_STAT 29 +#define PDM_DECIMATION_FACTOR 2 +#define ACP_PDM_CLK_FREQ_MASK 7 +#define ACP_WOV_MISC_CTRL_MASK 0x10 +#define ACP_PDM_ENABLE 1 +#define ACP_PDM_DISABLE 0 +#define ACP_PDM_DMA_EN_STATUS 2 +#define TWO_CH 2 +#define DELAY_US 5 +#define ACP_COUNTER 20000 + +#define ACP_SRAM_PTE_OFFSET 0x03800000 +#define PAGE_SIZE_4K_ENABLE 2 +#define PDM_PTE_OFFSET 0 +#define PDM_MEM_WINDOW_START 0x4000000 + +#define CAPTURE_MIN_NUM_PERIODS 4 +#define CAPTURE_MAX_NUM_PERIODS 4 +#define CAPTURE_MAX_PERIOD_SIZE 8192 +#define CAPTURE_MIN_PERIOD_SIZE 4096 + +#define MAX_BUFFER (CAPTURE_MAX_PERIOD_SIZE * CAPTURE_MAX_NUM_PERIODS) +#define MIN_BUFFER MAX_BUFFER + +/* time in ms for runtime suspend delay */ +#define ACP_SUSPEND_DELAY_MS 2000 + +enum acp_config { + ACP_CONFIG_0 = 0, + ACP_CONFIG_1, + ACP_CONFIG_2, + ACP_CONFIG_3, + ACP_CONFIG_4, + ACP_CONFIG_5, + ACP_CONFIG_6, + ACP_CONFIG_7, + ACP_CONFIG_8, + ACP_CONFIG_9, + ACP_CONFIG_10, + ACP_CONFIG_11, + ACP_CONFIG_12, + ACP_CONFIG_13, + ACP_CONFIG_14, + ACP_CONFIG_15, +}; + +struct pdm_dev_data { + u32 pdm_irq; + void __iomem *acp6x_base; + struct snd_pcm_substream *capture_stream; +}; + +struct pdm_stream_instance { + u16 num_pages; + u16 channels; + dma_addr_t dma_addr; + u64 bytescount; + void __iomem *acp6x_base; +}; + +union acp_pdm_dma_count { + struct { + u32 low; + u32 high; + } bcount; + u64 bytescount; +}; + +static inline u32 acp6x_readl(void __iomem *base_addr) +{ + return readl(base_addr - ACP6x_PHY_BASE_ADDRESS); +} + +static inline void acp6x_writel(u32 val, void __iomem *base_addr) +{ + writel(val, base_addr - ACP6x_PHY_BASE_ADDRESS); +} diff --git a/sound/soc/amd/yc/acp6x_chip_offset_byte.h b/sound/soc/amd/yc/acp6x_chip_offset_byte.h new file mode 100644 index 000000000000..f05fb2dfb5da --- /dev/null +++ b/sound/soc/amd/yc/acp6x_chip_offset_byte.h @@ -0,0 +1,444 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * AMD ACP 6.x Register Documentation + * + * Copyright 2021 Advanced Micro Devices, Inc. + */ + +#ifndef _acp6x_OFFSET_HEADER +#define _acp6x_OFFSET_HEADER + +/* Registers from ACP_DMA block */ +#define ACP_DMA_CNTL_0 0x1240000 +#define ACP_DMA_CNTL_1 0x1240004 +#define ACP_DMA_CNTL_2 0x1240008 +#define ACP_DMA_CNTL_3 0x124000C +#define ACP_DMA_CNTL_4 0x1240010 +#define ACP_DMA_CNTL_5 0x1240014 +#define ACP_DMA_CNTL_6 0x1240018 +#define ACP_DMA_CNTL_7 0x124001C +#define ACP_DMA_DSCR_STRT_IDX_0 0x1240020 +#define ACP_DMA_DSCR_STRT_IDX_1 0x1240024 +#define ACP_DMA_DSCR_STRT_IDX_2 0x1240028 +#define ACP_DMA_DSCR_STRT_IDX_3 0x124002C +#define ACP_DMA_DSCR_STRT_IDX_4 0x1240030 +#define ACP_DMA_DSCR_STRT_IDX_5 0x1240034 +#define ACP_DMA_DSCR_STRT_IDX_6 0x1240038 +#define ACP_DMA_DSCR_STRT_IDX_7 0x124003C +#define ACP_DMA_DSCR_CNT_0 0x1240040 +#define ACP_DMA_DSCR_CNT_1 0x1240044 +#define ACP_DMA_DSCR_CNT_2 0x1240048 +#define ACP_DMA_DSCR_CNT_3 0x124004C +#define ACP_DMA_DSCR_CNT_4 0x1240050 +#define ACP_DMA_DSCR_CNT_5 0x1240054 +#define ACP_DMA_DSCR_CNT_6 0x1240058 +#define ACP_DMA_DSCR_CNT_7 0x124005C +#define ACP_DMA_PRIO_0 0x1240060 +#define ACP_DMA_PRIO_1 0x1240064 +#define ACP_DMA_PRIO_2 0x1240068 +#define ACP_DMA_PRIO_3 0x124006C +#define ACP_DMA_PRIO_4 0x1240070 +#define ACP_DMA_PRIO_5 0x1240074 +#define ACP_DMA_PRIO_6 0x1240078 +#define ACP_DMA_PRIO_7 0x124007C +#define ACP_DMA_CUR_DSCR_0 0x1240080 +#define ACP_DMA_CUR_DSCR_1 0x1240084 +#define ACP_DMA_CUR_DSCR_2 0x1240088 +#define ACP_DMA_CUR_DSCR_3 0x124008C +#define ACP_DMA_CUR_DSCR_4 0x1240090 +#define ACP_DMA_CUR_DSCR_5 0x1240094 +#define ACP_DMA_CUR_DSCR_6 0x1240098 +#define ACP_DMA_CUR_DSCR_7 0x124009C +#define ACP_DMA_CUR_TRANS_CNT_0 0x12400A0 +#define ACP_DMA_CUR_TRANS_CNT_1 0x12400A4 +#define ACP_DMA_CUR_TRANS_CNT_2 0x12400A8 +#define ACP_DMA_CUR_TRANS_CNT_3 0x12400AC +#define ACP_DMA_CUR_TRANS_CNT_4 0x12400B0 +#define ACP_DMA_CUR_TRANS_CNT_5 0x12400B4 +#define ACP_DMA_CUR_TRANS_CNT_6 0x12400B8 +#define ACP_DMA_CUR_TRANS_CNT_7 0x12400BC +#define ACP_DMA_ERR_STS_0 0x12400C0 +#define ACP_DMA_ERR_STS_1 0x12400C4 +#define ACP_DMA_ERR_STS_2 0x12400C8 +#define ACP_DMA_ERR_STS_3 0x12400CC +#define ACP_DMA_ERR_STS_4 0x12400D0 +#define ACP_DMA_ERR_STS_5 0x12400D4 +#define ACP_DMA_ERR_STS_6 0x12400D8 +#define ACP_DMA_ERR_STS_7 0x12400DC +#define ACP_DMA_DESC_BASE_ADDR 0x12400E0 +#define ACP_DMA_DESC_MAX_NUM_DSCR 0x12400E4 +#define ACP_DMA_CH_STS 0x12400E8 +#define ACP_DMA_CH_GROUP 0x12400EC +#define ACP_DMA_CH_RST_STS 0x12400F0 + +/* Registers from ACP_AXI2AXIATU block */ +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 0x1240C00 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_1 0x1240C04 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_2 0x1240C08 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_2 0x1240C0C +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_3 0x1240C10 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_3 0x1240C14 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_4 0x1240C18 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_4 0x1240C1C +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5 0x1240C20 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_5 0x1240C24 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_6 0x1240C28 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_6 0x1240C2C +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_7 0x1240C30 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_7 0x1240C34 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_8 0x1240C38 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_8 0x1240C3C +#define ACPAXI2AXI_ATU_CTRL 0x1240C40 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_9 0x1240C44 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_9 0x1240C48 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_10 0x1240C4C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_10 0x1240C50 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_11 0x1240C54 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_11 0x1240C58 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_12 0x1240C5C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_12 0x1240C60 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_13 0x1240C64 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_13 0x1240C68 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_14 0x1240C6C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_14 0x1240C70 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_15 0x1240C74 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_15 0x1240C78 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_16 0x1240C7C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_16 0x1240C80 + +/* Registers from ACP_CLKRST block */ +#define ACP_SOFT_RESET 0x1241000 +#define ACP_CONTROL 0x1241004 +#define ACP_STATUS 0x1241008 +#define ACP_DYNAMIC_CG_MASTER_CONTROL 0x1241010 +#define ACP_ZSC_DSP_CTRL 0x1241014 +#define ACP_ZSC_STS 0x1241018 +#define ACP_PGFSM_CONTROL 0x1241024 +#define ACP_PGFSM_STATUS 0x1241028 +#define ACP_CLKMUX_SEL 0x124102C + +/* Registers from ACP_AON block */ +#define ACP_PME_EN 0x1241400 +#define ACP_DEVICE_STATE 0x1241404 +#define AZ_DEVICE_STATE 0x1241408 +#define ACP_PIN_CONFIG 0x1241440 +#define ACP_PAD_PULLUP_CTRL 0x1241444 +#define ACP_PAD_PULLDOWN_CTRL 0x1241448 +#define ACP_PAD_DRIVE_STRENGTH_CTRL 0x124144C +#define ACP_PAD_SCHMEN_CTRL 0x1241450 +#define ACP_SW_PAD_KEEPER_EN 0x1241454 +#define ACP_SW_WAKE_EN 0x1241458 +#define ACP_I2S_WAKE_EN 0x124145C +#define ACP_SW1_WAKE_EN 0x1241460 + +/* Registers from ACP_P1_MISC block */ +#define ACP_EXTERNAL_INTR_ENB 0x1241A00 +#define ACP_EXTERNAL_INTR_CNTL 0x1241A04 +#define ACP_EXTERNAL_INTR_CNTL1 0x1241A08 +#define ACP_EXTERNAL_INTR_STAT 0x1241A0C +#define ACP_EXTERNAL_INTR_STAT1 0x1241A10 +#define ACP_ERROR_STATUS 0x1241A4C +#define ACP_P1_SW_I2S_ERROR_REASON 0x1241A50 +#define ACP_P1_SW_POS_TRACK_I2S_TX_CTRL 0x1241A6C +#define ACP_P1_SW_I2S_TX_DMA_POS 0x1241A70 +#define ACP_P1_SW_POS_TRACK_I2S_RX_CTRL 0x1241A74 +#define ACP_P1_SW_I2S_RX_DMA_POS 0x1241A78 +#define ACP_P1_DMIC_I2S_GPIO_INTR_CTRL 0x1241A7C +#define ACP_P1_DMIC_I2S_GPIO_INTR_STATUS 0x1241A80 +#define ACP_SCRATCH_REG_BASE_ADDR 0x1241A84 +#define ACP_P1_SW_POS_TRACK_BT_TX_CTRL 0x1241A88 +#define ACP_P1_SW_BT_TX_DMA_POS 0x1241A8C +#define ACP_P1_SW_POS_TRACK_HS_TX_CTRL 0x1241A90 +#define ACP_P1_SW_HS_TX_DMA_POS 0x1241A94 +#define ACP_P1_SW_POS_TRACK_BT_RX_CTRL 0x1241A98 +#define ACP_P1_SW_BT_RX_DMA_POS 0x1241A9C +#define ACP_P1_SW_POS_TRACK_HS_RX_CTRL 0x1241AA0 +#define ACP_P1_SW_HS_RX_DMA_POS 0x1241AA4 + +/* Registers from ACP_AUDIO_BUFFERS block */ +#define ACP_I2S_RX_RINGBUFADDR 0x1242000 +#define ACP_I2S_RX_RINGBUFSIZE 0x1242004 +#define ACP_I2S_RX_LINKPOSITIONCNTR 0x1242008 +#define ACP_I2S_RX_FIFOADDR 0x124200C +#define ACP_I2S_RX_FIFOSIZE 0x1242010 +#define ACP_I2S_RX_DMA_SIZE 0x1242014 +#define ACP_I2S_RX_LINEARPOSITIONCNTR_HIGH 0x1242018 +#define ACP_I2S_RX_LINEARPOSITIONCNTR_LOW 0x124201C +#define ACP_I2S_RX_INTR_WATERMARK_SIZE 0x1242020 +#define ACP_I2S_TX_RINGBUFADDR 0x1242024 +#define ACP_I2S_TX_RINGBUFSIZE 0x1242028 +#define ACP_I2S_TX_LINKPOSITIONCNTR 0x124202C +#define ACP_I2S_TX_FIFOADDR 0x1242030 +#define ACP_I2S_TX_FIFOSIZE 0x1242034 +#define ACP_I2S_TX_DMA_SIZE 0x1242038 +#define ACP_I2S_TX_LINEARPOSITIONCNTR_HIGH 0x124203C +#define ACP_I2S_TX_LINEARPOSITIONCNTR_LOW 0x1242040 +#define ACP_I2S_TX_INTR_WATERMARK_SIZE 0x1242044 +#define ACP_BT_RX_RINGBUFADDR 0x1242048 +#define ACP_BT_RX_RINGBUFSIZE 0x124204C +#define ACP_BT_RX_LINKPOSITIONCNTR 0x1242050 +#define ACP_BT_RX_FIFOADDR 0x1242054 +#define ACP_BT_RX_FIFOSIZE 0x1242058 +#define ACP_BT_RX_DMA_SIZE 0x124205C +#define ACP_BT_RX_LINEARPOSITIONCNTR_HIGH 0x1242060 +#define ACP_BT_RX_LINEARPOSITIONCNTR_LOW 0x1242064 +#define ACP_BT_RX_INTR_WATERMARK_SIZE 0x1242068 +#define ACP_BT_TX_RINGBUFADDR 0x124206C +#define ACP_BT_TX_RINGBUFSIZE 0x1242070 +#define ACP_BT_TX_LINKPOSITIONCNTR 0x1242074 +#define ACP_BT_TX_FIFOADDR 0x1242078 +#define ACP_BT_TX_FIFOSIZE 0x124207C +#define ACP_BT_TX_DMA_SIZE 0x1242080 +#define ACP_BT_TX_LINEARPOSITIONCNTR_HIGH 0x1242084 +#define ACP_BT_TX_LINEARPOSITIONCNTR_LOW 0x1242088 +#define ACP_BT_TX_INTR_WATERMARK_SIZE 0x124208C +#define ACP_HS_RX_RINGBUFADDR 0x1242090 +#define ACP_HS_RX_RINGBUFSIZE 0x1242094 +#define ACP_HS_RX_LINKPOSITIONCNTR 0x1242098 +#define ACP_HS_RX_FIFOADDR 0x124209C +#define ACP_HS_RX_FIFOSIZE 0x12420A0 +#define ACP_HS_RX_DMA_SIZE 0x12420A4 +#define ACP_HS_RX_LINEARPOSITIONCNTR_HIGH 0x12420A8 +#define ACP_HS_RX_LINEARPOSITIONCNTR_LOW 0x12420AC +#define ACP_HS_RX_INTR_WATERMARK_SIZE 0x12420B0 +#define ACP_HS_TX_RINGBUFADDR 0x12420B4 +#define ACP_HS_TX_RINGBUFSIZE 0x12420B8 +#define ACP_HS_TX_LINKPOSITIONCNTR 0x12420BC +#define ACP_HS_TX_FIFOADDR 0x12420C0 +#define ACP_HS_TX_FIFOSIZE 0x12420C4 +#define ACP_HS_TX_DMA_SIZE 0x12420C8 +#define ACP_HS_TX_LINEARPOSITIONCNTR_HIGH 0x12420CC +#define ACP_HS_TX_LINEARPOSITIONCNTR_LOW 0x12420D0 +#define ACP_HS_TX_INTR_WATERMARK_SIZE 0x12420D4 + +/* Registers from ACP_I2S_TDM block */ +#define ACP_I2STDM_IER 0x1242400 +#define ACP_I2STDM_IRER 0x1242404 +#define ACP_I2STDM_RXFRMT 0x1242408 +#define ACP_I2STDM_ITER 0x124240C +#define ACP_I2STDM_TXFRMT 0x1242410 +#define ACP_I2STDM0_MSTRCLKGEN 0x1242414 +#define ACP_I2STDM1_MSTRCLKGEN 0x1242418 +#define ACP_I2STDM2_MSTRCLKGEN 0x124241C +#define ACP_I2STDM_REFCLKGEN 0x1242420 + +/* Registers from ACP_BT_TDM block */ +#define ACP_BTTDM_IER 0x1242800 +#define ACP_BTTDM_IRER 0x1242804 +#define ACP_BTTDM_RXFRMT 0x1242808 +#define ACP_BTTDM_ITER 0x124280C +#define ACP_BTTDM_TXFRMT 0x1242810 +#define ACP_HSTDM_IER 0x1242814 +#define ACP_HSTDM_IRER 0x1242818 +#define ACP_HSTDM_RXFRMT 0x124281C +#define ACP_HSTDM_ITER 0x1242820 +#define ACP_HSTDM_TXFRMT 0x1242824 + +/* Registers from ACP_WOV block */ +#define ACP_WOV_PDM_ENABLE 0x1242C04 +#define ACP_WOV_PDM_DMA_ENABLE 0x1242C08 +#define ACP_WOV_RX_RINGBUFADDR 0x1242C0C +#define ACP_WOV_RX_RINGBUFSIZE 0x1242C10 +#define ACP_WOV_RX_LINKPOSITIONCNTR 0x1242C14 +#define ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH 0x1242C18 +#define ACP_WOV_RX_LINEARPOSITIONCNTR_LOW 0x1242C1C +#define ACP_WOV_RX_INTR_WATERMARK_SIZE 0x1242C20 +#define ACP_WOV_PDM_FIFO_FLUSH 0x1242C24 +#define ACP_WOV_PDM_NO_OF_CHANNELS 0x1242C28 +#define ACP_WOV_PDM_DECIMATION_FACTOR 0x1242C2C +#define ACP_WOV_PDM_VAD_CTRL 0x1242C30 +#define ACP_WOV_WAKE 0x1242C54 +#define ACP_WOV_BUFFER_STATUS 0x1242C58 +#define ACP_WOV_MISC_CTRL 0x1242C5C +#define ACP_WOV_CLK_CTRL 0x1242C60 +#define ACP_PDM_VAD_DYNAMIC_CLK_GATING_EN 0x1242C64 +#define ACP_WOV_ERROR_STATUS_REGISTER 0x1242C68 +#define ACP_PDM_CLKDIV 0x1242C6C + +/* Registers from ACP_P1_AUDIO_BUFFERS block */ +#define ACP_P1_I2S_RX_RINGBUFADDR 0x1243A00 +#define ACP_P1_I2S_RX_RINGBUFSIZE 0x1243A04 +#define ACP_P1_I2S_RX_LINKPOSITIONCNTR 0x1243A08 +#define ACP_P1_I2S_RX_FIFOADDR 0x1243A0C +#define ACP_P1_I2S_RX_FIFOSIZE 0x1243A10 +#define ACP_P1_I2S_RX_DMA_SIZE 0x1243A14 +#define ACP_P1_I2S_RX_LINEARPOSITIONCNTR_HIGH 0x1243A18 +#define ACP_P1_I2S_RX_LINEARPOSITIONCNTR_LOW 0x1243A1C +#define ACP_P1_I2S_RX_INTR_WATERMARK_SIZE 0x1243A20 +#define ACP_P1_I2S_TX_RINGBUFADDR 0x1243A24 +#define ACP_P1_I2S_TX_RINGBUFSIZE 0x1243A28 +#define ACP_P1_I2S_TX_LINKPOSITIONCNTR 0x1243A2C +#define ACP_P1_I2S_TX_FIFOADDR 0x1243A30 +#define ACP_P1_I2S_TX_FIFOSIZE 0x1243A34 +#define ACP_P1_I2S_TX_DMA_SIZE 0x1243A38 +#define ACP_P1_I2S_TX_LINEARPOSITIONCNTR_HIGH 0x1243A3C +#define ACP_P1_I2S_TX_LINEARPOSITIONCNTR_LOW 0x1243A40 +#define ACP_P1_I2S_TX_INTR_WATERMARK_SIZE 0x1243A44 +#define ACP_P1_BT_RX_RINGBUFADDR 0x1243A48 +#define ACP_P1_BT_RX_RINGBUFSIZE 0x1243A4C +#define ACP_P1_BT_RX_LINKPOSITIONCNTR 0x1243A50 +#define ACP_P1_BT_RX_FIFOADDR 0x1243A54 +#define ACP_P1_BT_RX_FIFOSIZE 0x1243A58 +#define ACP_P1_BT_RX_DMA_SIZE 0x1243A5C +#define ACP_P1_BT_RX_LINEARPOSITIONCNTR_HIGH 0x1243A60 +#define ACP_P1_BT_RX_LINEARPOSITIONCNTR_LOW 0x1243A64 +#define ACP_P1_BT_RX_INTR_WATERMARK_SIZE 0x1243A68 +#define ACP_P1_BT_TX_RINGBUFADDR 0x1243A6C +#define ACP_P1_BT_TX_RINGBUFSIZE 0x1243A70 +#define ACP_P1_BT_TX_LINKPOSITIONCNTR 0x1243A74 +#define ACP_P1_BT_TX_FIFOADDR 0x1243A78 +#define ACP_P1_BT_TX_FIFOSIZE 0x1243A7C +#define ACP_P1_BT_TX_DMA_SIZE 0x1243A80 +#define ACP_P1_BT_TX_LINEARPOSITIONCNTR_HIGH 0x1243A84 +#define ACP_P1_BT_TX_LINEARPOSITIONCNTR_LOW 0x1243A88 +#define ACP_P1_BT_TX_INTR_WATERMARK_SIZE 0x1243A8C +#define ACP_P1_HS_RX_RINGBUFADDR 0x1243A90 +#define ACP_P1_HS_RX_RINGBUFSIZE 0x1243A94 +#define ACP_P1_HS_RX_LINKPOSITIONCNTR 0x1243A98 +#define ACP_P1_HS_RX_FIFOADDR 0x1243A9C +#define ACP_P1_HS_RX_FIFOSIZE 0x1243AA0 +#define ACP_P1_HS_RX_DMA_SIZE 0x1243AA4 +#define ACP_P1_HS_RX_LINEARPOSITIONCNTR_HIGH 0x1243AA8 +#define ACP_P1_HS_RX_LINEARPOSITIONCNTR_LOW 0x1243AAC +#define ACP_P1_HS_RX_INTR_WATERMARK_SIZE 0x1243AB0 +#define ACP_P1_HS_TX_RINGBUFADDR 0x1243AB4 +#define ACP_P1_HS_TX_RINGBUFSIZE 0x1243AB8 +#define ACP_P1_HS_TX_LINKPOSITIONCNTR 0x1243ABC +#define ACP_P1_HS_TX_FIFOADDR 0x1243AC0 +#define ACP_P1_HS_TX_FIFOSIZE 0x1243AC4 +#define ACP_P1_HS_TX_DMA_SIZE 0x1243AC8 +#define ACP_P1_HS_TX_LINEARPOSITIONCNTR_HIGH 0x1243ACC +#define ACP_P1_HS_TX_LINEARPOSITIONCNTR_LOW 0x1243AD0 +#define ACP_P1_HS_TX_INTR_WATERMARK_SIZE 0x1243AD4 + +/* Registers from ACP_SCRATCH block */ +#define ACP_SCRATCH_REG_0 0x1250000 +#define ACP_SCRATCH_REG_1 0x1250004 +#define ACP_SCRATCH_REG_2 0x1250008 +#define ACP_SCRATCH_REG_3 0x125000C +#define ACP_SCRATCH_REG_4 0x1250010 +#define ACP_SCRATCH_REG_5 0x1250014 +#define ACP_SCRATCH_REG_6 0x1250018 +#define ACP_SCRATCH_REG_7 0x125001C +#define ACP_SCRATCH_REG_8 0x1250020 +#define ACP_SCRATCH_REG_9 0x1250024 +#define ACP_SCRATCH_REG_10 0x1250028 +#define ACP_SCRATCH_REG_11 0x125002C +#define ACP_SCRATCH_REG_12 0x1250030 +#define ACP_SCRATCH_REG_13 0x1250034 +#define ACP_SCRATCH_REG_14 0x1250038 +#define ACP_SCRATCH_REG_15 0x125003C +#define ACP_SCRATCH_REG_16 0x1250040 +#define ACP_SCRATCH_REG_17 0x1250044 +#define ACP_SCRATCH_REG_18 0x1250048 +#define ACP_SCRATCH_REG_19 0x125004C +#define ACP_SCRATCH_REG_20 0x1250050 +#define ACP_SCRATCH_REG_21 0x1250054 +#define ACP_SCRATCH_REG_22 0x1250058 +#define ACP_SCRATCH_REG_23 0x125005C +#define ACP_SCRATCH_REG_24 0x1250060 +#define ACP_SCRATCH_REG_25 0x1250064 +#define ACP_SCRATCH_REG_26 0x1250068 +#define ACP_SCRATCH_REG_27 0x125006C +#define ACP_SCRATCH_REG_28 0x1250070 +#define ACP_SCRATCH_REG_29 0x1250074 +#define ACP_SCRATCH_REG_30 0x1250078 +#define ACP_SCRATCH_REG_31 0x125007C +#define ACP_SCRATCH_REG_32 0x1250080 +#define ACP_SCRATCH_REG_33 0x1250084 +#define ACP_SCRATCH_REG_34 0x1250088 +#define ACP_SCRATCH_REG_35 0x125008C +#define ACP_SCRATCH_REG_36 0x1250090 +#define ACP_SCRATCH_REG_37 0x1250094 +#define ACP_SCRATCH_REG_38 0x1250098 +#define ACP_SCRATCH_REG_39 0x125009C +#define ACP_SCRATCH_REG_40 0x12500A0 +#define ACP_SCRATCH_REG_41 0x12500A4 +#define ACP_SCRATCH_REG_42 0x12500A8 +#define ACP_SCRATCH_REG_43 0x12500AC +#define ACP_SCRATCH_REG_44 0x12500B0 +#define ACP_SCRATCH_REG_45 0x12500B4 +#define ACP_SCRATCH_REG_46 0x12500B8 +#define ACP_SCRATCH_REG_47 0x12500BC +#define ACP_SCRATCH_REG_48 0x12500C0 +#define ACP_SCRATCH_REG_49 0x12500C4 +#define ACP_SCRATCH_REG_50 0x12500C8 +#define ACP_SCRATCH_REG_51 0x12500CC +#define ACP_SCRATCH_REG_52 0x12500D0 +#define ACP_SCRATCH_REG_53 0x12500D4 +#define ACP_SCRATCH_REG_54 0x12500D8 +#define ACP_SCRATCH_REG_55 0x12500DC +#define ACP_SCRATCH_REG_56 0x12500E0 +#define ACP_SCRATCH_REG_57 0x12500E4 +#define ACP_SCRATCH_REG_58 0x12500E8 +#define ACP_SCRATCH_REG_59 0x12500EC +#define ACP_SCRATCH_REG_60 0x12500F0 +#define ACP_SCRATCH_REG_61 0x12500F4 +#define ACP_SCRATCH_REG_62 0x12500F8 +#define ACP_SCRATCH_REG_63 0x12500FC +#define ACP_SCRATCH_REG_64 0x1250100 +#define ACP_SCRATCH_REG_65 0x1250104 +#define ACP_SCRATCH_REG_66 0x1250108 +#define ACP_SCRATCH_REG_67 0x125010C +#define ACP_SCRATCH_REG_68 0x1250110 +#define ACP_SCRATCH_REG_69 0x1250114 +#define ACP_SCRATCH_REG_70 0x1250118 +#define ACP_SCRATCH_REG_71 0x125011C +#define ACP_SCRATCH_REG_72 0x1250120 +#define ACP_SCRATCH_REG_73 0x1250124 +#define ACP_SCRATCH_REG_74 0x1250128 +#define ACP_SCRATCH_REG_75 0x125012C +#define ACP_SCRATCH_REG_76 0x1250130 +#define ACP_SCRATCH_REG_77 0x1250134 +#define ACP_SCRATCH_REG_78 0x1250138 +#define ACP_SCRATCH_REG_79 0x125013C +#define ACP_SCRATCH_REG_80 0x1250140 +#define ACP_SCRATCH_REG_81 0x1250144 +#define ACP_SCRATCH_REG_82 0x1250148 +#define ACP_SCRATCH_REG_83 0x125014C +#define ACP_SCRATCH_REG_84 0x1250150 +#define ACP_SCRATCH_REG_85 0x1250154 +#define ACP_SCRATCH_REG_86 0x1250158 +#define ACP_SCRATCH_REG_87 0x125015C +#define ACP_SCRATCH_REG_88 0x1250160 +#define ACP_SCRATCH_REG_89 0x1250164 +#define ACP_SCRATCH_REG_90 0x1250168 +#define ACP_SCRATCH_REG_91 0x125016C +#define ACP_SCRATCH_REG_92 0x1250170 +#define ACP_SCRATCH_REG_93 0x1250174 +#define ACP_SCRATCH_REG_94 0x1250178 +#define ACP_SCRATCH_REG_95 0x125017C +#define ACP_SCRATCH_REG_96 0x1250180 +#define ACP_SCRATCH_REG_97 0x1250184 +#define ACP_SCRATCH_REG_98 0x1250188 +#define ACP_SCRATCH_REG_99 0x125018C +#define ACP_SCRATCH_REG_100 0x1250190 +#define ACP_SCRATCH_REG_101 0x1250194 +#define ACP_SCRATCH_REG_102 0x1250198 +#define ACP_SCRATCH_REG_103 0x125019C +#define ACP_SCRATCH_REG_104 0x12501A0 +#define ACP_SCRATCH_REG_105 0x12501A4 +#define ACP_SCRATCH_REG_106 0x12501A8 +#define ACP_SCRATCH_REG_107 0x12501AC +#define ACP_SCRATCH_REG_108 0x12501B0 +#define ACP_SCRATCH_REG_109 0x12501B4 +#define ACP_SCRATCH_REG_110 0x12501B8 +#define ACP_SCRATCH_REG_111 0x12501BC +#define ACP_SCRATCH_REG_112 0x12501C0 +#define ACP_SCRATCH_REG_113 0x12501C4 +#define ACP_SCRATCH_REG_114 0x12501C8 +#define ACP_SCRATCH_REG_115 0x12501CC +#define ACP_SCRATCH_REG_116 0x12501D0 +#define ACP_SCRATCH_REG_117 0x12501D4 +#define ACP_SCRATCH_REG_118 0x12501D8 +#define ACP_SCRATCH_REG_119 0x12501DC +#define ACP_SCRATCH_REG_120 0x12501E0 +#define ACP_SCRATCH_REG_121 0x12501E4 +#define ACP_SCRATCH_REG_122 0x12501E8 +#define ACP_SCRATCH_REG_123 0x12501EC +#define ACP_SCRATCH_REG_124 0x12501F0 +#define ACP_SCRATCH_REG_125 0x12501F4 +#define ACP_SCRATCH_REG_126 0x12501F8 +#define ACP_SCRATCH_REG_127 0x12501FC +#define ACP_SCRATCH_REG_128 0x1250200 +#endif diff --git a/sound/soc/amd/yc/pci-acp6x.c b/sound/soc/amd/yc/pci-acp6x.c new file mode 100644 index 000000000000..957eeb6fb8e3 --- /dev/null +++ b/sound/soc/amd/yc/pci-acp6x.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AMD Yellow Carp ACP PCI Driver + * + * Copyright 2021 Advanced Micro Devices, Inc. + */ + +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <sound/pcm_params.h> +#include <linux/pm_runtime.h> + +#include "acp6x.h" + +struct acp6x_dev_data { + void __iomem *acp6x_base; + struct resource *res; + bool acp6x_audio_mode; + struct platform_device *pdev[ACP6x_DEVS]; +}; + +static int acp6x_power_on(void __iomem *acp_base) +{ + u32 val; + int timeout; + + val = acp6x_readl(acp_base + ACP_PGFSM_STATUS); + + if (!val) + return val; + + if ((val & ACP_PGFSM_STATUS_MASK) != ACP_POWER_ON_IN_PROGRESS) + acp6x_writel(ACP_PGFSM_CNTL_POWER_ON_MASK, acp_base + ACP_PGFSM_CONTROL); + timeout = 0; + while (++timeout < 500) { + val = acp6x_readl(acp_base + ACP_PGFSM_STATUS); + if (!val) + return 0; + udelay(1); + } + return -ETIMEDOUT; +} + +static int acp6x_reset(void __iomem *acp_base) +{ + u32 val; + int timeout; + + acp6x_writel(1, acp_base + ACP_SOFT_RESET); + timeout = 0; + while (++timeout < 500) { + val = acp6x_readl(acp_base + ACP_SOFT_RESET); + if (val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK) + break; + cpu_relax(); + } + acp6x_writel(0, acp_base + ACP_SOFT_RESET); + timeout = 0; + while (++timeout < 500) { + val = acp6x_readl(acp_base + ACP_SOFT_RESET); + if (!val) + return 0; + cpu_relax(); + } + return -ETIMEDOUT; +} + +static void acp6x_enable_interrupts(void __iomem *acp_base) +{ + acp6x_writel(0x01, acp_base + ACP_EXTERNAL_INTR_ENB); +} + +static void acp6x_disable_interrupts(void __iomem *acp_base) +{ + acp6x_writel(ACP_EXT_INTR_STAT_CLEAR_MASK, acp_base + + ACP_EXTERNAL_INTR_STAT); + acp6x_writel(0x00, acp_base + ACP_EXTERNAL_INTR_CNTL); + acp6x_writel(0x00, acp_base + ACP_EXTERNAL_INTR_ENB); +} + +static int acp6x_init(void __iomem *acp_base) +{ + int ret; + + /* power on */ + ret = acp6x_power_on(acp_base); + if (ret) { + pr_err("ACP power on failed\n"); + return ret; + } + acp6x_writel(0x01, acp_base + ACP_CONTROL); + /* Reset */ + ret = acp6x_reset(acp_base); + if (ret) { + pr_err("ACP reset failed\n"); + return ret; + } + acp6x_writel(0x03, acp_base + ACP_CLKMUX_SEL); + acp6x_enable_interrupts(acp_base); + return 0; +} + +static int acp6x_deinit(void __iomem *acp_base) +{ + int ret; + + acp6x_disable_interrupts(acp_base); + /* Reset */ + ret = acp6x_reset(acp_base); + if (ret) { + pr_err("ACP reset failed\n"); + return ret; + } + acp6x_writel(0x00, acp_base + ACP_CLKMUX_SEL); + acp6x_writel(0x00, acp_base + ACP_CONTROL); + return 0; +} + +static irqreturn_t acp6x_irq_handler(int irq, void *dev_id) +{ + struct acp6x_dev_data *adata; + struct pdm_dev_data *yc_pdm_data; + u32 val; + + adata = dev_id; + if (!adata) + return IRQ_NONE; + + val = acp6x_readl(adata->acp6x_base + ACP_EXTERNAL_INTR_STAT); + if (val & BIT(PDM_DMA_STAT)) { + yc_pdm_data = dev_get_drvdata(&adata->pdev[0]->dev); + acp6x_writel(BIT(PDM_DMA_STAT), adata->acp6x_base + ACP_EXTERNAL_INTR_STAT); + if (yc_pdm_data->capture_stream) + snd_pcm_period_elapsed(yc_pdm_data->capture_stream); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static int snd_acp6x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct acp6x_dev_data *adata; + struct platform_device_info pdevinfo[ACP6x_DEVS]; + int ret, index; + int val = 0x00; + u32 addr; + unsigned int irqflags; + + irqflags = IRQF_SHARED; + /* Yellow Carp device check */ + if (pci->revision != 0x60) + return -ENODEV; + + if (pci_enable_device(pci)) { + dev_err(&pci->dev, "pci_enable_device failed\n"); + return -ENODEV; + } + + ret = pci_request_regions(pci, "AMD ACP3x audio"); + if (ret < 0) { + dev_err(&pci->dev, "pci_request_regions failed\n"); + goto disable_pci; + } + + adata = devm_kzalloc(&pci->dev, sizeof(struct acp6x_dev_data), + GFP_KERNEL); + if (!adata) { + ret = -ENOMEM; + goto release_regions; + } + + addr = pci_resource_start(pci, 0); + adata->acp6x_base = devm_ioremap(&pci->dev, addr, + pci_resource_len(pci, 0)); + if (!adata->acp6x_base) { + ret = -ENOMEM; + goto release_regions; + } + pci_set_master(pci); + pci_set_drvdata(pci, adata); + ret = acp6x_init(adata->acp6x_base); + if (ret) + goto release_regions; + val = acp6x_readl(adata->acp6x_base + ACP_PIN_CONFIG); + switch (val) { + case ACP_CONFIG_0: + case ACP_CONFIG_1: + case ACP_CONFIG_2: + case ACP_CONFIG_3: + case ACP_CONFIG_9: + case ACP_CONFIG_15: + dev_info(&pci->dev, "Audio Mode %d\n", val); + break; + default: + adata->res = devm_kzalloc(&pci->dev, + sizeof(struct resource), + GFP_KERNEL); + if (!adata->res) { + ret = -ENOMEM; + goto de_init; + } + + adata->res->name = "acp_iomem"; + adata->res->flags = IORESOURCE_MEM; + adata->res->start = addr; + adata->res->end = addr + (ACP6x_REG_END - ACP6x_REG_START); + + adata->acp6x_audio_mode = ACP6x_PDM_MODE; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo[0].name = "acp_yc_pdm_dma"; + pdevinfo[0].id = 0; + pdevinfo[0].parent = &pci->dev; + pdevinfo[0].num_res = 1; + pdevinfo[0].res = adata->res; + + pdevinfo[1].name = "dmic-codec"; + pdevinfo[1].id = 0; + pdevinfo[1].parent = &pci->dev; + + pdevinfo[2].name = "acp_yc_mach"; + pdevinfo[2].id = 0; + pdevinfo[2].parent = &pci->dev; + + for (index = 0; index < ACP6x_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; + } + } + break; + } + ret = devm_request_irq(&pci->dev, pci->irq, acp6x_irq_handler, + irqflags, "ACP_PCI_IRQ", adata); + if (ret) { + dev_err(&pci->dev, "ACP PCI IRQ request failed\n"); + goto unregister_devs; + } + pm_runtime_set_autosuspend_delay(&pci->dev, ACP_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&pci->dev); + pm_runtime_put_noidle(&pci->dev); + pm_runtime_allow(&pci->dev); + + return 0; +unregister_devs: + for (--index; index >= 0; index--) + platform_device_unregister(adata->pdev[index]); +de_init: + if (acp6x_deinit(adata->acp6x_base)) + dev_err(&pci->dev, "ACP de-init failed\n"); +release_regions: + pci_release_regions(pci); +disable_pci: + pci_disable_device(pci); + + return ret; +} + +static int __maybe_unused snd_acp6x_suspend(struct device *dev) +{ + struct acp6x_dev_data *adata; + int ret; + + adata = dev_get_drvdata(dev); + ret = acp6x_deinit(adata->acp6x_base); + if (ret) + dev_err(dev, "ACP de-init failed\n"); + return ret; +} + +static int __maybe_unused snd_acp6x_resume(struct device *dev) +{ + struct acp6x_dev_data *adata; + int ret; + + adata = dev_get_drvdata(dev); + ret = acp6x_init(adata->acp6x_base); + if (ret) + dev_err(dev, "ACP init failed\n"); + return ret; +} + +static const struct dev_pm_ops acp6x_pm = { + SET_RUNTIME_PM_OPS(snd_acp6x_suspend, snd_acp6x_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(snd_acp6x_suspend, snd_acp6x_resume) +}; + +static void snd_acp6x_remove(struct pci_dev *pci) +{ + struct acp6x_dev_data *adata; + int ret, index; + + adata = pci_get_drvdata(pci); + if (adata->acp6x_audio_mode == ACP6x_PDM_MODE) { + for (index = 0; index < ACP6x_DEVS; index++) + platform_device_unregister(adata->pdev[index]); + } + ret = acp6x_deinit(adata->acp6x_base); + if (ret) + dev_err(&pci->dev, "ACP de-init failed\n"); + pm_runtime_forbid(&pci->dev); + pm_runtime_get_noresume(&pci->dev); + pci_release_regions(pci); + pci_disable_device(pci); +} + +static const struct pci_device_id snd_acp6x_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_DEVICE_ID), + .class = PCI_CLASS_MULTIMEDIA_OTHER << 8, + .class_mask = 0xffffff }, + { 0, }, +}; +MODULE_DEVICE_TABLE(pci, snd_acp6x_ids); + +static struct pci_driver yc_acp6x_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_acp6x_ids, + .probe = snd_acp6x_probe, + .remove = snd_acp6x_remove, + .driver = { + .pm = &acp6x_pm, + } +}; + +module_pci_driver(yc_acp6x_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD ACP Yellow Carp PCI driver"); +MODULE_LICENSE("GPL v2"); |