summaryrefslogtreecommitdiffstats
path: root/sound/soc/qcom
diff options
context:
space:
mode:
authorNiklas Cassel <cassel@kernel.org>2026-09-16 15:00:32 +0200
committerNiklas Cassel <cassel@kernel.org>2026-09-16 16:18:00 +0200
commit8d836581f9b1f57ceaa2b47654754ef1260b410b (patch)
tree1dbd4d0dfb99c2e238356cbe947e5b0be3e3a571 /sound/soc/qcom
downloadlinux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.tar.gz
linux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.zip
ata: libata-scsi: fix ata_dsm_trim_pages() kernel-docgrafted
make htmldocs fails with: Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301: ERROR: Unexpected indentation. [docutils] The Return: section of ata_dsm_trim_pages() ends its first sentence with a colon and continues with an indented bullet list. reStructuredText requires a blank line before an indented block, so docutils chokes on the list. Simply adding the missing blank line does not work either: Return: is a kernel-doc "special section", which is terminated by the first blank line, so the bullet list would end up in the Description section, detached from the sentence introducing it. Spell the two bounds out as prose instead, so that the Return: section stays self-contained. Documentation-only change. Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND") Reported-by: Thomas Huth <thuth@redhat.com> Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/ Reviewed-by: Damien Le Moal <dlemoal@kernel.org> Reviewed-by: Thomas Huth <thuth@redhat.com> Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org Signed-off-by: Niklas Cassel <cassel@kernel.org>
Diffstat (limited to 'sound/soc/qcom')
-rw-r--r--sound/soc/qcom/Kconfig255
-rw-r--r--sound/soc/qcom/Makefile49
-rw-r--r--sound/soc/qcom/apq8016_sbc.c347
-rw-r--r--sound/soc/qcom/apq8096.c144
-rw-r--r--sound/soc/qcom/common.c423
-rw-r--r--sound/soc/qcom/common.h33
-rw-r--r--sound/soc/qcom/lpass-apq8016.c309
-rw-r--r--sound/soc/qcom/lpass-cdc-dma.c303
-rw-r--r--sound/soc/qcom/lpass-cpu.c1303
-rw-r--r--sound/soc/qcom/lpass-hdmi.c254
-rw-r--r--sound/soc/qcom/lpass-hdmi.h102
-rw-r--r--sound/soc/qcom/lpass-ipq806x.c180
-rw-r--r--sound/soc/qcom/lpass-lpaif-reg.h319
-rw-r--r--sound/soc/qcom/lpass-platform.c1396
-rw-r--r--sound/soc/qcom/lpass-sc7180.c325
-rw-r--r--sound/soc/qcom/lpass-sc7280.c455
-rw-r--r--sound/soc/qcom/lpass.h411
-rw-r--r--sound/soc/qcom/qdsp6/Makefile20
-rw-r--r--sound/soc/qcom/qdsp6/audioreach.c1614
-rw-r--r--sound/soc/qcom/qdsp6/audioreach.h1045
-rw-r--r--sound/soc/qcom/qdsp6/q6adm.c609
-rw-r--r--sound/soc/qcom/qdsp6/q6adm.h27
-rw-r--r--sound/soc/qcom/qdsp6/q6afe-clocks.c120
-rw-r--r--sound/soc/qcom/qdsp6/q6afe-dai.c1201
-rw-r--r--sound/soc/qcom/qdsp6/q6afe.c2005
-rw-r--r--sound/soc/qcom/qdsp6/q6afe.h275
-rw-r--r--sound/soc/qcom/qdsp6/q6apm-dai.c1061
-rw-r--r--sound/soc/qcom/qdsp6/q6apm-lpass-dais.c565
-rw-r--r--sound/soc/qcom/qdsp6/q6apm.c1019
-rw-r--r--sound/soc/qcom/qdsp6/q6apm.h169
-rw-r--r--sound/soc/qcom/qdsp6/q6asm-dai.c1345
-rw-r--r--sound/soc/qcom/qdsp6/q6asm.c1687
-rw-r--r--sound/soc/qcom/qdsp6/q6asm.h152
-rw-r--r--sound/soc/qcom/qdsp6/q6core.c375
-rw-r--r--sound/soc/qcom/qdsp6/q6core.h15
-rw-r--r--sound/soc/qcom/qdsp6/q6dsp-common.c103
-rw-r--r--sound/soc/qcom/qdsp6/q6dsp-common.h25
-rw-r--r--sound/soc/qcom/qdsp6/q6dsp-errno.h51
-rw-r--r--sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c185
-rw-r--r--sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.h30
-rw-r--r--sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c739
-rw-r--r--sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h23
-rw-r--r--sound/soc/qcom/qdsp6/q6prm-clocks.c129
-rw-r--r--sound/soc/qcom/qdsp6/q6prm.c250
-rw-r--r--sound/soc/qcom/qdsp6/q6prm.h141
-rw-r--r--sound/soc/qcom/qdsp6/q6routing.c1250
-rw-r--r--sound/soc/qcom/qdsp6/q6routing.h9
-rw-r--r--sound/soc/qcom/qdsp6/q6usb.c422
-rw-r--r--sound/soc/qcom/qdsp6/topology.c1443
-rw-r--r--sound/soc/qcom/sc7180.c582
-rw-r--r--sound/soc/qcom/sc7280.c399
-rw-r--r--sound/soc/qcom/sc8280xp.c594
-rw-r--r--sound/soc/qcom/sdm845.c591
-rw-r--r--sound/soc/qcom/sdw.c216
-rw-r--r--sound/soc/qcom/sdw.h16
-rw-r--r--sound/soc/qcom/sm8250.c230
-rw-r--r--sound/soc/qcom/storm.c142
-rw-r--r--sound/soc/qcom/usb_offload_utils.c56
-rw-r--r--sound/soc/qcom/usb_offload_utils.h30
-rw-r--r--sound/soc/qcom/x1e80100.c229
60 files changed, 27797 insertions, 0 deletions
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
new file mode 100644
index 000000000..e6e24f3b9
--- /dev/null
+++ b/sound/soc/qcom/Kconfig
@@ -0,0 +1,255 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig SND_SOC_QCOM
+ tristate "Qualcomm"
+ depends on ARCH_QCOM || COMPILE_TEST
+ help
+ Say Y or M if you want to add support to use audio devices
+ in Qualcomm Technologies SOC-based platforms.
+
+if SND_SOC_QCOM
+
+config SND_SOC_LPASS_CPU
+ tristate
+ select REGMAP_MMIO
+
+config SND_SOC_LPASS_HDMI
+ tristate
+ select REGMAP_MMIO
+
+config SND_SOC_LPASS_PLATFORM
+ tristate
+ select REGMAP_MMIO
+
+config SND_SOC_LPASS_CDC_DMA
+ tristate
+ select REGMAP_MMIO
+
+config SND_SOC_LPASS_IPQ806X
+ tristate
+ select SND_SOC_LPASS_CPU
+ select SND_SOC_LPASS_PLATFORM
+
+config SND_SOC_LPASS_APQ8016
+ tristate
+ select SND_SOC_LPASS_CPU
+ select SND_SOC_LPASS_PLATFORM
+
+config SND_SOC_LPASS_SC7180
+ tristate
+ select SND_SOC_LPASS_CPU
+ select SND_SOC_LPASS_PLATFORM
+ select SND_SOC_LPASS_HDMI
+
+config SND_SOC_LPASS_SC7280
+ tristate
+ select SND_SOC_LPASS_CPU
+ select SND_SOC_LPASS_PLATFORM
+ select SND_SOC_LPASS_HDMI
+ select SND_SOC_LPASS_CDC_DMA
+
+config SND_SOC_STORM
+ tristate "ASoC I2S support for Storm boards"
+ depends on GPIOLIB
+ select SND_SOC_LPASS_IPQ806X
+ select SND_SOC_MAX98357A
+ help
+ Say Y or M if you want add support for SoC audio on the
+ Qualcomm Technologies IPQ806X-based Storm board.
+
+config SND_SOC_APQ8016_SBC
+ tristate "SoC Audio support for APQ8016 SBC platforms"
+ select SND_SOC_LPASS_APQ8016
+ select SND_SOC_QCOM_COMMON
+ help
+ Support for Qualcomm Technologies LPASS audio block in
+ APQ8016 SOC-based systems.
+ Say Y if you want to use audio devices on MI2S.
+
+config SND_SOC_QCOM_COMMON
+ tristate
+
+config SND_SOC_QCOM_SDW
+ tristate
+
+config SND_SOC_QDSP6_COMMON
+ tristate
+
+config SND_SOC_QDSP6_CORE
+ tristate
+
+config SND_SOC_QDSP6_AFE
+ tristate
+
+config SND_SOC_QDSP6_AFE_DAI
+ tristate
+
+config SND_SOC_QDSP6_AFE_CLOCKS
+ tristate
+
+config SND_SOC_QDSP6_ADM
+ tristate
+
+config SND_SOC_QDSP6_ROUTING
+ tristate
+
+config SND_SOC_QDSP6_ASM
+ tristate
+
+config SND_SOC_QDSP6_ASM_DAI
+ select SND_SOC_COMPRESS
+ tristate
+
+config SND_SOC_QDSP6_APM_DAI
+ tristate
+ select SND_SOC_COMPRESS
+
+config SND_SOC_QDSP6_APM_LPASS_DAI
+ tristate
+
+config SND_SOC_QDSP6_APM
+ tristate
+ select SND_SOC_QDSP6_APM_DAI
+ select SND_SOC_QDSP6_APM_LPASS_DAI
+
+config SND_SOC_QDSP6_PRM_LPASS_CLOCKS
+ tristate
+
+config SND_SOC_QDSP6_PRM
+ tristate
+ select SND_SOC_QDSP6_PRM_LPASS_CLOCKS
+
+config SND_SOC_QCOM_OFFLOAD_UTILS
+ tristate
+
+config SND_SOC_QDSP6_USB
+ tristate "SoC ALSA USB offloading backing for QDSP6"
+ depends on SND_SOC_USB
+ select AUXILIARY_BUS
+ select SND_SOC_QCOM_OFFLOAD_UTILS
+
+ help
+ Adds support for USB offloading for QDSP6 ASoC
+ based platform sound cards. This will enable the
+ Q6USB DPCM backend DAI link, which will interact
+ with the SoC USB framework to initialize a session
+ with active USB SND devices.
+
+config SND_SOC_QDSP6
+ tristate "SoC ALSA audio driver for QDSP6"
+ depends on QCOM_APR
+ depends on COMMON_CLK
+ select SND_SOC_QDSP6_COMMON
+ select SND_SOC_QDSP6_CORE
+ select SND_SOC_QDSP6_AFE
+ select SND_SOC_QDSP6_AFE_DAI
+ select SND_SOC_QDSP6_AFE_CLOCKS
+ select SND_SOC_QDSP6_ADM
+ select SND_SOC_QDSP6_ROUTING
+ select SND_SOC_QDSP6_ASM
+ select SND_SOC_QDSP6_ASM_DAI
+ select SND_SOC_TOPOLOGY
+ select SND_SOC_QDSP6_APM
+ select SND_SOC_QDSP6_PRM
+ help
+ To add support for MSM QDSP6 Soc Audio.
+ This will enable sound soc platform specific
+ audio drivers. This includes q6asm, q6adm,
+ q6afe interfaces to DSP using apr.
+
+config SND_SOC_MSM8996
+ tristate "SoC Machine driver for MSM8996 and APQ8096 boards"
+ depends on QCOM_APR
+ depends on COMMON_CLK
+ select SND_SOC_QDSP6
+ select SND_SOC_QCOM_COMMON
+ help
+ Support for Qualcomm Technologies LPASS audio block in
+ APQ8096 SoC-based systems.
+ Say Y if you want to use audio device on this SoCs
+
+config SND_SOC_SDM845
+ tristate "SoC Machine driver for SDM845 boards"
+ depends on QCOM_APR && I2C && SOUNDWIRE
+ depends on COMMON_CLK
+ select SND_SOC_QDSP6
+ select SND_SOC_QCOM_COMMON
+ select SND_SOC_QCOM_SDW
+ select SND_SOC_RT5663
+ select SND_SOC_MAX98927
+ imply SND_SOC_CROS_EC_CODEC
+ help
+ To add support for audio on Qualcomm Technologies Inc.
+ SDM845 SoC-based systems.
+ Say Y if you want to use audio device on this SoCs.
+
+config SND_SOC_SM8250
+ tristate "SoC Machine driver for SM8250 boards"
+ depends on QCOM_APR && SOUNDWIRE
+ depends on COMMON_CLK
+ depends on SND_SOC_QCOM_OFFLOAD_UTILS || !SND_SOC_QCOM_OFFLOAD_UTILS
+ select SND_SOC_QDSP6
+ select SND_SOC_QCOM_COMMON
+ select SND_SOC_QCOM_SDW
+ help
+ To add support for audio on Qualcomm Technologies Inc.
+ SM8250 SoC-based systems.
+ Say Y if you want to use audio device on this SoCs.
+
+config SND_SOC_SC8280XP
+ tristate "SoC Machine driver for SC8280XP boards"
+ depends on QCOM_APR && SOUNDWIRE
+ depends on COMMON_CLK
+ select SND_SOC_QDSP6
+ select SND_SOC_QCOM_COMMON
+ select SND_SOC_QCOM_SDW
+ help
+ To add support for audio on Qualcomm Technologies Inc.
+ SC8280XP SoC-based systems.
+ Say Y if you want to use audio device on this SoCs.
+
+config SND_SOC_SC7180
+ tristate "SoC Machine driver for SC7180 boards"
+ depends on I2C && GPIOLIB
+ depends on SOUNDWIRE || SOUNDWIRE=n
+ select SND_SOC_QCOM_COMMON
+ select SND_SOC_LPASS_SC7180
+ select SND_SOC_MAX98357A
+ select SND_SOC_RT5682_I2C
+ select SND_SOC_RT5682S
+ select SND_SOC_ADAU7002
+ help
+ To add support for audio on Qualcomm Technologies Inc.
+ SC7180 SoC-based systems.
+ Say Y if you want to use audio device on this SoCs.
+
+config SND_SOC_SC7280
+ tristate "SoC Machine driver for SC7280 boards"
+ depends on I2C && SOUNDWIRE
+ select SND_SOC_QCOM_COMMON
+ select SND_SOC_QCOM_SDW
+ select SND_SOC_LPASS_SC7280
+ select SND_SOC_MAX98357A
+ select SND_SOC_WCD938X_SDW
+ select SND_SOC_LPASS_MACRO_COMMON
+ imply SND_SOC_LPASS_RX_MACRO
+ imply SND_SOC_LPASS_TX_MACRO
+ select SND_SOC_RT5682_I2C
+ select SND_SOC_RT5682S
+ help
+ Add support for audio on Qualcomm Technologies Inc.
+ SC7280 SoC-based systems.
+ Say Y or M if you want to use audio device on this SoCs.
+
+config SND_SOC_X1E80100
+ tristate "SoC Machine driver for X1E80100 boards"
+ depends on QCOM_APR && SOUNDWIRE
+ depends on COMMON_CLK
+ select SND_SOC_QDSP6
+ select SND_SOC_QCOM_COMMON
+ select SND_SOC_QCOM_SDW
+ help
+ Add support for audio on Qualcomm Technologies Inc.
+ X1E80100 SoC-based systems.
+ Say Y or M if you want to use audio device on this SoCs.
+
+endif #SND_SOC_QCOM
diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile
new file mode 100644
index 000000000..985ce2ae2
--- /dev/null
+++ b/sound/soc/qcom/Makefile
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: GPL-2.0
+# Platform
+snd-soc-lpass-cpu-y := lpass-cpu.o
+snd-soc-lpass-cdc-dma-y := lpass-cdc-dma.o
+snd-soc-lpass-hdmi-y := lpass-hdmi.o
+snd-soc-lpass-platform-y := lpass-platform.o
+snd-soc-lpass-ipq806x-y := lpass-ipq806x.o
+snd-soc-lpass-apq8016-y := lpass-apq8016.o
+snd-soc-lpass-sc7180-y := lpass-sc7180.o
+snd-soc-lpass-sc7280-y := lpass-sc7280.o
+
+obj-$(CONFIG_SND_SOC_LPASS_CPU) += snd-soc-lpass-cpu.o
+obj-$(CONFIG_SND_SOC_LPASS_CDC_DMA) += snd-soc-lpass-cdc-dma.o
+obj-$(CONFIG_SND_SOC_LPASS_HDMI) += snd-soc-lpass-hdmi.o
+obj-$(CONFIG_SND_SOC_LPASS_PLATFORM) += snd-soc-lpass-platform.o
+obj-$(CONFIG_SND_SOC_LPASS_IPQ806X) += snd-soc-lpass-ipq806x.o
+obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o
+obj-$(CONFIG_SND_SOC_LPASS_SC7180) += snd-soc-lpass-sc7180.o
+obj-$(CONFIG_SND_SOC_LPASS_SC7280) += snd-soc-lpass-sc7280.o
+
+# Machine
+snd-soc-storm-y := storm.o
+snd-soc-apq8016-sbc-y := apq8016_sbc.o
+snd-soc-apq8096-y := apq8096.o
+snd-soc-sc7180-y := sc7180.o
+snd-soc-sc7280-y := sc7280.o
+snd-soc-sdm845-y := sdm845.o
+snd-soc-sm8250-y := sm8250.o
+snd-soc-sc8280xp-y := sc8280xp.o
+snd-soc-qcom-common-y := common.o
+snd-soc-qcom-sdw-y := sdw.o
+snd-soc-x1e80100-y := x1e80100.o
+snd-soc-qcom-offload-utils-objs := usb_offload_utils.o
+
+obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o
+obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o
+obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-apq8096.o
+obj-$(CONFIG_SND_SOC_SC7180) += snd-soc-sc7180.o
+obj-$(CONFIG_SND_SOC_SC7280) += snd-soc-sc7280.o
+obj-$(CONFIG_SND_SOC_SC8280XP) += snd-soc-sc8280xp.o
+obj-$(CONFIG_SND_SOC_SDM845) += snd-soc-sdm845.o
+obj-$(CONFIG_SND_SOC_SM8250) += snd-soc-sm8250.o
+obj-$(CONFIG_SND_SOC_QCOM_COMMON) += snd-soc-qcom-common.o
+obj-$(CONFIG_SND_SOC_QCOM_SDW) += snd-soc-qcom-sdw.o
+obj-$(CONFIG_SND_SOC_X1E80100) += snd-soc-x1e80100.o
+obj-$(CONFIG_SND_SOC_QCOM_OFFLOAD_UTILS) += snd-soc-qcom-offload-utils.o
+
+#DSP lib
+obj-$(CONFIG_SND_SOC_QDSP6) += qdsp6/
diff --git a/sound/soc/qcom/apq8016_sbc.c b/sound/soc/qcom/apq8016_sbc.c
new file mode 100644
index 000000000..3023cf180
--- /dev/null
+++ b/sound/soc/qcom/apq8016_sbc.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2015 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <uapi/linux/input-event-codes.h>
+#include <dt-bindings/sound/apq8016-lpass.h>
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include "common.h"
+#include "qdsp6/q6afe.h"
+
+#define MI2S_COUNT (MI2S_QUATERNARY + 1)
+
+struct apq8016_sbc_data {
+ struct snd_soc_card card;
+ void __iomem *mic_iomux;
+ void __iomem *spkr_iomux;
+ struct snd_soc_jack jack;
+ bool jack_setup;
+ int mi2s_clk_count[MI2S_COUNT];
+};
+
+#define MIC_CTRL_TER_WS_SLAVE_SEL BIT(21)
+#define MIC_CTRL_QUA_WS_SLAVE_SEL_10 BIT(17)
+#define MIC_CTRL_TLMM_SCLK_EN BIT(1)
+#define SPKR_CTL_PRI_WS_SLAVE_SEL_11 (BIT(17) | BIT(16))
+#define SPKR_CTL_TLMM_MCLK_EN BIT(1)
+#define SPKR_CTL_TLMM_SCLK_EN BIT(2)
+#define SPKR_CTL_TLMM_DATA1_EN BIT(3)
+#define SPKR_CTL_TLMM_WS_OUT_SEL_MASK GENMASK(7, 6)
+#define SPKR_CTL_TLMM_WS_OUT_SEL_SEC BIT(6)
+#define SPKR_CTL_TLMM_WS_EN_SEL_MASK GENMASK(19, 18)
+#define SPKR_CTL_TLMM_WS_EN_SEL_SEC BIT(18)
+#define DEFAULT_MCLK_RATE 9600000
+#define MI2S_BCLK_RATE 1536000
+
+static struct snd_soc_jack_pin apq8016_sbc_jack_pins[] = {
+ {
+ .pin = "Mic Jack",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static int apq8016_dai_init(struct snd_soc_pcm_runtime *rtd, int mi2s)
+{
+ struct snd_soc_dai *codec_dai;
+ struct snd_soc_component *component;
+ struct snd_soc_card *card = rtd->card;
+ struct apq8016_sbc_data *pdata = snd_soc_card_get_drvdata(card);
+ int i, rval;
+ u32 value;
+
+ switch (mi2s) {
+ case MI2S_PRIMARY:
+ writel(readl(pdata->spkr_iomux) | SPKR_CTL_PRI_WS_SLAVE_SEL_11,
+ pdata->spkr_iomux);
+ break;
+
+ case MI2S_QUATERNARY:
+ /* Configure the Quat MI2S to TLMM */
+ writel(readl(pdata->mic_iomux) | MIC_CTRL_QUA_WS_SLAVE_SEL_10 |
+ MIC_CTRL_TLMM_SCLK_EN,
+ pdata->mic_iomux);
+ break;
+ case MI2S_SECONDARY:
+ /* Clear TLMM_WS_OUT_SEL and TLMM_WS_EN_SEL fields */
+ value = readl(pdata->spkr_iomux) &
+ ~(SPKR_CTL_TLMM_WS_OUT_SEL_MASK | SPKR_CTL_TLMM_WS_EN_SEL_MASK);
+ /* Configure the Sec MI2S to TLMM */
+ writel(value | SPKR_CTL_TLMM_MCLK_EN | SPKR_CTL_TLMM_SCLK_EN |
+ SPKR_CTL_TLMM_DATA1_EN | SPKR_CTL_TLMM_WS_OUT_SEL_SEC |
+ SPKR_CTL_TLMM_WS_EN_SEL_SEC, pdata->spkr_iomux);
+ break;
+ case MI2S_TERTIARY:
+ writel(readl(pdata->mic_iomux) | MIC_CTRL_TER_WS_SLAVE_SEL |
+ MIC_CTRL_TLMM_SCLK_EN,
+ pdata->mic_iomux);
+
+ break;
+
+ default:
+ dev_err(card->dev, "unsupported cpu dai configuration\n");
+ return -EINVAL;
+
+ }
+
+ if (!pdata->jack_setup) {
+ struct snd_jack *jack;
+
+ rval = snd_soc_card_jack_new_pins(card, "Headset Jack",
+ SND_JACK_HEADSET |
+ SND_JACK_HEADPHONE |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3 |
+ SND_JACK_BTN_4,
+ &pdata->jack,
+ apq8016_sbc_jack_pins,
+ ARRAY_SIZE(apq8016_sbc_jack_pins));
+
+ if (rval < 0) {
+ dev_err(card->dev, "Unable to add Headphone Jack\n");
+ return rval;
+ }
+
+ jack = pdata->jack.jack;
+
+ snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+ pdata->jack_setup = true;
+ }
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+
+ component = codec_dai->component;
+ /* Set default mclk for internal codec */
+ rval = snd_soc_component_set_sysclk(component, 0, 0, DEFAULT_MCLK_RATE,
+ SND_SOC_CLOCK_IN);
+ if (rval != 0 && rval != -ENOTSUPP) {
+ dev_warn(card->dev, "Failed to set mclk: %d\n", rval);
+ return rval;
+ }
+ rval = snd_soc_component_set_jack(component, &pdata->jack, NULL);
+ if (rval != 0 && rval != -ENOTSUPP) {
+ dev_warn(card->dev, "Failed to set jack: %d\n", rval);
+ return rval;
+ }
+ }
+
+ return 0;
+}
+
+static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ return apq8016_dai_init(rtd, cpu_dai->id);
+}
+
+static void apq8016_sbc_add_ops(struct snd_soc_card *card)
+{
+ struct snd_soc_dai_link *link;
+ int i;
+
+ for_each_card_prelinks(card, i, link)
+ link->init = apq8016_sbc_dai_init;
+}
+
+static int qdsp6_dai_get_lpass_id(struct snd_soc_dai *cpu_dai)
+{
+ switch (cpu_dai->id) {
+ case PRIMARY_MI2S_RX:
+ case PRIMARY_MI2S_TX:
+ return MI2S_PRIMARY;
+ case SECONDARY_MI2S_RX:
+ case SECONDARY_MI2S_TX:
+ return MI2S_SECONDARY;
+ case TERTIARY_MI2S_RX:
+ case TERTIARY_MI2S_TX:
+ return MI2S_TERTIARY;
+ case QUATERNARY_MI2S_RX:
+ case QUATERNARY_MI2S_TX:
+ return MI2S_QUATERNARY;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int msm8916_qdsp6_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP);
+ return apq8016_dai_init(rtd, qdsp6_dai_get_lpass_id(cpu_dai));
+}
+
+static int msm8916_qdsp6_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_card *card = rtd->card;
+ struct apq8016_sbc_data *data = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ int mi2s, ret;
+
+ mi2s = qdsp6_dai_get_lpass_id(cpu_dai);
+ if (mi2s < 0)
+ return mi2s;
+
+ if (++data->mi2s_clk_count[mi2s] > 1)
+ return 0;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_BIT_CLK, MI2S_BCLK_RATE, 0);
+ if (ret)
+ dev_err(card->dev, "Failed to enable LPAIF bit clk: %d\n", ret);
+ return ret;
+}
+
+static void msm8916_qdsp6_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_card *card = rtd->card;
+ struct apq8016_sbc_data *data = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ int mi2s, ret;
+
+ mi2s = qdsp6_dai_get_lpass_id(cpu_dai);
+ if (mi2s < 0)
+ return;
+
+ if (--data->mi2s_clk_count[mi2s] > 0)
+ return;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_BIT_CLK, 0, 0);
+ if (ret)
+ dev_err(card->dev, "Failed to disable LPAIF bit clk: %d\n", ret);
+}
+
+static const struct snd_soc_ops msm8916_qdsp6_be_ops = {
+ .startup = msm8916_qdsp6_startup,
+ .shutdown = msm8916_qdsp6_shutdown,
+};
+
+static int msm8916_qdsp6_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+
+ return 0;
+}
+
+static void msm8916_qdsp6_add_ops(struct snd_soc_card *card)
+{
+ struct snd_soc_dai_link *link;
+ int i;
+
+ /* Make it obvious to userspace that QDSP6 is used */
+ card->components = "qdsp6";
+
+ for_each_card_prelinks(card, i, link) {
+ if (link->no_pcm) {
+ link->init = msm8916_qdsp6_dai_init;
+ link->ops = &msm8916_qdsp6_be_ops;
+ link->be_hw_params_fixup = msm8916_qdsp6_be_hw_params_fixup;
+ }
+ }
+}
+
+static const struct snd_kcontrol_new apq8016_sbc_snd_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Mic Jack"),
+};
+
+static const struct snd_soc_dapm_widget apq8016_sbc_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_MIC("Handset Mic", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Secondary Mic", NULL),
+ SND_SOC_DAPM_MIC("Digital Mic1", NULL),
+ SND_SOC_DAPM_MIC("Digital Mic2", NULL),
+};
+
+static int apq8016_sbc_platform_probe(struct platform_device *pdev)
+{
+ void (*add_ops)(struct snd_soc_card *card);
+ struct device *dev = &pdev->dev;
+ struct snd_soc_card *card;
+ struct apq8016_sbc_data *data;
+ int ret;
+
+ add_ops = device_get_match_data(&pdev->dev);
+ if (!add_ops)
+ return -EINVAL;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ card = &data->card;
+ card->dev = dev;
+ card->owner = THIS_MODULE;
+ card->dapm_widgets = apq8016_sbc_dapm_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(apq8016_sbc_dapm_widgets);
+ card->controls = apq8016_sbc_snd_controls;
+ card->num_controls = ARRAY_SIZE(apq8016_sbc_snd_controls);
+
+ ret = qcom_snd_parse_of(card);
+ if (ret)
+ return ret;
+
+ data->mic_iomux = devm_platform_ioremap_resource_byname(pdev, "mic-iomux");
+ if (IS_ERR(data->mic_iomux))
+ return PTR_ERR(data->mic_iomux);
+
+ data->spkr_iomux = devm_platform_ioremap_resource_byname(pdev, "spkr-iomux");
+ if (IS_ERR(data->spkr_iomux))
+ return PTR_ERR(data->spkr_iomux);
+
+ snd_soc_card_set_drvdata(card, data);
+
+ add_ops(card);
+ return devm_snd_soc_register_card(&pdev->dev, card);
+}
+
+static const struct of_device_id apq8016_sbc_device_id[] __maybe_unused = {
+ { .compatible = "qcom,apq8016-sbc-sndcard", .data = apq8016_sbc_add_ops },
+ { .compatible = "qcom,msm8916-qdsp6-sndcard", .data = msm8916_qdsp6_add_ops },
+ {},
+};
+MODULE_DEVICE_TABLE(of, apq8016_sbc_device_id);
+
+static struct platform_driver apq8016_sbc_platform_driver = {
+ .driver = {
+ .name = "qcom-apq8016-sbc",
+ .of_match_table = of_match_ptr(apq8016_sbc_device_id),
+ },
+ .probe = apq8016_sbc_platform_probe,
+};
+module_platform_driver(apq8016_sbc_platform_driver);
+
+MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org");
+MODULE_DESCRIPTION("APQ8016 ASoC Machine Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/apq8096.c b/sound/soc/qcom/apq8096.c
new file mode 100644
index 000000000..cfd6438db
--- /dev/null
+++ b/sound/soc/qcom/apq8096.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018, Linaro Limited
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include "common.h"
+
+#define SLIM_MAX_TX_PORTS 16
+#define SLIM_MAX_RX_PORTS 16
+#define WCD9335_DEFAULT_MCLK_RATE 9600000
+
+static int apq8096_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ return 0;
+}
+
+static int msm_snd_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ u32 rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS];
+ u32 rx_ch_cnt = 0, tx_ch_cnt = 0;
+ int ret = 0;
+
+ ret = snd_soc_dai_get_channel_map(codec_dai,
+ &tx_ch_cnt, tx_ch, &rx_ch_cnt, rx_ch);
+ if (ret != 0 && ret != -ENOTSUPP) {
+ pr_err("failed to get codec chan map, err:%d\n", ret);
+ goto end;
+ } else if (ret == -ENOTSUPP) {
+ return 0;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = snd_soc_dai_set_channel_map(cpu_dai, 0, NULL,
+ rx_ch_cnt, rx_ch);
+ else
+ ret = snd_soc_dai_set_channel_map(cpu_dai, tx_ch_cnt, tx_ch,
+ 0, NULL);
+ if (ret != 0 && ret != -ENOTSUPP)
+ pr_err("Failed to set cpu chan map, err:%d\n", ret);
+ else if (ret == -ENOTSUPP)
+ ret = 0;
+end:
+ return ret;
+}
+
+static const struct snd_soc_ops apq8096_ops = {
+ .hw_params = msm_snd_hw_params,
+};
+
+static int apq8096_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+ /*
+ * Codec SLIMBUS configuration
+ * RX1, RX2, RX3, RX4, RX5, RX6, RX7, RX8, RX9, RX10, RX11, RX12, RX13
+ * TX1, TX2, TX3, TX4, TX5, TX6, TX7, TX8, TX9, TX10, TX11, TX12, TX13
+ * TX14, TX15, TX16
+ */
+ unsigned int rx_ch[SLIM_MAX_RX_PORTS] = {144, 145, 146, 147, 148, 149,
+ 150, 151, 152, 153, 154, 155, 156};
+ unsigned int tx_ch[SLIM_MAX_TX_PORTS] = {128, 129, 130, 131, 132, 133,
+ 134, 135, 136, 137, 138, 139,
+ 140, 141, 142, 143};
+
+ snd_soc_dai_set_channel_map(codec_dai, ARRAY_SIZE(tx_ch),
+ tx_ch, ARRAY_SIZE(rx_ch), rx_ch);
+
+ snd_soc_dai_set_sysclk(codec_dai, 0, WCD9335_DEFAULT_MCLK_RATE,
+ SNDRV_PCM_STREAM_PLAYBACK);
+
+ return 0;
+}
+
+static void apq8096_add_be_ops(struct snd_soc_card *card)
+{
+ struct snd_soc_dai_link *link;
+ int i;
+
+ for_each_card_prelinks(card, i, link) {
+ if (link->no_pcm == 1) {
+ link->be_hw_params_fixup = apq8096_be_hw_params_fixup;
+ link->init = apq8096_init;
+ link->ops = &apq8096_ops;
+ }
+ }
+}
+
+static int apq8096_platform_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+
+ card->driver_name = "apq8096";
+ card->dev = dev;
+ card->owner = THIS_MODULE;
+ dev_set_drvdata(dev, card);
+ ret = qcom_snd_parse_of(card);
+ if (ret)
+ return ret;
+
+ apq8096_add_be_ops(card);
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static const struct of_device_id msm_snd_apq8096_dt_match[] = {
+ {.compatible = "qcom,apq8096-sndcard"},
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, msm_snd_apq8096_dt_match);
+
+static struct platform_driver msm_snd_apq8096_driver = {
+ .probe = apq8096_platform_probe,
+ .driver = {
+ .name = "msm-snd-apq8096",
+ .of_match_table = msm_snd_apq8096_dt_match,
+ },
+};
+module_platform_driver(msm_snd_apq8096_driver);
+MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org");
+MODULE_DESCRIPTION("APQ8096 ASoC Machine Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c
new file mode 100644
index 000000000..d9f256d51
--- /dev/null
+++ b/sound/soc/qcom/common.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018, Linaro Limited.
+// Copyright (c) 2018, The Linux Foundation. All rights reserved.
+
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/module.h>
+#include <sound/jack.h>
+#include <linux/input-event-codes.h>
+#include "common.h"
+
+#define NAME_SIZE 32
+
+static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_SPK("DP0 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP1 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP2 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP3 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP4 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP5 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP6 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP7 Jack", NULL),
+};
+
+static struct device_node *qcom_snd_get_link_node(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_card *card = rtd->card;
+ struct of_phandle_args args;
+ int ret;
+
+ if (!card->dev || !card->dev->of_node)
+ return NULL;
+
+ for_each_available_child_of_node_scoped(card->dev->of_node, np) {
+ struct device_node *cpu_np __free(device_node) =
+ of_get_child_by_name(np, "cpu");
+
+ if (!cpu_np)
+ continue;
+
+ ret = of_parse_phandle_with_args(cpu_np, "sound-dai", "#sound-dai-cells", 0,
+ &args);
+ if (ret)
+ continue;
+
+ if (args.np == rtd->dai_link->cpus[0].of_node &&
+ args.args_count == 1 && args.args[0] == cpu_dai->id) {
+ of_node_put(args.np);
+ return of_node_get(np);
+ }
+
+ of_node_put(args.np);
+ }
+
+ return NULL;
+}
+
+static int qcom_snd_parse_tdm_slot(struct device_node *np,
+ struct qcom_snd_tdm_slot_cfg *cfg)
+{
+ memset(cfg, 0, sizeof(*cfg));
+
+ return snd_soc_of_parse_tdm_slot(np, &cfg->tx_mask, &cfg->rx_mask,
+ &cfg->slots, &cfg->slot_width);
+}
+
+static int qcom_snd_normalize_tdm_slots(struct qcom_snd_tdm_slot_cfg *cpu_cfg,
+ struct qcom_snd_tdm_slot_cfg *codec_cfg)
+{
+ unsigned int slots;
+ unsigned int slot_width;
+
+ if (cpu_cfg->slots && codec_cfg->slots && cpu_cfg->slots != codec_cfg->slots)
+ return -EINVAL;
+
+ if (cpu_cfg->slot_width && codec_cfg->slot_width &&
+ cpu_cfg->slot_width != codec_cfg->slot_width)
+ return -EINVAL;
+
+ slots = cpu_cfg->slots ?: codec_cfg->slots;
+ if (!slots)
+ return 0;
+
+ slot_width = cpu_cfg->slot_width ?: codec_cfg->slot_width;
+ if (!slot_width)
+ return -EINVAL;
+
+ cpu_cfg->slots = slots;
+ codec_cfg->slots = slots;
+ cpu_cfg->slot_width = slot_width;
+ codec_cfg->slot_width = slot_width;
+
+ return 0;
+}
+
+static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd,
+ struct qcom_snd_tdm_slot_cfg *cpu_cfg,
+ struct qcom_snd_tdm_slot_cfg *codec_cfg)
+{
+ struct device_node *link_np __free(device_node) = qcom_snd_get_link_node(rtd);
+ int ret;
+
+ if (!link_np)
+ return -ENOENT;
+
+ struct device_node *cpu_np __free(device_node) =
+ of_get_child_by_name(link_np, "cpu");
+ struct device_node *codec_np __free(device_node) =
+ of_get_child_by_name(link_np, "codec");
+ if (!cpu_np || !codec_np)
+ return -ENOENT;
+
+ ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg);
+ if (ret)
+ return ret;
+
+ return qcom_snd_parse_tdm_slot(codec_np, codec_cfg);
+}
+
+int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd,
+ struct qcom_snd_tdm_slot_cfg *cpu_cfg,
+ struct qcom_snd_tdm_slot_cfg *codec_cfg)
+{
+ int ret;
+
+ ret = qcom_snd_parse_dai_tdm_slots(rtd, cpu_cfg, codec_cfg);
+ if (ret)
+ return ret;
+
+ return qcom_snd_normalize_tdm_slots(cpu_cfg, codec_cfg);
+}
+EXPORT_SYMBOL_GPL(qcom_snd_get_dai_tdm_slots);
+
+int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd,
+ const struct qcom_snd_tdm_slot_cfg *cpu_cfg,
+ const struct qcom_snd_tdm_slot_cfg *codec_cfg)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai;
+ int i;
+ int ret;
+
+ if (!cpu_cfg->slots)
+ return 0;
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, cpu_cfg->tx_mask, cpu_cfg->rx_mask,
+ cpu_cfg->slots, cpu_cfg->slot_width);
+ if (ret)
+ return ret;
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ ret = snd_soc_dai_set_tdm_slot(codec_dai,
+ codec_cfg->tx_mask,
+ codec_cfg->rx_mask,
+ codec_cfg->slots,
+ codec_cfg->slot_width);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots_cfg);
+
+int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd)
+{
+ struct qcom_snd_tdm_slot_cfg cpu_cfg;
+ struct qcom_snd_tdm_slot_cfg codec_cfg;
+ int ret;
+
+ ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg);
+ if (ret)
+ return ret == -ENOENT ? 0 : ret;
+
+ return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg);
+}
+EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots);
+
+int qcom_snd_parse_of(struct snd_soc_card *card)
+{
+ struct device *dev = card->dev;
+ struct snd_soc_dai_link *link;
+ struct of_phandle_args args;
+ struct snd_soc_dai_link_component *dlc;
+ int ret, num_links;
+
+ ret = snd_soc_of_parse_card_name(card, "model");
+ if (ret == 0 && !card->name)
+ /* Deprecated, only for compatibility with old device trees */
+ ret = snd_soc_of_parse_card_name(card, "qcom,model");
+ if (ret) {
+ dev_err(dev, "Error parsing card name: %d\n", ret);
+ return ret;
+ }
+
+ if (of_property_present(dev->of_node, "widgets")) {
+ ret = snd_soc_of_parse_audio_simple_widgets(card, "widgets");
+ if (ret)
+ return ret;
+ }
+
+ /* DAPM routes */
+ if (of_property_present(dev->of_node, "audio-routing")) {
+ ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
+ if (ret)
+ return ret;
+ }
+ /* Deprecated, only for compatibility with old device trees */
+ if (of_property_present(dev->of_node, "qcom,audio-routing")) {
+ ret = snd_soc_of_parse_audio_routing(card, "qcom,audio-routing");
+ if (ret)
+ return ret;
+ }
+
+ ret = snd_soc_of_parse_pin_switches(card, "pin-switches");
+ if (ret)
+ return ret;
+
+ ret = snd_soc_of_parse_aux_devs(card, "aux-devs");
+ if (ret)
+ return ret;
+
+ /* Populate links */
+ num_links = of_get_available_child_count(dev->of_node);
+
+ /* Allocate the DAI link array */
+ card->dai_link = devm_kcalloc(dev, num_links, sizeof(*link), GFP_KERNEL);
+ if (!card->dai_link)
+ return -ENOMEM;
+
+ card->num_links = num_links;
+ link = card->dai_link;
+
+ for_each_available_child_of_node_scoped(dev->of_node, np) {
+ dlc = devm_kcalloc(dev, 2, sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ link->cpus = &dlc[0];
+ link->platforms = &dlc[1];
+
+ link->num_cpus = 1;
+ link->num_platforms = 1;
+
+ ret = of_property_read_string(np, "link-name", &link->name);
+ if (ret) {
+ dev_err(dev, "error getting codec dai_link name\n");
+ return ret;
+ }
+
+ struct device_node *cpu __free(device_node) =
+ of_get_child_by_name(np, "cpu");
+ struct device_node *platform __free(device_node) =
+ of_get_child_by_name(np, "platform");
+ struct device_node *codec __free(device_node) =
+ of_get_child_by_name(np, "codec");
+
+ if (!cpu) {
+ dev_err(dev, "%s: Can't find cpu DT node\n", link->name);
+ return -EINVAL;
+ }
+
+ ret = snd_soc_of_get_dlc(cpu, &args, link->cpus, 0);
+ if (ret) {
+ dev_err_probe(dev, ret,
+ "%s: error getting cpu dai name\n", link->name);
+ return ret;
+ }
+
+ link->id = args.args[0];
+
+ if (link->id >= LPASS_MAX_PORT) {
+ dev_err(dev, "%s: Invalid cpu dai id %d\n", link->name, link->id);
+ return -EINVAL;
+ }
+
+ if (platform) {
+ link->platforms->of_node = of_parse_phandle(platform,
+ "sound-dai",
+ 0);
+ if (!link->platforms->of_node) {
+ dev_err(dev, "%s: platform dai not found\n", link->name);
+ return -EINVAL;
+ }
+ } else {
+ link->platforms->of_node = link->cpus->of_node;
+ }
+
+ if (codec) {
+ ret = snd_soc_of_get_dai_link_codecs(dev, codec, link);
+ if (ret < 0) {
+ dev_err_probe(dev, ret,
+ "%s: codec dai not found\n", link->name);
+ return ret;
+ }
+
+ if (platform) {
+ /* DPCM backend */
+ link->no_pcm = 1;
+ link->ignore_pmdown_time = 1;
+ }
+ } else {
+ /* DPCM frontend */
+ link->codecs = &snd_soc_dummy_dlc;
+ link->num_codecs = 1;
+ link->dynamic = 1;
+ }
+
+ if (platform || !codec) {
+ /* DPCM */
+ link->ignore_suspend = 1;
+ link->nonatomic = 1;
+ }
+
+ link->stream_name = link->name;
+ link++;
+ }
+
+ if (!card->dapm_widgets) {
+ card->dapm_widgets = qcom_jack_snd_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(qcom_jack_snd_widgets);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(qcom_snd_parse_of);
+
+static struct snd_soc_jack_pin qcom_headset_jack_pins[] = {
+ /* Headset */
+ {
+ .pin = "Mic Jack",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_jack *jack, bool *jack_setup)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_card *card = rtd->card;
+ int rval, i;
+
+ if (!*jack_setup) {
+ rval = snd_soc_card_jack_new_pins(card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_LINEOUT |
+ SND_JACK_MECHANICAL |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3 |
+ SND_JACK_BTN_4 | SND_JACK_BTN_5,
+ jack, qcom_headset_jack_pins,
+ ARRAY_SIZE(qcom_headset_jack_pins));
+
+ if (rval < 0) {
+ dev_err(card->dev, "Unable to add Headphone Jack\n");
+ return rval;
+ }
+
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_MEDIA);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+ *jack_setup = true;
+ }
+
+ switch (cpu_dai->id) {
+ case LPI_MI2S_RX_0:
+ case TX_CODEC_DMA_TX_0:
+ case TX_CODEC_DMA_TX_1:
+ case TX_CODEC_DMA_TX_2:
+ case TX_CODEC_DMA_TX_3:
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ rval = snd_soc_component_set_jack(codec_dai->component,
+ jack, NULL);
+ if (rval != 0 && rval != -ENOTSUPP) {
+ dev_warn(card->dev, "Failed to set jack: %d\n", rval);
+ return rval;
+ }
+ }
+
+ break;
+ default:
+ break;
+ }
+
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(qcom_snd_wcd_jack_setup);
+
+int qcom_snd_dp_jack_setup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_jack *dp_jack, int dp_pcm_id)
+{
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_card *card = rtd->card;
+ char jack_name[NAME_SIZE];
+ int rval, i;
+
+ snprintf(jack_name, sizeof(jack_name), "DP%d Jack", dp_pcm_id);
+ rval = snd_soc_card_jack_new(card, jack_name, SND_JACK_AVOUT, dp_jack);
+ if (rval)
+ return rval;
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ rval = snd_soc_component_set_jack(codec_dai->component, dp_jack, NULL);
+ if (rval != 0 && rval != -ENOTSUPP) {
+ dev_warn(card->dev, "Failed to set jack: %d\n", rval);
+ return rval;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(qcom_snd_dp_jack_setup);
+
+MODULE_DESCRIPTION("ASoC Qualcomm helper functions");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h
new file mode 100644
index 000000000..c1deac109
--- /dev/null
+++ b/sound/soc/qcom/common.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+// Copyright (c) 2018, The Linux Foundation. All rights reserved.
+
+#ifndef __QCOM_SND_COMMON_H__
+#define __QCOM_SND_COMMON_H__
+
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <sound/soc.h>
+
+#define LPASS_MAX_PORT (LPI_MI2S_TX_6 + 1)
+
+struct qcom_snd_tdm_slot_cfg {
+ unsigned int tx_mask;
+ unsigned int rx_mask;
+ unsigned int slots;
+ unsigned int slot_width;
+};
+
+int qcom_snd_parse_of(struct snd_soc_card *card);
+int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd,
+ struct qcom_snd_tdm_slot_cfg *cpu_cfg,
+ struct qcom_snd_tdm_slot_cfg *codec_cfg);
+int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd,
+ const struct qcom_snd_tdm_slot_cfg *cpu_cfg,
+ const struct qcom_snd_tdm_slot_cfg *codec_cfg);
+int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd);
+int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_jack *jack, bool *jack_setup);
+int qcom_snd_dp_jack_setup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_jack *dp_jack, int id);
+
+
+#endif
diff --git a/sound/soc/qcom/lpass-apq8016.c b/sound/soc/qcom/lpass-apq8016.c
new file mode 100644
index 000000000..b8f23414e
--- /dev/null
+++ b/sound/soc/qcom/lpass-apq8016.c
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
+ *
+ * lpass-apq8016.c -- ALSA SoC CPU DAI driver for APQ8016 LPASS
+ */
+
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include <dt-bindings/sound/apq8016-lpass.h>
+#include "lpass-lpaif-reg.h"
+#include "lpass.h"
+
+static struct snd_soc_dai_driver apq8016_lpass_cpu_dai_driver[] = {
+ [MI2S_PRIMARY] = {
+ .id = MI2S_PRIMARY,
+ .name = "Primary MI2S",
+ .playback = {
+ .stream_name = "Primary Playback",
+ .formats = SNDRV_PCM_FMTBIT_S16 |
+ SNDRV_PCM_FMTBIT_S24 |
+ SNDRV_PCM_FMTBIT_S32,
+ .rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .ops = &asoc_qcom_lpass_cpu_dai_ops,
+ },
+ [MI2S_SECONDARY] = {
+ .id = MI2S_SECONDARY,
+ .name = "Secondary MI2S",
+ .playback = {
+ .stream_name = "Secondary Playback",
+ .formats = SNDRV_PCM_FMTBIT_S16 |
+ SNDRV_PCM_FMTBIT_S24 |
+ SNDRV_PCM_FMTBIT_S32,
+ .rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .ops = &asoc_qcom_lpass_cpu_dai_ops,
+ },
+ [MI2S_TERTIARY] = {
+ .id = MI2S_TERTIARY,
+ .name = "Tertiary MI2S",
+ .capture = {
+ .stream_name = "Tertiary Capture",
+ .formats = SNDRV_PCM_FMTBIT_S16 |
+ SNDRV_PCM_FMTBIT_S24 |
+ SNDRV_PCM_FMTBIT_S32,
+ .rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .ops = &asoc_qcom_lpass_cpu_dai_ops,
+ },
+ [MI2S_QUATERNARY] = {
+ .id = MI2S_QUATERNARY,
+ .name = "Quatenary MI2S",
+ .playback = {
+ .stream_name = "Quatenary Playback",
+ .formats = SNDRV_PCM_FMTBIT_S16 |
+ SNDRV_PCM_FMTBIT_S24 |
+ SNDRV_PCM_FMTBIT_S32,
+ .rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .stream_name = "Quatenary Capture",
+ .formats = SNDRV_PCM_FMTBIT_S16 |
+ SNDRV_PCM_FMTBIT_S24 |
+ SNDRV_PCM_FMTBIT_S32,
+ .rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .ops = &asoc_qcom_lpass_cpu_dai_ops,
+ },
+};
+
+static int apq8016_lpass_alloc_dma_channel(struct lpass_data *drvdata,
+ int direction, unsigned int dai_id)
+{
+ const struct lpass_variant *v = drvdata->variant;
+ int chan = 0;
+
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ chan = find_first_zero_bit(&drvdata->dma_ch_bit_map,
+ v->rdma_channels);
+
+ if (chan >= v->rdma_channels)
+ return -EBUSY;
+ } else {
+ chan = find_next_zero_bit(&drvdata->dma_ch_bit_map,
+ v->wrdma_channel_start +
+ v->wrdma_channels,
+ v->wrdma_channel_start);
+
+ if (chan >= v->wrdma_channel_start + v->wrdma_channels)
+ return -EBUSY;
+ }
+
+ set_bit(chan, &drvdata->dma_ch_bit_map);
+
+ return chan;
+}
+
+static int apq8016_lpass_free_dma_channel(struct lpass_data *drvdata, int chan, unsigned int dai_id)
+{
+ clear_bit(chan, &drvdata->dma_ch_bit_map);
+
+ return 0;
+}
+
+static int apq8016_lpass_init(struct platform_device *pdev)
+{
+ struct lpass_data *drvdata = platform_get_drvdata(pdev);
+ const struct lpass_variant *variant = drvdata->variant;
+ struct device *dev = &pdev->dev;
+ int ret, i;
+
+
+ drvdata->clks = devm_kcalloc(dev, variant->num_clks,
+ sizeof(*drvdata->clks), GFP_KERNEL);
+ if (!drvdata->clks)
+ return -ENOMEM;
+ drvdata->num_clks = variant->num_clks;
+
+ for (i = 0; i < drvdata->num_clks; i++)
+ drvdata->clks[i].id = variant->clk_name[i];
+
+ ret = devm_clk_bulk_get(dev, drvdata->num_clks, drvdata->clks);
+ if (ret) {
+ dev_err(dev, "Failed to get clocks %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_bulk_prepare_enable(drvdata->num_clks, drvdata->clks);
+ if (ret) {
+ dev_err(dev, "apq8016 clk_enable failed\n");
+ return ret;
+ }
+
+ drvdata->ahbix_clk = devm_clk_get(dev, "ahbix-clk");
+ if (IS_ERR(drvdata->ahbix_clk)) {
+ dev_err(dev, "error getting ahbix-clk: %ld\n",
+ PTR_ERR(drvdata->ahbix_clk));
+ ret = PTR_ERR(drvdata->ahbix_clk);
+ goto err_ahbix_clk;
+ }
+
+ ret = clk_set_rate(drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY);
+ if (ret) {
+ dev_err(dev, "error setting rate on ahbix_clk: %d\n", ret);
+ goto err_ahbix_clk;
+ }
+ dev_dbg(dev, "set ahbix_clk rate to %lu\n",
+ clk_get_rate(drvdata->ahbix_clk));
+
+ ret = clk_prepare_enable(drvdata->ahbix_clk);
+ if (ret) {
+ dev_err(dev, "error enabling ahbix_clk: %d\n", ret);
+ goto err_ahbix_clk;
+ }
+
+ return 0;
+
+err_ahbix_clk:
+ clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks);
+ return ret;
+}
+
+static int apq8016_lpass_exit(struct platform_device *pdev)
+{
+ struct lpass_data *drvdata = platform_get_drvdata(pdev);
+
+ clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks);
+ clk_disable_unprepare(drvdata->ahbix_clk);
+
+ return 0;
+}
+
+
+static const struct lpass_variant apq8016_data = {
+ .i2sctrl_reg_base = 0x1000,
+ .i2sctrl_reg_stride = 0x1000,
+ .i2s_ports = 4,
+ .irq_reg_base = 0x6000,
+ .irq_reg_stride = 0x1000,
+ .irq_ports = 3,
+ .rdma_reg_base = 0x8400,
+ .rdma_reg_stride = 0x1000,
+ .rdma_channels = 2,
+ .dmactl_audif_start = 1,
+ .wrdma_reg_base = 0xB000,
+ .wrdma_reg_stride = 0x1000,
+ .wrdma_channel_start = 5,
+ .wrdma_channels = 2,
+ .loopback = REG_FIELD_ID(0x1000, 15, 15, 4, 0x1000),
+ .spken = REG_FIELD_ID(0x1000, 14, 14, 4, 0x1000),
+ .spkmode = REG_FIELD_ID(0x1000, 10, 13, 4, 0x1000),
+ .spkmono = REG_FIELD_ID(0x1000, 9, 9, 4, 0x1000),
+ .micen = REG_FIELD_ID(0x1000, 8, 8, 4, 0x1000),
+ .micmode = REG_FIELD_ID(0x1000, 4, 7, 4, 0x1000),
+ .micmono = REG_FIELD_ID(0x1000, 3, 3, 4, 0x1000),
+ .wssrc = REG_FIELD_ID(0x1000, 2, 2, 4, 0x1000),
+ .bitwidth = REG_FIELD_ID(0x1000, 0, 1, 4, 0x1000),
+
+ .rdma_dyncclk = REG_FIELD_ID(0x8400, 12, 12, 2, 0x1000),
+ .rdma_bursten = REG_FIELD_ID(0x8400, 11, 11, 2, 0x1000),
+ .rdma_wpscnt = REG_FIELD_ID(0x8400, 8, 10, 2, 0x1000),
+ .rdma_intf = REG_FIELD_ID(0x8400, 4, 7, 2, 0x1000),
+ .rdma_fifowm = REG_FIELD_ID(0x8400, 1, 3, 2, 0x1000),
+ .rdma_enable = REG_FIELD_ID(0x8400, 0, 0, 2, 0x1000),
+
+ .wrdma_dyncclk = REG_FIELD_ID(0xB000, 12, 12, 2, 0x1000),
+ .wrdma_bursten = REG_FIELD_ID(0xB000, 11, 11, 2, 0x1000),
+ .wrdma_wpscnt = REG_FIELD_ID(0xB000, 8, 10, 2, 0x1000),
+ .wrdma_intf = REG_FIELD_ID(0xB000, 4, 7, 2, 0x1000),
+ .wrdma_fifowm = REG_FIELD_ID(0xB000, 1, 3, 2, 0x1000),
+ .wrdma_enable = REG_FIELD_ID(0xB000, 0, 0, 2, 0x1000),
+
+ .clk_name = (const char*[]) {
+ "pcnoc-mport-clk",
+ "pcnoc-sway-clk",
+ },
+ .num_clks = 2,
+ .dai_driver = apq8016_lpass_cpu_dai_driver,
+ .num_dai = ARRAY_SIZE(apq8016_lpass_cpu_dai_driver),
+ .dai_osr_clk_names = (const char *[]) {
+ "mi2s-osr-clk0",
+ "mi2s-osr-clk1",
+ "mi2s-osr-clk2",
+ "mi2s-osr-clk3",
+ },
+ .dai_bit_clk_names = (const char *[]) {
+ "mi2s-bit-clk0",
+ "mi2s-bit-clk1",
+ "mi2s-bit-clk2",
+ "mi2s-bit-clk3",
+ },
+ .init = apq8016_lpass_init,
+ .exit = apq8016_lpass_exit,
+ .alloc_dma_channel = apq8016_lpass_alloc_dma_channel,
+ .free_dma_channel = apq8016_lpass_free_dma_channel,
+};
+
+static const struct of_device_id apq8016_lpass_cpu_device_id[] __maybe_unused = {
+ { .compatible = "qcom,lpass-cpu-apq8016", .data = &apq8016_data },
+ { .compatible = "qcom,apq8016-lpass-cpu", .data = &apq8016_data },
+ {}
+};
+MODULE_DEVICE_TABLE(of, apq8016_lpass_cpu_device_id);
+
+static struct platform_driver apq8016_lpass_cpu_platform_driver = {
+ .driver = {
+ .name = "apq8016-lpass-cpu",
+ .of_match_table = of_match_ptr(apq8016_lpass_cpu_device_id),
+ },
+ .probe = asoc_qcom_lpass_cpu_platform_probe,
+ .remove = asoc_qcom_lpass_cpu_platform_remove,
+};
+module_platform_driver(apq8016_lpass_cpu_platform_driver);
+
+MODULE_DESCRIPTION("APQ8016 LPASS CPU Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/qcom/lpass-cdc-dma.c b/sound/soc/qcom/lpass-cdc-dma.c
new file mode 100644
index 000000000..2dc8c75c4
--- /dev/null
+++ b/sound/soc/qcom/lpass-cdc-dma.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021 The Linux Foundation. All rights reserved.
+ *
+ * lpass-cdc-dma.c -- ALSA SoC CDC DMA CPU DAI driver for QTi LPASS
+ */
+
+#include <dt-bindings/sound/qcom,lpass.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/export.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "lpass-lpaif-reg.h"
+#include "lpass.h"
+
+#define CODEC_MEM_HZ_NORMAL 153600000
+
+enum codec_dma_interfaces {
+ LPASS_CDC_DMA_INTERFACE1 = 1,
+ LPASS_CDC_DMA_INTERFACE2,
+ LPASS_CDC_DMA_INTERFACE3,
+ LPASS_CDC_DMA_INTERFACE4,
+ LPASS_CDC_DMA_INTERFACE5,
+ LPASS_CDC_DMA_INTERFACE6,
+ LPASS_CDC_DMA_INTERFACE7,
+ LPASS_CDC_DMA_INTERFACE8,
+ LPASS_CDC_DMA_INTERFACE9,
+ LPASS_CDC_DMA_INTERFACE10,
+};
+
+static void __lpass_get_dmactl_handle(struct snd_pcm_substream *substream, struct snd_soc_dai *dai,
+ struct lpaif_dmactl **dmactl, int *id)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct lpass_pcm_data *pcm_data = rt->private_data;
+ const struct lpass_variant *v = drvdata->variant;
+ unsigned int dai_id = cpu_dai->driver->id;
+
+ switch (dai_id) {
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ *dmactl = drvdata->rxtx_rd_dmactl;
+ *id = pcm_data->dma_ch;
+ break;
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ *dmactl = drvdata->rxtx_wr_dmactl;
+ *id = pcm_data->dma_ch - v->rxtx_wrdma_channel_start;
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ *dmactl = drvdata->va_wr_dmactl;
+ *id = pcm_data->dma_ch - v->va_wrdma_channel_start;
+ break;
+ default:
+ dev_err(soc_runtime->dev, "invalid dai id for dma ctl: %d\n", dai_id);
+ break;
+ }
+}
+
+static int __lpass_get_codec_dma_intf_type(int dai_id)
+{
+ int ret;
+
+ switch (dai_id) {
+ case LPASS_CDC_DMA_RX0:
+ case LPASS_CDC_DMA_TX0:
+ case LPASS_CDC_DMA_VA_TX0:
+ ret = LPASS_CDC_DMA_INTERFACE1;
+ break;
+ case LPASS_CDC_DMA_RX1:
+ case LPASS_CDC_DMA_TX1:
+ case LPASS_CDC_DMA_VA_TX1:
+ ret = LPASS_CDC_DMA_INTERFACE2;
+ break;
+ case LPASS_CDC_DMA_RX2:
+ case LPASS_CDC_DMA_TX2:
+ case LPASS_CDC_DMA_VA_TX2:
+ ret = LPASS_CDC_DMA_INTERFACE3;
+ break;
+ case LPASS_CDC_DMA_RX3:
+ case LPASS_CDC_DMA_TX3:
+ case LPASS_CDC_DMA_VA_TX3:
+ ret = LPASS_CDC_DMA_INTERFACE4;
+ break;
+ case LPASS_CDC_DMA_RX4:
+ case LPASS_CDC_DMA_TX4:
+ case LPASS_CDC_DMA_VA_TX4:
+ ret = LPASS_CDC_DMA_INTERFACE5;
+ break;
+ case LPASS_CDC_DMA_RX5:
+ case LPASS_CDC_DMA_TX5:
+ case LPASS_CDC_DMA_VA_TX5:
+ ret = LPASS_CDC_DMA_INTERFACE6;
+ break;
+ case LPASS_CDC_DMA_RX6:
+ case LPASS_CDC_DMA_TX6:
+ case LPASS_CDC_DMA_VA_TX6:
+ ret = LPASS_CDC_DMA_INTERFACE7;
+ break;
+ case LPASS_CDC_DMA_RX7:
+ case LPASS_CDC_DMA_TX7:
+ case LPASS_CDC_DMA_VA_TX7:
+ ret = LPASS_CDC_DMA_INTERFACE8;
+ break;
+ case LPASS_CDC_DMA_RX8:
+ case LPASS_CDC_DMA_TX8:
+ case LPASS_CDC_DMA_VA_TX8:
+ ret = LPASS_CDC_DMA_INTERFACE9;
+ break;
+ case LPASS_CDC_DMA_RX9:
+ ret = LPASS_CDC_DMA_INTERFACE10;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int __lpass_platform_codec_intf_init(struct snd_soc_dai *dai,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ struct lpaif_dmactl *dmactl = NULL;
+ struct device *dev = soc_runtime->dev;
+ int ret, id, codec_intf;
+ unsigned int dai_id = cpu_dai->driver->id;
+
+ codec_intf = __lpass_get_codec_dma_intf_type(dai_id);
+ if (codec_intf < 0) {
+ dev_err(dev, "failed to get codec_intf: %d\n", codec_intf);
+ return codec_intf;
+ }
+
+ __lpass_get_dmactl_handle(substream, dai, &dmactl, &id);
+ if (!dmactl)
+ return -EINVAL;
+
+ ret = regmap_fields_write(dmactl->codec_intf, id, codec_intf);
+ if (ret) {
+ dev_err(dev, "error writing to dmactl codec_intf reg field: %d\n", ret);
+ return ret;
+ }
+ ret = regmap_fields_write(dmactl->codec_fs_sel, id, 0x0);
+ if (ret) {
+ dev_err(dev, "error writing to dmactl codec_fs_sel reg field: %d\n", ret);
+ return ret;
+ }
+ ret = regmap_fields_write(dmactl->codec_fs_delay, id, 0x0);
+ if (ret) {
+ dev_err(dev, "error writing to dmactl codec_fs_delay reg field: %d\n", ret);
+ return ret;
+ }
+ ret = regmap_fields_write(dmactl->codec_pack, id, 0x1);
+ if (ret) {
+ dev_err(dev, "error writing to dmactl codec_pack reg field: %d\n", ret);
+ return ret;
+ }
+ ret = regmap_fields_write(dmactl->codec_enable, id, LPAIF_DMACTL_ENABLE_ON);
+ if (ret) {
+ dev_err(dev, "error writing to dmactl codec_enable reg field: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int lpass_cdc_dma_daiops_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+
+ switch (dai->id) {
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ clk_set_rate(drvdata->codec_mem0, CODEC_MEM_HZ_NORMAL);
+ clk_prepare_enable(drvdata->codec_mem0);
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX0:
+ clk_set_rate(drvdata->va_mem0, CODEC_MEM_HZ_NORMAL);
+ clk_prepare_enable(drvdata->va_mem0);
+ break;
+ default:
+ dev_err(soc_runtime->dev, "%s: invalid interface: %d\n", __func__, dai->id);
+ break;
+ }
+ return 0;
+}
+
+static void lpass_cdc_dma_daiops_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+
+ switch (dai->id) {
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ clk_disable_unprepare(drvdata->codec_mem0);
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX0:
+ clk_disable_unprepare(drvdata->va_mem0);
+ break;
+ default:
+ dev_err(soc_runtime->dev, "%s: invalid interface: %d\n", __func__, dai->id);
+ break;
+ }
+}
+
+static int lpass_cdc_dma_daiops_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct lpaif_dmactl *dmactl = NULL;
+ unsigned int regval;
+ unsigned int channels = params_channels(params);
+ int ret;
+ int id;
+
+ switch (channels) {
+ case 1:
+ regval = LPASS_CDC_DMA_INTF_ONE_CHANNEL;
+ break;
+ case 2:
+ regval = LPASS_CDC_DMA_INTF_TWO_CHANNEL;
+ break;
+ case 4:
+ regval = LPASS_CDC_DMA_INTF_FOUR_CHANNEL;
+ break;
+ case 6:
+ regval = LPASS_CDC_DMA_INTF_SIX_CHANNEL;
+ break;
+ case 8:
+ regval = LPASS_CDC_DMA_INTF_EIGHT_CHANNEL;
+ break;
+ default:
+ dev_err(soc_runtime->dev, "invalid PCM config\n");
+ return -EINVAL;
+ }
+
+ __lpass_get_dmactl_handle(substream, dai, &dmactl, &id);
+ if (!dmactl)
+ return -EINVAL;
+
+ ret = regmap_fields_write(dmactl->codec_channel, id, regval);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error writing to dmactl codec_channel reg field: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int lpass_cdc_dma_daiops_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct lpaif_dmactl *dmactl = NULL;
+ int ret = 0, id;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ __lpass_platform_codec_intf_init(dai, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ __lpass_get_dmactl_handle(substream, dai, &dmactl, &id);
+ if (!dmactl)
+ return -EINVAL;
+
+ ret = regmap_fields_write(dmactl->codec_enable, id, LPAIF_DMACTL_ENABLE_OFF);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error writing to dmactl codec_enable reg: %d\n", ret);
+ return ret;
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ dev_err(soc_runtime->dev, "%s: invalid %d interface\n", __func__, cmd);
+ break;
+ }
+ return ret;
+}
+
+const struct snd_soc_dai_ops asoc_qcom_lpass_cdc_dma_dai_ops = {
+ .startup = lpass_cdc_dma_daiops_startup,
+ .shutdown = lpass_cdc_dma_daiops_shutdown,
+ .hw_params = lpass_cdc_dma_daiops_hw_params,
+ .trigger = lpass_cdc_dma_daiops_trigger,
+};
+EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cdc_dma_dai_ops);
+
+MODULE_DESCRIPTION("QTi LPASS CDC DMA Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c
new file mode 100644
index 000000000..242bc16da
--- /dev/null
+++ b/sound/soc/qcom/lpass-cpu.c
@@ -0,0 +1,1303 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
+ *
+ * lpass-cpu.c -- ALSA SoC CPU DAI driver for QTi LPASS
+ */
+
+#include <dt-bindings/sound/qcom,lpass.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include "lpass-lpaif-reg.h"
+#include "lpass.h"
+
+#define LPASS_CPU_MAX_MI2S_LINES 4
+#define LPASS_CPU_I2S_SD0_MASK BIT(0)
+#define LPASS_CPU_I2S_SD1_MASK BIT(1)
+#define LPASS_CPU_I2S_SD2_MASK BIT(2)
+#define LPASS_CPU_I2S_SD3_MASK BIT(3)
+#define LPASS_CPU_I2S_SD0_1_MASK GENMASK(1, 0)
+#define LPASS_CPU_I2S_SD2_3_MASK GENMASK(3, 2)
+#define LPASS_CPU_I2S_SD0_1_2_MASK GENMASK(2, 0)
+#define LPASS_CPU_I2S_SD0_1_2_3_MASK GENMASK(3, 0)
+#define LPASS_REG_READ 1
+#define LPASS_REG_WRITE 0
+
+/*
+ * Channel maps for Quad channel playbacks on MI2S Secondary
+ */
+static struct snd_pcm_chmap_elem lpass_quad_chmaps[] = {
+ { .channels = 4,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_RL,
+ SNDRV_CHMAP_FR, SNDRV_CHMAP_RR } },
+ { }
+};
+static int lpass_cpu_init_i2sctl_bitfields(struct device *dev,
+ struct lpaif_i2sctl *i2sctl, struct regmap *map)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+
+ i2sctl->loopback = devm_regmap_field_alloc(dev, map, v->loopback);
+ i2sctl->spken = devm_regmap_field_alloc(dev, map, v->spken);
+ i2sctl->spkmode = devm_regmap_field_alloc(dev, map, v->spkmode);
+ i2sctl->spkmono = devm_regmap_field_alloc(dev, map, v->spkmono);
+ i2sctl->micen = devm_regmap_field_alloc(dev, map, v->micen);
+ i2sctl->micmode = devm_regmap_field_alloc(dev, map, v->micmode);
+ i2sctl->micmono = devm_regmap_field_alloc(dev, map, v->micmono);
+ i2sctl->wssrc = devm_regmap_field_alloc(dev, map, v->wssrc);
+ i2sctl->bitwidth = devm_regmap_field_alloc(dev, map, v->bitwidth);
+
+ if (IS_ERR(i2sctl->loopback) || IS_ERR(i2sctl->spken) ||
+ IS_ERR(i2sctl->spkmode) || IS_ERR(i2sctl->spkmono) ||
+ IS_ERR(i2sctl->micen) || IS_ERR(i2sctl->micmode) ||
+ IS_ERR(i2sctl->micmono) || IS_ERR(i2sctl->wssrc) ||
+ IS_ERR(i2sctl->bitwidth))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int lpass_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+ int ret;
+
+ ret = clk_set_rate(drvdata->mi2s_osr_clk[dai->driver->id], freq);
+ if (ret)
+ dev_err(dai->dev, "error setting mi2s osrclk to %u: %d\n",
+ freq, ret);
+
+ return ret;
+}
+
+static int lpass_cpu_daiops_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+ int ret;
+
+ ret = clk_prepare_enable(drvdata->mi2s_osr_clk[dai->driver->id]);
+ if (ret) {
+ dev_err(dai->dev, "error in enabling mi2s osr clk: %d\n", ret);
+ return ret;
+ }
+ ret = clk_prepare(drvdata->mi2s_bit_clk[dai->driver->id]);
+ if (ret) {
+ dev_err(dai->dev, "error in enabling mi2s bit clk: %d\n", ret);
+ clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]);
+ return ret;
+ }
+ return 0;
+}
+
+static void lpass_cpu_daiops_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+ struct lpaif_i2sctl *i2sctl = drvdata->i2sctl;
+ unsigned int id = dai->driver->id;
+
+ clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]);
+ /*
+ * Ensure LRCLK is disabled even in device node validation.
+ * Will not impact if disabled in lpass_cpu_daiops_trigger()
+ * suspend.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ regmap_fields_write(i2sctl->spken, id, LPAIF_I2SCTL_SPKEN_DISABLE);
+ else
+ regmap_fields_write(i2sctl->micen, id, LPAIF_I2SCTL_MICEN_DISABLE);
+
+ /*
+ * BCLK may not be enabled if lpass_cpu_daiops_prepare is called before
+ * lpass_cpu_daiops_shutdown. It's paired with the clk_enable in
+ * lpass_cpu_daiops_prepare.
+ */
+ if (drvdata->mi2s_was_prepared[dai->driver->id]) {
+ drvdata->mi2s_was_prepared[dai->driver->id] = false;
+ clk_disable(drvdata->mi2s_bit_clk[dai->driver->id]);
+ }
+
+ clk_unprepare(drvdata->mi2s_bit_clk[dai->driver->id]);
+}
+
+static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+ struct lpaif_i2sctl *i2sctl = drvdata->i2sctl;
+ unsigned int id = dai->driver->id;
+ snd_pcm_format_t format = params_format(params);
+ unsigned int channels = params_channels(params);
+ unsigned int rate = params_rate(params);
+ unsigned int mode;
+ unsigned int regval;
+ int bitwidth, ret;
+
+ bitwidth = snd_pcm_format_width(format);
+ if (bitwidth < 0) {
+ dev_err(dai->dev, "invalid bit width given: %d\n", bitwidth);
+ return bitwidth;
+ }
+
+ ret = regmap_fields_write(i2sctl->loopback, id,
+ LPAIF_I2SCTL_LOOPBACK_DISABLE);
+ if (ret) {
+ dev_err(dai->dev, "error updating loopback field: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_fields_write(i2sctl->wssrc, id,
+ LPAIF_I2SCTL_WSSRC_INTERNAL);
+ if (ret) {
+ dev_err(dai->dev, "error updating wssrc field: %d\n", ret);
+ return ret;
+ }
+
+ switch (bitwidth) {
+ case 16:
+ regval = LPAIF_I2SCTL_BITWIDTH_16;
+ break;
+ case 24:
+ regval = LPAIF_I2SCTL_BITWIDTH_24;
+ break;
+ case 32:
+ regval = LPAIF_I2SCTL_BITWIDTH_32;
+ break;
+ default:
+ dev_err(dai->dev, "invalid bitwidth given: %d\n", bitwidth);
+ return -EINVAL;
+ }
+
+ ret = regmap_fields_write(i2sctl->bitwidth, id, regval);
+ if (ret) {
+ dev_err(dai->dev, "error updating bitwidth field: %d\n", ret);
+ return ret;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ mode = drvdata->mi2s_playback_sd_mode[id];
+ else
+ mode = drvdata->mi2s_capture_sd_mode[id];
+
+ if (!mode) {
+ dev_err(dai->dev, "no line is assigned\n");
+ return -EINVAL;
+ }
+
+ switch (channels) {
+ case 1:
+ case 2:
+ switch (mode) {
+ case LPAIF_I2SCTL_MODE_QUAD01:
+ case LPAIF_I2SCTL_MODE_6CH:
+ case LPAIF_I2SCTL_MODE_8CH:
+ mode = LPAIF_I2SCTL_MODE_SD0;
+ break;
+ case LPAIF_I2SCTL_MODE_QUAD23:
+ mode = LPAIF_I2SCTL_MODE_SD2;
+ break;
+ }
+
+ break;
+ case 4:
+ if (mode < LPAIF_I2SCTL_MODE_QUAD01) {
+ dev_err(dai->dev, "cannot configure 4 channels with mode %d\n",
+ mode);
+ return -EINVAL;
+ }
+
+ switch (mode) {
+ case LPAIF_I2SCTL_MODE_6CH:
+ case LPAIF_I2SCTL_MODE_8CH:
+ mode = LPAIF_I2SCTL_MODE_QUAD01;
+ break;
+ }
+ break;
+ case 6:
+ if (mode < LPAIF_I2SCTL_MODE_6CH) {
+ dev_err(dai->dev, "cannot configure 6 channels with mode %d\n",
+ mode);
+ return -EINVAL;
+ }
+
+ switch (mode) {
+ case LPAIF_I2SCTL_MODE_8CH:
+ mode = LPAIF_I2SCTL_MODE_6CH;
+ break;
+ }
+ break;
+ case 8:
+ if (mode < LPAIF_I2SCTL_MODE_8CH) {
+ dev_err(dai->dev, "cannot configure 8 channels with mode %d\n",
+ mode);
+ return -EINVAL;
+ }
+ break;
+ default:
+ dev_err(dai->dev, "invalid channels given: %u\n", channels);
+ return -EINVAL;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ret = regmap_fields_write(i2sctl->spkmode, id,
+ LPAIF_I2SCTL_SPKMODE(mode));
+ if (ret) {
+ dev_err(dai->dev, "error writing to i2sctl spkr mode: %d\n",
+ ret);
+ return ret;
+ }
+ if (channels >= 2)
+ ret = regmap_fields_write(i2sctl->spkmono, id,
+ LPAIF_I2SCTL_SPKMONO_STEREO);
+ else
+ ret = regmap_fields_write(i2sctl->spkmono, id,
+ LPAIF_I2SCTL_SPKMONO_MONO);
+ } else {
+ ret = regmap_fields_write(i2sctl->micmode, id,
+ LPAIF_I2SCTL_MICMODE(mode));
+ if (ret) {
+ dev_err(dai->dev, "error writing to i2sctl mic mode: %d\n",
+ ret);
+ return ret;
+ }
+ if (channels >= 2)
+ ret = regmap_fields_write(i2sctl->micmono, id,
+ LPAIF_I2SCTL_MICMONO_STEREO);
+ else
+ ret = regmap_fields_write(i2sctl->micmono, id,
+ LPAIF_I2SCTL_MICMONO_MONO);
+ }
+
+ if (ret) {
+ dev_err(dai->dev, "error writing to i2sctl channels mode: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = clk_set_rate(drvdata->mi2s_bit_clk[id],
+ rate * bitwidth * 2);
+ if (ret) {
+ dev_err(dai->dev, "error setting mi2s bitclk to %u: %d\n",
+ rate * bitwidth * 2, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lpass_cpu_daiops_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+ struct lpaif_i2sctl *i2sctl = drvdata->i2sctl;
+ unsigned int id = dai->driver->id;
+ int ret = -EINVAL;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ /*
+ * Ensure lpass BCLK/LRCLK is enabled during
+ * device resume as lpass_cpu_daiops_prepare() is not called
+ * after the device resumes. We don't check mi2s_was_prepared before
+ * enable/disable BCLK in trigger events because:
+ * 1. These trigger events are paired, so the BCLK
+ * enable_count is balanced.
+ * 2. the BCLK can be shared (ex: headset and headset mic),
+ * we need to increase the enable_count so that we don't
+ * turn off the shared BCLK while other devices are using
+ * it.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ret = regmap_fields_write(i2sctl->spken, id,
+ LPAIF_I2SCTL_SPKEN_ENABLE);
+ } else {
+ ret = regmap_fields_write(i2sctl->micen, id,
+ LPAIF_I2SCTL_MICEN_ENABLE);
+ }
+ if (ret)
+ dev_err(dai->dev, "error writing to i2sctl reg: %d\n",
+ ret);
+
+ ret = clk_enable(drvdata->mi2s_bit_clk[id]);
+ if (ret) {
+ dev_err(dai->dev, "error in enabling mi2s bit clk: %d\n", ret);
+ clk_disable(drvdata->mi2s_osr_clk[id]);
+ return ret;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ /*
+ * To ensure lpass BCLK/LRCLK is disabled during
+ * device suspend.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ret = regmap_fields_write(i2sctl->spken, id,
+ LPAIF_I2SCTL_SPKEN_DISABLE);
+ } else {
+ ret = regmap_fields_write(i2sctl->micen, id,
+ LPAIF_I2SCTL_MICEN_DISABLE);
+ }
+ if (ret)
+ dev_err(dai->dev, "error writing to i2sctl reg: %d\n",
+ ret);
+
+ clk_disable(drvdata->mi2s_bit_clk[dai->driver->id]);
+
+ break;
+ }
+
+ return ret;
+}
+
+static int lpass_cpu_daiops_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+ struct lpaif_i2sctl *i2sctl = drvdata->i2sctl;
+ unsigned int id = dai->driver->id;
+ int ret;
+
+ /*
+ * Ensure lpass BCLK/LRCLK is enabled bit before playback/capture
+ * data flow starts. This allows other codec to have some delay before
+ * the data flow.
+ * (ex: to drop start up pop noise before capture starts).
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = regmap_fields_write(i2sctl->spken, id, LPAIF_I2SCTL_SPKEN_ENABLE);
+ else
+ ret = regmap_fields_write(i2sctl->micen, id, LPAIF_I2SCTL_MICEN_ENABLE);
+
+ if (ret) {
+ dev_err(dai->dev, "error writing to i2sctl reg: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Check mi2s_was_prepared before enabling BCLK as lpass_cpu_daiops_prepare can
+ * be called multiple times. It's paired with the clk_disable in
+ * lpass_cpu_daiops_shutdown.
+ */
+ if (!drvdata->mi2s_was_prepared[dai->driver->id]) {
+ ret = clk_enable(drvdata->mi2s_bit_clk[id]);
+ if (ret) {
+ dev_err(dai->dev, "error in enabling mi2s bit clk: %d\n", ret);
+ return ret;
+ }
+ drvdata->mi2s_was_prepared[dai->driver->id] = true;
+ }
+ return 0;
+}
+
+static int lpass_cpu_daiops_pcm_new(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai)
+{
+ int ret;
+ struct snd_soc_dai_driver *drv = dai->driver;
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+
+ if (drvdata->mi2s_playback_sd_mode[dai->id] == LPAIF_I2SCTL_MODE_QUAD01) {
+ ret = snd_pcm_add_chmap_ctls(rtd->pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ lpass_quad_chmaps, drv->playback.channels_max, 0,
+ NULL);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lpass_cpu_daiops_probe(struct snd_soc_dai *dai)
+{
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+ int ret;
+
+ /* ensure audio hardware is disabled */
+ ret = regmap_write(drvdata->lpaif_map,
+ LPAIF_I2SCTL_REG(drvdata->variant, dai->driver->id), 0);
+ if (ret)
+ dev_err(dai->dev, "error writing to i2sctl reg: %d\n", ret);
+
+ return ret;
+}
+
+const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops = {
+ .probe = lpass_cpu_daiops_probe,
+ .set_sysclk = lpass_cpu_daiops_set_sysclk,
+ .startup = lpass_cpu_daiops_startup,
+ .shutdown = lpass_cpu_daiops_shutdown,
+ .hw_params = lpass_cpu_daiops_hw_params,
+ .trigger = lpass_cpu_daiops_trigger,
+ .prepare = lpass_cpu_daiops_prepare,
+};
+EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_ops);
+
+const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops2 = {
+ .pcm_new = lpass_cpu_daiops_pcm_new,
+ .probe = lpass_cpu_daiops_probe,
+ .set_sysclk = lpass_cpu_daiops_set_sysclk,
+ .startup = lpass_cpu_daiops_startup,
+ .shutdown = lpass_cpu_daiops_shutdown,
+ .hw_params = lpass_cpu_daiops_hw_params,
+ .trigger = lpass_cpu_daiops_trigger,
+ .prepare = lpass_cpu_daiops_prepare,
+};
+EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_ops2);
+
+static int asoc_qcom_of_xlate_dai_name(struct snd_soc_component *component,
+ const struct of_phandle_args *args,
+ const char **dai_name)
+{
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ const struct lpass_variant *variant = drvdata->variant;
+ int id = args->args[0];
+ int ret = -EINVAL;
+ int i;
+
+ for (i = 0; i < variant->num_dai; i++) {
+ if (variant->dai_driver[i].id == id) {
+ *dai_name = variant->dai_driver[i].name;
+ ret = 0;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_component_driver lpass_cpu_comp_driver = {
+ .name = "lpass-cpu",
+ .of_xlate_dai_name = asoc_qcom_of_xlate_dai_name,
+ .legacy_dai_naming = 1,
+};
+
+static bool lpass_cpu_regmap_writeable(struct device *dev, unsigned int reg)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ int i;
+
+ for (i = 0; i < v->i2s_ports; ++i)
+ if (reg == LPAIF_I2SCTL_REG(v, i))
+ return true;
+
+ for (i = 0; i < v->irq_ports; ++i) {
+ if (reg == LPAIF_IRQEN_REG(v, i))
+ return true;
+ if (reg == LPAIF_IRQCLEAR_REG(v, i))
+ return true;
+ }
+
+ for (i = 0; i < v->rdma_channels; ++i) {
+ if (reg == LPAIF_RDMACTL_REG(v, i))
+ return true;
+ if (reg == LPAIF_RDMABASE_REG(v, i))
+ return true;
+ if (reg == LPAIF_RDMABUFF_REG(v, i))
+ return true;
+ if (reg == LPAIF_RDMAPER_REG(v, i))
+ return true;
+ }
+
+ for (i = 0; i < v->wrdma_channels; ++i) {
+ if (reg == LPAIF_WRDMACTL_REG(v, i + v->wrdma_channel_start))
+ return true;
+ if (reg == LPAIF_WRDMABASE_REG(v, i + v->wrdma_channel_start))
+ return true;
+ if (reg == LPAIF_WRDMABUFF_REG(v, i + v->wrdma_channel_start))
+ return true;
+ if (reg == LPAIF_WRDMAPER_REG(v, i + v->wrdma_channel_start))
+ return true;
+ }
+
+ return false;
+}
+
+static bool lpass_cpu_regmap_readable(struct device *dev, unsigned int reg)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ int i;
+
+ for (i = 0; i < v->i2s_ports; ++i)
+ if (reg == LPAIF_I2SCTL_REG(v, i))
+ return true;
+
+ for (i = 0; i < v->irq_ports; ++i) {
+ if (reg == LPAIF_IRQCLEAR_REG(v, i))
+ return true;
+ if (reg == LPAIF_IRQEN_REG(v, i))
+ return true;
+ if (reg == LPAIF_IRQSTAT_REG(v, i))
+ return true;
+ }
+
+ for (i = 0; i < v->rdma_channels; ++i) {
+ if (reg == LPAIF_RDMACTL_REG(v, i))
+ return true;
+ if (reg == LPAIF_RDMABASE_REG(v, i))
+ return true;
+ if (reg == LPAIF_RDMABUFF_REG(v, i))
+ return true;
+ if (reg == LPAIF_RDMACURR_REG(v, i))
+ return true;
+ if (reg == LPAIF_RDMAPER_REG(v, i))
+ return true;
+ }
+
+ for (i = 0; i < v->wrdma_channels; ++i) {
+ if (reg == LPAIF_WRDMACTL_REG(v, i + v->wrdma_channel_start))
+ return true;
+ if (reg == LPAIF_WRDMABASE_REG(v, i + v->wrdma_channel_start))
+ return true;
+ if (reg == LPAIF_WRDMABUFF_REG(v, i + v->wrdma_channel_start))
+ return true;
+ if (reg == LPAIF_WRDMACURR_REG(v, i + v->wrdma_channel_start))
+ return true;
+ if (reg == LPAIF_WRDMAPER_REG(v, i + v->wrdma_channel_start))
+ return true;
+ }
+
+ return false;
+}
+
+static bool lpass_cpu_regmap_volatile(struct device *dev, unsigned int reg)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ int i;
+
+ for (i = 0; i < v->irq_ports; ++i) {
+ if (reg == LPAIF_IRQCLEAR_REG(v, i))
+ return true;
+ if (reg == LPAIF_IRQSTAT_REG(v, i))
+ return true;
+ }
+
+ for (i = 0; i < v->rdma_channels; ++i)
+ if (reg == LPAIF_RDMACURR_REG(v, i))
+ return true;
+
+ for (i = 0; i < v->wrdma_channels; ++i)
+ if (reg == LPAIF_WRDMACURR_REG(v, i + v->wrdma_channel_start))
+ return true;
+
+ return false;
+}
+
+static struct regmap_config lpass_cpu_regmap_config = {
+ .name = "lpass_cpu",
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .writeable_reg = lpass_cpu_regmap_writeable,
+ .readable_reg = lpass_cpu_regmap_readable,
+ .volatile_reg = lpass_cpu_regmap_volatile,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int lpass_hdmi_init_bitfields(struct device *dev, struct regmap *map)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ unsigned int i;
+ struct lpass_hdmi_tx_ctl *tx_ctl;
+ struct regmap_field *legacy_en;
+ struct lpass_vbit_ctrl *vbit_ctl;
+ struct regmap_field *tx_parity;
+ struct lpass_dp_metadata_ctl *meta_ctl;
+ struct lpass_sstream_ctl *sstream_ctl;
+ struct regmap_field *ch_msb;
+ struct regmap_field *ch_lsb;
+ struct lpass_hdmitx_dmactl *tx_dmactl;
+ int rval;
+
+ tx_ctl = devm_kzalloc(dev, sizeof(*tx_ctl), GFP_KERNEL);
+ if (!tx_ctl)
+ return -ENOMEM;
+
+ QCOM_REGMAP_FIELD_ALLOC(dev, map, v->soft_reset, tx_ctl->soft_reset);
+ QCOM_REGMAP_FIELD_ALLOC(dev, map, v->force_reset, tx_ctl->force_reset);
+ drvdata->tx_ctl = tx_ctl;
+
+ QCOM_REGMAP_FIELD_ALLOC(dev, map, v->legacy_en, legacy_en);
+ drvdata->hdmitx_legacy_en = legacy_en;
+
+ vbit_ctl = devm_kzalloc(dev, sizeof(*vbit_ctl), GFP_KERNEL);
+ if (!vbit_ctl)
+ return -ENOMEM;
+
+ QCOM_REGMAP_FIELD_ALLOC(dev, map, v->replace_vbit, vbit_ctl->replace_vbit);
+ QCOM_REGMAP_FIELD_ALLOC(dev, map, v->vbit_stream, vbit_ctl->vbit_stream);
+ drvdata->vbit_ctl = vbit_ctl;
+
+
+ QCOM_REGMAP_FIELD_ALLOC(dev, map, v->calc_en, tx_parity);
+ drvdata->hdmitx_parity_calc_en = tx_parity;
+
+ meta_ctl = devm_kzalloc(dev, sizeof(*meta_ctl), GFP_KERNEL);
+ if (!meta_ctl)
+ return -ENOMEM;
+
+ rval = devm_regmap_field_bulk_alloc(dev, map, &meta_ctl->mute, &v->mute, 7);
+ if (rval)
+ return rval;
+ drvdata->meta_ctl = meta_ctl;
+
+ sstream_ctl = devm_kzalloc(dev, sizeof(*sstream_ctl), GFP_KERNEL);
+ if (!sstream_ctl)
+ return -ENOMEM;
+
+ rval = devm_regmap_field_bulk_alloc(dev, map, &sstream_ctl->sstream_en, &v->sstream_en, 9);
+ if (rval)
+ return rval;
+
+ drvdata->sstream_ctl = sstream_ctl;
+
+ for (i = 0; i < LPASS_MAX_HDMI_DMA_CHANNELS; i++) {
+ QCOM_REGMAP_FIELD_ALLOC(dev, map, v->msb_bits, ch_msb);
+ drvdata->hdmitx_ch_msb[i] = ch_msb;
+
+ QCOM_REGMAP_FIELD_ALLOC(dev, map, v->lsb_bits, ch_lsb);
+ drvdata->hdmitx_ch_lsb[i] = ch_lsb;
+
+ tx_dmactl = devm_kzalloc(dev, sizeof(*tx_dmactl), GFP_KERNEL);
+ if (!tx_dmactl)
+ return -ENOMEM;
+
+ QCOM_REGMAP_FIELD_ALLOC(dev, map, v->use_hw_chs, tx_dmactl->use_hw_chs);
+ QCOM_REGMAP_FIELD_ALLOC(dev, map, v->use_hw_usr, tx_dmactl->use_hw_usr);
+ QCOM_REGMAP_FIELD_ALLOC(dev, map, v->hw_chs_sel, tx_dmactl->hw_chs_sel);
+ QCOM_REGMAP_FIELD_ALLOC(dev, map, v->hw_usr_sel, tx_dmactl->hw_usr_sel);
+ drvdata->hdmi_tx_dmactl[i] = tx_dmactl;
+ }
+ return 0;
+}
+
+static bool lpass_hdmi_regmap_writeable(struct device *dev, unsigned int reg)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ int i;
+
+ if (reg == LPASS_HDMI_TX_CTL_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMI_TX_LEGACY_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMI_TX_VBIT_CTL_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMI_TX_PARITY_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMI_TX_DP_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMI_TX_SSTREAM_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMITX_APP_IRQEN_REG(v))
+ return true;
+ if (reg == LPASS_HDMITX_APP_IRQCLEAR_REG(v))
+ return true;
+
+ for (i = 0; i < v->hdmi_rdma_channels; i++) {
+ if (reg == LPASS_HDMI_TX_CH_LSB_ADDR(v, i))
+ return true;
+ if (reg == LPASS_HDMI_TX_CH_MSB_ADDR(v, i))
+ return true;
+ if (reg == LPASS_HDMI_TX_DMA_ADDR(v, i))
+ return true;
+ }
+
+ for (i = 0; i < v->hdmi_rdma_channels; ++i) {
+ if (reg == LPAIF_HDMI_RDMACTL_REG(v, i))
+ return true;
+ if (reg == LPAIF_HDMI_RDMABASE_REG(v, i))
+ return true;
+ if (reg == LPAIF_HDMI_RDMABUFF_REG(v, i))
+ return true;
+ if (reg == LPAIF_HDMI_RDMAPER_REG(v, i))
+ return true;
+ }
+ return false;
+}
+
+static bool lpass_hdmi_regmap_readable(struct device *dev, unsigned int reg)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ int i;
+
+ if (reg == LPASS_HDMI_TX_CTL_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMI_TX_LEGACY_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMI_TX_VBIT_CTL_ADDR(v))
+ return true;
+
+ for (i = 0; i < v->hdmi_rdma_channels; i++) {
+ if (reg == LPASS_HDMI_TX_CH_LSB_ADDR(v, i))
+ return true;
+ if (reg == LPASS_HDMI_TX_CH_MSB_ADDR(v, i))
+ return true;
+ if (reg == LPASS_HDMI_TX_DMA_ADDR(v, i))
+ return true;
+ }
+
+ if (reg == LPASS_HDMI_TX_PARITY_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMI_TX_DP_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMI_TX_SSTREAM_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMITX_APP_IRQEN_REG(v))
+ return true;
+ if (reg == LPASS_HDMITX_APP_IRQSTAT_REG(v))
+ return true;
+
+ for (i = 0; i < v->hdmi_rdma_channels; ++i) {
+ if (reg == LPAIF_HDMI_RDMACTL_REG(v, i))
+ return true;
+ if (reg == LPAIF_HDMI_RDMABASE_REG(v, i))
+ return true;
+ if (reg == LPAIF_HDMI_RDMABUFF_REG(v, i))
+ return true;
+ if (reg == LPAIF_HDMI_RDMAPER_REG(v, i))
+ return true;
+ if (reg == LPAIF_HDMI_RDMACURR_REG(v, i))
+ return true;
+ }
+
+ return false;
+}
+
+static bool lpass_hdmi_regmap_volatile(struct device *dev, unsigned int reg)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ int i;
+
+ if (reg == LPASS_HDMITX_APP_IRQSTAT_REG(v))
+ return true;
+ if (reg == LPASS_HDMI_TX_LEGACY_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMI_TX_VBIT_CTL_ADDR(v))
+ return true;
+ if (reg == LPASS_HDMI_TX_PARITY_ADDR(v))
+ return true;
+
+ for (i = 0; i < v->hdmi_rdma_channels; ++i) {
+ if (reg == LPAIF_HDMI_RDMACURR_REG(v, i))
+ return true;
+ if (reg == LPASS_HDMI_TX_DMA_ADDR(v, i))
+ return true;
+ if (reg == LPASS_HDMI_TX_CH_LSB_ADDR(v, i))
+ return true;
+ if (reg == LPASS_HDMI_TX_CH_MSB_ADDR(v, i))
+ return true;
+ }
+ return false;
+}
+
+static struct regmap_config lpass_hdmi_regmap_config = {
+ .name = "lpass_hdmi",
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .writeable_reg = lpass_hdmi_regmap_writeable,
+ .readable_reg = lpass_hdmi_regmap_readable,
+ .volatile_reg = lpass_hdmi_regmap_volatile,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static bool __lpass_rxtx_regmap_accessible(struct device *dev, unsigned int reg, bool rw)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ int i;
+
+ for (i = 0; i < v->rxtx_irq_ports; ++i) {
+ if (reg == LPAIF_RXTX_IRQCLEAR_REG(v, i))
+ return true;
+ if (reg == LPAIF_RXTX_IRQEN_REG(v, i))
+ return true;
+ if (reg == LPAIF_RXTX_IRQSTAT_REG(v, i))
+ return true;
+ }
+
+ for (i = 0; i < v->rxtx_rdma_channels; ++i) {
+ if (reg == LPAIF_CDC_RXTX_RDMACTL_REG(v, i, LPASS_CDC_DMA_RX0))
+ return true;
+ if (reg == LPAIF_CDC_RXTX_RDMABASE_REG(v, i, LPASS_CDC_DMA_RX0))
+ return true;
+ if (reg == LPAIF_CDC_RXTX_RDMABUFF_REG(v, i, LPASS_CDC_DMA_RX0))
+ return true;
+ if (rw == LPASS_REG_READ) {
+ if (reg == LPAIF_CDC_RXTX_RDMACURR_REG(v, i, LPASS_CDC_DMA_RX0))
+ return true;
+ }
+ if (reg == LPAIF_CDC_RXTX_RDMAPER_REG(v, i, LPASS_CDC_DMA_RX0))
+ return true;
+ if (reg == LPAIF_CDC_RXTX_RDMA_INTF_REG(v, i, LPASS_CDC_DMA_RX0))
+ return true;
+ }
+
+ for (i = 0; i < v->rxtx_wrdma_channels; ++i) {
+ if (reg == LPAIF_CDC_RXTX_WRDMACTL_REG(v, i + v->rxtx_wrdma_channel_start,
+ LPASS_CDC_DMA_TX3))
+ return true;
+ if (reg == LPAIF_CDC_RXTX_WRDMABASE_REG(v, i + v->rxtx_wrdma_channel_start,
+ LPASS_CDC_DMA_TX3))
+ return true;
+ if (reg == LPAIF_CDC_RXTX_WRDMABUFF_REG(v, i + v->rxtx_wrdma_channel_start,
+ LPASS_CDC_DMA_TX3))
+ return true;
+ if (rw == LPASS_REG_READ) {
+ if (reg == LPAIF_CDC_RXTX_WRDMACURR_REG(v, i, LPASS_CDC_DMA_RX0))
+ return true;
+ }
+ if (reg == LPAIF_CDC_RXTX_WRDMAPER_REG(v, i + v->rxtx_wrdma_channel_start,
+ LPASS_CDC_DMA_TX3))
+ return true;
+ if (reg == LPAIF_CDC_RXTX_WRDMA_INTF_REG(v, i + v->rxtx_wrdma_channel_start,
+ LPASS_CDC_DMA_TX3))
+ return true;
+ }
+ return false;
+}
+
+static bool lpass_rxtx_regmap_writeable(struct device *dev, unsigned int reg)
+{
+ return __lpass_rxtx_regmap_accessible(dev, reg, LPASS_REG_WRITE);
+}
+
+static bool lpass_rxtx_regmap_readable(struct device *dev, unsigned int reg)
+{
+ return __lpass_rxtx_regmap_accessible(dev, reg, LPASS_REG_READ);
+}
+
+static bool lpass_rxtx_regmap_volatile(struct device *dev, unsigned int reg)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ int i;
+
+ for (i = 0; i < v->rxtx_irq_ports; ++i) {
+ if (reg == LPAIF_RXTX_IRQCLEAR_REG(v, i))
+ return true;
+ if (reg == LPAIF_RXTX_IRQSTAT_REG(v, i))
+ return true;
+ }
+
+ for (i = 0; i < v->rxtx_rdma_channels; ++i)
+ if (reg == LPAIF_CDC_RXTX_RDMACURR_REG(v, i, LPASS_CDC_DMA_RX0))
+ return true;
+
+ for (i = 0; i < v->rxtx_wrdma_channels; ++i)
+ if (reg == LPAIF_CDC_RXTX_WRDMACURR_REG(v, i + v->rxtx_wrdma_channel_start,
+ LPASS_CDC_DMA_TX3))
+ return true;
+
+ return false;
+}
+
+static bool __lpass_va_regmap_accessible(struct device *dev, unsigned int reg, bool rw)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ int i;
+
+ for (i = 0; i < v->va_irq_ports; ++i) {
+ if (reg == LPAIF_VA_IRQCLEAR_REG(v, i))
+ return true;
+ if (reg == LPAIF_VA_IRQEN_REG(v, i))
+ return true;
+ if (reg == LPAIF_VA_IRQSTAT_REG(v, i))
+ return true;
+ }
+
+ for (i = 0; i < v->va_wrdma_channels; ++i) {
+ if (reg == LPAIF_CDC_VA_WRDMACTL_REG(v, i + v->va_wrdma_channel_start,
+ LPASS_CDC_DMA_VA_TX0))
+ return true;
+ if (reg == LPAIF_CDC_VA_WRDMABASE_REG(v, i + v->va_wrdma_channel_start,
+ LPASS_CDC_DMA_VA_TX0))
+ return true;
+ if (reg == LPAIF_CDC_VA_WRDMABUFF_REG(v, i + v->va_wrdma_channel_start,
+ LPASS_CDC_DMA_VA_TX0))
+ return true;
+ if (rw == LPASS_REG_READ) {
+ if (reg == LPAIF_CDC_VA_WRDMACURR_REG(v, i + v->va_wrdma_channel_start,
+ LPASS_CDC_DMA_VA_TX0))
+ return true;
+ }
+ if (reg == LPAIF_CDC_VA_WRDMAPER_REG(v, i + v->va_wrdma_channel_start,
+ LPASS_CDC_DMA_VA_TX0))
+ return true;
+ if (reg == LPAIF_CDC_VA_WRDMA_INTF_REG(v, i + v->va_wrdma_channel_start,
+ LPASS_CDC_DMA_VA_TX0))
+ return true;
+ }
+ return false;
+}
+
+static bool lpass_va_regmap_writeable(struct device *dev, unsigned int reg)
+{
+ return __lpass_va_regmap_accessible(dev, reg, LPASS_REG_WRITE);
+}
+
+static bool lpass_va_regmap_readable(struct device *dev, unsigned int reg)
+{
+ return __lpass_va_regmap_accessible(dev, reg, LPASS_REG_READ);
+}
+
+static bool lpass_va_regmap_volatile(struct device *dev, unsigned int reg)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ int i;
+
+ for (i = 0; i < v->va_irq_ports; ++i) {
+ if (reg == LPAIF_VA_IRQCLEAR_REG(v, i))
+ return true;
+ if (reg == LPAIF_VA_IRQSTAT_REG(v, i))
+ return true;
+ }
+
+ for (i = 0; i < v->va_wrdma_channels; ++i) {
+ if (reg == LPAIF_CDC_VA_WRDMACURR_REG(v, i + v->va_wrdma_channel_start,
+ LPASS_CDC_DMA_VA_TX0))
+ return true;
+ }
+
+ return false;
+}
+
+static struct regmap_config lpass_rxtx_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .writeable_reg = lpass_rxtx_regmap_writeable,
+ .readable_reg = lpass_rxtx_regmap_readable,
+ .volatile_reg = lpass_rxtx_regmap_volatile,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static struct regmap_config lpass_va_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .writeable_reg = lpass_va_regmap_writeable,
+ .readable_reg = lpass_va_regmap_readable,
+ .volatile_reg = lpass_va_regmap_volatile,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static unsigned int of_lpass_cpu_parse_sd_lines(struct device *dev,
+ struct device_node *node,
+ const char *name)
+{
+ unsigned int lines[LPASS_CPU_MAX_MI2S_LINES];
+ unsigned int sd_line_mask = 0;
+ int num_lines, i;
+
+ num_lines = of_property_read_variable_u32_array(node, name, lines, 0,
+ LPASS_CPU_MAX_MI2S_LINES);
+ if (num_lines < 0)
+ return LPAIF_I2SCTL_MODE_NONE;
+
+ for (i = 0; i < num_lines; i++)
+ sd_line_mask |= BIT(lines[i]);
+
+ switch (sd_line_mask) {
+ case LPASS_CPU_I2S_SD0_MASK:
+ return LPAIF_I2SCTL_MODE_SD0;
+ case LPASS_CPU_I2S_SD1_MASK:
+ return LPAIF_I2SCTL_MODE_SD1;
+ case LPASS_CPU_I2S_SD2_MASK:
+ return LPAIF_I2SCTL_MODE_SD2;
+ case LPASS_CPU_I2S_SD3_MASK:
+ return LPAIF_I2SCTL_MODE_SD3;
+ case LPASS_CPU_I2S_SD0_1_MASK:
+ return LPAIF_I2SCTL_MODE_QUAD01;
+ case LPASS_CPU_I2S_SD2_3_MASK:
+ return LPAIF_I2SCTL_MODE_QUAD23;
+ case LPASS_CPU_I2S_SD0_1_2_MASK:
+ return LPAIF_I2SCTL_MODE_6CH;
+ case LPASS_CPU_I2S_SD0_1_2_3_MASK:
+ return LPAIF_I2SCTL_MODE_8CH;
+ default:
+ dev_err(dev, "Unsupported SD line mask: %#x\n", sd_line_mask);
+ return LPAIF_I2SCTL_MODE_NONE;
+ }
+}
+
+static void of_lpass_cpu_parse_dai_data(struct device *dev,
+ struct lpass_data *data)
+{
+ struct device_node *node;
+ int ret, i, id;
+
+ /* Allow all channels by default for backwards compatibility */
+ for (i = 0; i < data->variant->num_dai; i++) {
+ id = data->variant->dai_driver[i].id;
+ data->mi2s_playback_sd_mode[id] = LPAIF_I2SCTL_MODE_8CH;
+ data->mi2s_capture_sd_mode[id] = LPAIF_I2SCTL_MODE_8CH;
+ }
+
+ for_each_child_of_node(dev->of_node, node) {
+ ret = of_property_read_u32(node, "reg", &id);
+ if (ret || id < 0) {
+ dev_err(dev, "valid dai id not found: %d\n", ret);
+ continue;
+ }
+ if (id == LPASS_DP_RX) {
+ data->hdmi_port_enable = 1;
+ } else if (is_cdc_dma_port(id)) {
+ data->codec_dma_enable = 1;
+ } else {
+ data->mi2s_playback_sd_mode[id] =
+ of_lpass_cpu_parse_sd_lines(dev, node,
+ "qcom,playback-sd-lines");
+ data->mi2s_capture_sd_mode[id] =
+ of_lpass_cpu_parse_sd_lines(dev, node,
+ "qcom,capture-sd-lines");
+ }
+ }
+}
+
+static int of_lpass_cdc_dma_clks_parse(struct device *dev,
+ struct lpass_data *data)
+{
+ data->codec_mem0 = devm_clk_get(dev, "audio_cc_codec_mem0");
+ if (IS_ERR(data->codec_mem0))
+ return PTR_ERR(data->codec_mem0);
+
+ data->codec_mem1 = devm_clk_get(dev, "audio_cc_codec_mem1");
+ if (IS_ERR(data->codec_mem1))
+ return PTR_ERR(data->codec_mem1);
+
+ data->codec_mem2 = devm_clk_get(dev, "audio_cc_codec_mem2");
+ if (IS_ERR(data->codec_mem2))
+ return PTR_ERR(data->codec_mem2);
+
+ data->va_mem0 = devm_clk_get(dev, "aon_cc_va_mem0");
+ if (IS_ERR(data->va_mem0))
+ return PTR_ERR(data->va_mem0);
+
+ return 0;
+}
+
+int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev)
+{
+ struct lpass_data *drvdata;
+ struct device_node *dsp_of_node;
+ struct resource *res;
+ const struct lpass_variant *variant;
+ struct device *dev = &pdev->dev;
+ int ret, i, dai_id;
+
+ dsp_of_node = of_parse_phandle(pdev->dev.of_node, "qcom,adsp", 0);
+ if (dsp_of_node) {
+ dev_err(dev, "DSP exists and holds audio resources\n");
+ of_node_put(dsp_of_node);
+ return -EBUSY;
+ }
+
+ drvdata = devm_kzalloc(dev, sizeof(struct lpass_data), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, drvdata);
+
+ variant = device_get_match_data(dev);
+ if (!variant)
+ return -EINVAL;
+
+ if (of_device_is_compatible(dev->of_node, "qcom,lpass-cpu-apq8016"))
+ dev_warn(dev, "qcom,lpass-cpu-apq8016 compatible is deprecated\n");
+
+ drvdata->variant = variant;
+
+ of_lpass_cpu_parse_dai_data(dev, drvdata);
+
+ if (drvdata->codec_dma_enable) {
+ drvdata->rxtx_lpaif =
+ devm_platform_ioremap_resource_byname(pdev, "lpass-rxtx-lpaif");
+ if (IS_ERR(drvdata->rxtx_lpaif))
+ return PTR_ERR(drvdata->rxtx_lpaif);
+
+ drvdata->va_lpaif = devm_platform_ioremap_resource_byname(pdev, "lpass-va-lpaif");
+ if (IS_ERR(drvdata->va_lpaif))
+ return PTR_ERR(drvdata->va_lpaif);
+
+ lpass_rxtx_regmap_config.max_register = LPAIF_CDC_RXTX_WRDMAPER_REG(variant,
+ variant->rxtx_wrdma_channels +
+ variant->rxtx_wrdma_channel_start, LPASS_CDC_DMA_TX3);
+
+ drvdata->rxtx_lpaif_map = devm_regmap_init_mmio(dev, drvdata->rxtx_lpaif,
+ &lpass_rxtx_regmap_config);
+ if (IS_ERR(drvdata->rxtx_lpaif_map))
+ return PTR_ERR(drvdata->rxtx_lpaif_map);
+
+ lpass_va_regmap_config.max_register = LPAIF_CDC_VA_WRDMAPER_REG(variant,
+ variant->va_wrdma_channels +
+ variant->va_wrdma_channel_start, LPASS_CDC_DMA_VA_TX0);
+
+ drvdata->va_lpaif_map = devm_regmap_init_mmio(dev, drvdata->va_lpaif,
+ &lpass_va_regmap_config);
+ if (IS_ERR(drvdata->va_lpaif_map))
+ return PTR_ERR(drvdata->va_lpaif_map);
+
+ ret = of_lpass_cdc_dma_clks_parse(dev, drvdata);
+ if (ret) {
+ dev_err(dev, "failed to get cdc dma clocks %d\n", ret);
+ return ret;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-rxtx-cdc-dma-lpm");
+ if (!res)
+ return -EINVAL;
+ drvdata->rxtx_cdc_dma_lpm_buf = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-va-cdc-dma-lpm");
+ if (!res)
+ return -EINVAL;
+ drvdata->va_cdc_dma_lpm_buf = res->start;
+ }
+
+ drvdata->lpaif = devm_platform_ioremap_resource_byname(pdev, "lpass-lpaif");
+ if (IS_ERR(drvdata->lpaif))
+ return PTR_ERR(drvdata->lpaif);
+
+ lpass_cpu_regmap_config.max_register = LPAIF_WRDMAPER_REG(variant,
+ variant->wrdma_channels +
+ variant->wrdma_channel_start);
+
+ drvdata->lpaif_map = devm_regmap_init_mmio(dev, drvdata->lpaif,
+ &lpass_cpu_regmap_config);
+ if (IS_ERR(drvdata->lpaif_map)) {
+ dev_err(dev, "error initializing regmap: %ld\n",
+ PTR_ERR(drvdata->lpaif_map));
+ return PTR_ERR(drvdata->lpaif_map);
+ }
+
+ if (drvdata->hdmi_port_enable) {
+ drvdata->hdmiif = devm_platform_ioremap_resource_byname(pdev, "lpass-hdmiif");
+ if (IS_ERR(drvdata->hdmiif))
+ return PTR_ERR(drvdata->hdmiif);
+
+ lpass_hdmi_regmap_config.max_register = LPAIF_HDMI_RDMAPER_REG(variant,
+ variant->hdmi_rdma_channels - 1);
+ drvdata->hdmiif_map = devm_regmap_init_mmio(dev, drvdata->hdmiif,
+ &lpass_hdmi_regmap_config);
+ if (IS_ERR(drvdata->hdmiif_map)) {
+ dev_err(dev, "error initializing regmap: %ld\n",
+ PTR_ERR(drvdata->hdmiif_map));
+ return PTR_ERR(drvdata->hdmiif_map);
+ }
+ }
+
+ if (variant->init) {
+ ret = variant->init(pdev);
+ if (ret) {
+ dev_err(dev, "error initializing variant: %d\n", ret);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < variant->num_dai; i++) {
+ dai_id = variant->dai_driver[i].id;
+ if (dai_id == LPASS_DP_RX || is_cdc_dma_port(dai_id))
+ continue;
+
+ drvdata->mi2s_osr_clk[dai_id] = devm_clk_get_optional(dev,
+ variant->dai_osr_clk_names[i]);
+ drvdata->mi2s_bit_clk[dai_id] = devm_clk_get(dev,
+ variant->dai_bit_clk_names[i]);
+ if (IS_ERR(drvdata->mi2s_bit_clk[dai_id])) {
+ dev_err(dev,
+ "error getting %s: %ld\n",
+ variant->dai_bit_clk_names[i],
+ PTR_ERR(drvdata->mi2s_bit_clk[dai_id]));
+ return PTR_ERR(drvdata->mi2s_bit_clk[dai_id]);
+ }
+ if (drvdata->mi2s_playback_sd_mode[dai_id] ==
+ LPAIF_I2SCTL_MODE_QUAD01) {
+ variant->dai_driver[dai_id].playback.channels_min = 4;
+ variant->dai_driver[dai_id].playback.channels_max = 4;
+ }
+ }
+
+ /* Allocation for i2sctl regmap fields */
+ drvdata->i2sctl = devm_kzalloc(&pdev->dev, sizeof(struct lpaif_i2sctl),
+ GFP_KERNEL);
+ if (!drvdata->i2sctl)
+ return -ENOMEM;
+
+ /* Initialize bitfields for dai I2SCTL register */
+ ret = lpass_cpu_init_i2sctl_bitfields(dev, drvdata->i2sctl,
+ drvdata->lpaif_map);
+ if (ret) {
+ dev_err(dev, "error init i2sctl field: %d\n", ret);
+ return ret;
+ }
+
+ if (drvdata->hdmi_port_enable) {
+ ret = lpass_hdmi_init_bitfields(dev, drvdata->hdmiif_map);
+ if (ret) {
+ dev_err(dev, "%s error hdmi init failed\n", __func__);
+ return ret;
+ }
+ }
+ ret = devm_snd_soc_register_component(dev,
+ &lpass_cpu_comp_driver,
+ variant->dai_driver,
+ variant->num_dai);
+ if (ret) {
+ dev_err(dev, "error registering cpu driver: %d\n", ret);
+ goto err;
+ }
+
+ ret = asoc_qcom_lpass_platform_register(pdev);
+ if (ret) {
+ dev_err(dev, "error registering platform driver: %d\n", ret);
+ goto err;
+ }
+
+err:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_probe);
+
+void asoc_qcom_lpass_cpu_platform_remove(struct platform_device *pdev)
+{
+ struct lpass_data *drvdata = platform_get_drvdata(pdev);
+
+ if (drvdata->variant->exit)
+ drvdata->variant->exit(pdev);
+}
+EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_remove);
+
+void asoc_qcom_lpass_cpu_platform_shutdown(struct platform_device *pdev)
+{
+ struct lpass_data *drvdata = platform_get_drvdata(pdev);
+
+ if (drvdata->variant->exit)
+ drvdata->variant->exit(pdev);
+
+}
+EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_shutdown);
+
+MODULE_DESCRIPTION("QTi LPASS CPU Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/lpass-hdmi.c b/sound/soc/qcom/lpass-hdmi.c
new file mode 100644
index 000000000..6d9795306
--- /dev/null
+++ b/sound/soc/qcom/lpass-hdmi.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ *
+ * lpass-hdmi.c -- ALSA SoC HDMI-CPU DAI driver for QTi LPASS HDMI
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <sound/pcm_params.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <dt-bindings/sound/sc7180-lpass.h>
+#include "lpass-lpaif-reg.h"
+#include "lpass.h"
+
+static int lpass_hdmi_daiops_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+ snd_pcm_format_t format = params_format(params);
+ unsigned int rate = params_rate(params);
+ unsigned int channels = params_channels(params);
+ int bitwidth;
+ unsigned int word_length;
+ unsigned int ch_sts_buf0;
+ unsigned int ch_sts_buf1;
+ unsigned int data_format;
+ unsigned int sampling_freq;
+ unsigned int ch = 0;
+ struct lpass_dp_metadata_ctl *meta_ctl = drvdata->meta_ctl;
+ struct lpass_sstream_ctl *sstream_ctl = drvdata->sstream_ctl;
+ int ret;
+
+ bitwidth = snd_pcm_format_width(format);
+ if (bitwidth < 0) {
+ dev_err(dai->dev, "%s invalid bit width given : %d\n",
+ __func__, bitwidth);
+ return bitwidth;
+ }
+
+ switch (bitwidth) {
+ case 16:
+ word_length = LPASS_DP_AUDIO_BITWIDTH16;
+ break;
+ case 24:
+ word_length = LPASS_DP_AUDIO_BITWIDTH24;
+ break;
+ default:
+ dev_err(dai->dev, "%s invalid bit width given : %d\n",
+ __func__, bitwidth);
+ return -EINVAL;
+ }
+
+ switch (rate) {
+ case 32000:
+ sampling_freq = LPASS_SAMPLING_FREQ32;
+ break;
+ case 44100:
+ sampling_freq = LPASS_SAMPLING_FREQ44;
+ break;
+ case 48000:
+ sampling_freq = LPASS_SAMPLING_FREQ48;
+ break;
+ default:
+ dev_err(dai->dev, "%s invalid bit width given : %d\n",
+ __func__, bitwidth);
+ return -EINVAL;
+ }
+ data_format = LPASS_DATA_FORMAT_LINEAR;
+ ch_sts_buf0 = (((data_format << LPASS_DATA_FORMAT_SHIFT) & LPASS_DATA_FORMAT_MASK)
+ | ((sampling_freq << LPASS_FREQ_BIT_SHIFT) & LPASS_FREQ_BIT_MASK));
+ ch_sts_buf1 = (word_length) & LPASS_WORDLENGTH_MASK;
+
+ ret = regmap_field_write(drvdata->tx_ctl->soft_reset, LPASS_TX_CTL_RESET);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(drvdata->tx_ctl->soft_reset, LPASS_TX_CTL_CLEAR);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(drvdata->hdmitx_legacy_en, LPASS_HDMITX_LEGACY_DISABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(drvdata->hdmitx_parity_calc_en, HDMITX_PARITY_CALC_EN);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(drvdata->vbit_ctl->replace_vbit, REPLACE_VBIT);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(drvdata->vbit_ctl->vbit_stream, LINEAR_PCM_DATA);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(drvdata->hdmitx_ch_msb[0], ch_sts_buf1);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(drvdata->hdmitx_ch_lsb[0], ch_sts_buf0);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(drvdata->hdmi_tx_dmactl[0]->use_hw_chs, HW_MODE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(drvdata->hdmi_tx_dmactl[0]->hw_chs_sel, SW_MODE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(drvdata->hdmi_tx_dmactl[0]->use_hw_usr, HW_MODE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(drvdata->hdmi_tx_dmactl[0]->hw_usr_sel, SW_MODE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(meta_ctl->mute, LPASS_MUTE_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(meta_ctl->as_sdp_cc, channels - 1);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(meta_ctl->as_sdp_ct, LPASS_META_DEFAULT_VAL);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(meta_ctl->aif_db4, LPASS_META_DEFAULT_VAL);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(meta_ctl->frequency, sampling_freq);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(meta_ctl->mst_index, LPASS_META_DEFAULT_VAL);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(meta_ctl->dptx_index, LPASS_META_DEFAULT_VAL);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(sstream_ctl->sstream_en, LPASS_SSTREAM_DISABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(sstream_ctl->dma_sel, ch);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(sstream_ctl->auto_bbit_en, LPASS_SSTREAM_DEFAULT_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(sstream_ctl->layout, LPASS_SSTREAM_DEFAULT_DISABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(sstream_ctl->layout_sp, LPASS_LAYOUT_SP_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(sstream_ctl->dp_audio, LPASS_SSTREAM_DEFAULT_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(sstream_ctl->set_sp_on_en, LPASS_SSTREAM_DEFAULT_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(sstream_ctl->dp_sp_b_hw_en, LPASS_SSTREAM_DEFAULT_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(sstream_ctl->dp_staffing_en, LPASS_SSTREAM_DEFAULT_ENABLE);
+
+ return ret;
+}
+
+static int lpass_hdmi_daiops_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+
+ ret = regmap_field_write(drvdata->sstream_ctl->sstream_en, LPASS_SSTREAM_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(drvdata->meta_ctl->mute, LPASS_MUTE_DISABLE);
+
+ return ret;
+}
+
+static int lpass_hdmi_daiops_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
+ struct lpass_dp_metadata_ctl *meta_ctl = drvdata->meta_ctl;
+ struct lpass_sstream_ctl *sstream_ctl = drvdata->sstream_ctl;
+ int ret = -EINVAL;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = regmap_field_write(sstream_ctl->sstream_en, LPASS_SSTREAM_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(meta_ctl->mute, LPASS_MUTE_DISABLE);
+ if (ret)
+ return ret;
+
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = regmap_field_write(sstream_ctl->sstream_en, LPASS_SSTREAM_DISABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(meta_ctl->mute, LPASS_MUTE_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(sstream_ctl->dp_audio, 0);
+ if (ret)
+ return ret;
+
+ break;
+ }
+ return ret;
+}
+
+const struct snd_soc_dai_ops asoc_qcom_lpass_hdmi_dai_ops = {
+ .hw_params = lpass_hdmi_daiops_hw_params,
+ .prepare = lpass_hdmi_daiops_prepare,
+ .trigger = lpass_hdmi_daiops_trigger,
+};
+EXPORT_SYMBOL_GPL(asoc_qcom_lpass_hdmi_dai_ops);
+
+MODULE_DESCRIPTION("QTi LPASS HDMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/lpass-hdmi.h b/sound/soc/qcom/lpass-hdmi.h
new file mode 100644
index 000000000..ee74d7830
--- /dev/null
+++ b/sound/soc/qcom/lpass-hdmi.h
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ *
+ * lpass_hdmi.h - Definitions for the QTi LPASS HDMI
+ */
+
+#ifndef __LPASS_HDMI_H__
+#define __LPASS_HDMI_H__
+
+#include <linux/regmap.h>
+
+#define LPASS_HDMITX_LEGACY_DISABLE 0x0
+#define LPASS_HDMITX_LEGACY_ENABLE 0x1
+#define LPASS_DP_AUDIO_BITWIDTH16 0x0
+#define LPASS_DP_AUDIO_BITWIDTH24 0xb
+#define LPASS_DATA_FORMAT_SHIFT 0x1
+#define LPASS_FREQ_BIT_SHIFT 24
+#define LPASS_DATA_FORMAT_LINEAR 0x0
+#define LPASS_DATA_FORMAT_NON_LINEAR 0x1
+#define LPASS_SAMPLING_FREQ32 0x3
+#define LPASS_SAMPLING_FREQ44 0x0
+#define LPASS_SAMPLING_FREQ48 0x2
+#define LPASS_TX_CTL_RESET 0x1
+#define LPASS_TX_CTL_CLEAR 0x0
+#define LPASS_SSTREAM_ENABLE 1
+#define LPASS_SSTREAM_DISABLE 0
+#define LPASS_LAYOUT_SP_DEFAULT 0xf
+#define LPASS_SSTREAM_DEFAULT_ENABLE 1
+#define LPASS_SSTREAM_DEFAULT_DISABLE 0
+#define LPASS_MUTE_ENABLE 1
+#define LPASS_MUTE_DISABLE 0
+#define LPASS_META_DEFAULT_VAL 0
+#define HW_MODE 1
+#define SW_MODE 0
+#define LEGACY_LPASS_LPAIF 1
+#define LEGACY_LPASS_HDMI 0
+#define REPLACE_VBIT 0x1
+#define LINEAR_PCM_DATA 0x0
+#define NON_LINEAR_PCM_DATA 0x1
+#define HDMITX_PARITY_CALC_EN 0x1
+#define HDMITX_PARITY_CALC_DIS 0x0
+#define LPASS_DATA_FORMAT_MASK GENMASK(1, 1)
+#define LPASS_WORDLENGTH_MASK GENMASK(3, 0)
+#define LPASS_FREQ_BIT_MASK GENMASK(27, 24)
+
+#define LPASS_HDMI_TX_CTL_ADDR(v) (v->hdmi_tx_ctl_addr)
+#define LPASS_HDMI_TX_LEGACY_ADDR(v) (v->hdmi_legacy_addr)
+#define LPASS_HDMI_TX_VBIT_CTL_ADDR(v) (v->hdmi_vbit_addr)
+#define LPASS_HDMI_TX_PARITY_ADDR(v) (v->hdmi_parity_addr)
+#define LPASS_HDMI_TX_DP_ADDR(v) (v->hdmi_DP_addr)
+#define LPASS_HDMI_TX_SSTREAM_ADDR(v) (v->hdmi_sstream_addr)
+
+#define LPASS_HDMI_TX_CH_LSB_ADDR(v, port) \
+ (v->hdmi_ch_lsb_addr + v->ch_stride * (port))
+#define LPASS_HDMI_TX_CH_MSB_ADDR(v, port) \
+ (v->hdmi_ch_msb_addr + v->ch_stride * (port))
+#define LPASS_HDMI_TX_DMA_ADDR(v, port) \
+ (v->hdmi_dmactl_addr + v->hdmi_dma_stride * (port))
+
+struct lpass_sstream_ctl {
+ struct regmap_field *sstream_en;
+ struct regmap_field *dma_sel;
+ struct regmap_field *auto_bbit_en;
+ struct regmap_field *layout;
+ struct regmap_field *layout_sp;
+ struct regmap_field *set_sp_on_en;
+ struct regmap_field *dp_audio;
+ struct regmap_field *dp_staffing_en;
+ struct regmap_field *dp_sp_b_hw_en;
+};
+
+struct lpass_dp_metadata_ctl {
+ struct regmap_field *mute;
+ struct regmap_field *as_sdp_cc;
+ struct regmap_field *as_sdp_ct;
+ struct regmap_field *aif_db4;
+ struct regmap_field *frequency;
+ struct regmap_field *mst_index;
+ struct regmap_field *dptx_index;
+};
+
+struct lpass_hdmi_tx_ctl {
+ struct regmap_field *soft_reset;
+ struct regmap_field *force_reset;
+};
+
+struct lpass_hdmitx_dmactl {
+ struct regmap_field *use_hw_chs;
+ struct regmap_field *use_hw_usr;
+ struct regmap_field *hw_chs_sel;
+ struct regmap_field *hw_usr_sel;
+};
+
+struct lpass_vbit_ctrl {
+ struct regmap_field *replace_vbit;
+ struct regmap_field *vbit_stream;
+};
+
+extern const struct snd_soc_dai_ops asoc_qcom_lpass_hdmi_dai_ops;
+
+#endif /* __LPASS_HDMI_H__ */
diff --git a/sound/soc/qcom/lpass-ipq806x.c b/sound/soc/qcom/lpass-ipq806x.c
new file mode 100644
index 000000000..e57d29ea4
--- /dev/null
+++ b/sound/soc/qcom/lpass-ipq806x.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
+ *
+ * lpass-ipq806x.c -- ALSA SoC CPU DAI driver for QTi LPASS
+ * Splited out the IPQ8064 soc specific from lpass-cpu.c
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "lpass-lpaif-reg.h"
+#include "lpass.h"
+
+enum lpaif_i2s_ports {
+ IPQ806X_LPAIF_I2S_PORT_CODEC_SPK,
+ IPQ806X_LPAIF_I2S_PORT_CODEC_MIC,
+ IPQ806X_LPAIF_I2S_PORT_SEC_SPK,
+ IPQ806X_LPAIF_I2S_PORT_SEC_MIC,
+ IPQ806X_LPAIF_I2S_PORT_MI2S,
+};
+
+enum lpaif_dma_channels {
+ IPQ806X_LPAIF_RDMA_CHAN_MI2S,
+ IPQ806X_LPAIF_RDMA_CHAN_PCM0,
+ IPQ806X_LPAIF_RDMA_CHAN_PCM1,
+};
+
+static struct snd_soc_dai_driver ipq806x_lpass_cpu_dai_driver = {
+ .id = IPQ806X_LPAIF_I2S_PORT_MI2S,
+ .playback = {
+ .stream_name = "lpass-cpu-playback",
+ .formats = SNDRV_PCM_FMTBIT_S16 |
+ SNDRV_PCM_FMTBIT_S24 |
+ SNDRV_PCM_FMTBIT_S32,
+ .rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .ops = &asoc_qcom_lpass_cpu_dai_ops,
+};
+
+static int ipq806x_lpass_init(struct platform_device *pdev)
+{
+ struct lpass_data *drvdata = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ drvdata->ahbix_clk = devm_clk_get(dev, "ahbix-clk");
+ if (IS_ERR(drvdata->ahbix_clk)) {
+ dev_err(dev, "error getting ahbix-clk: %ld\n",
+ PTR_ERR(drvdata->ahbix_clk));
+ ret = PTR_ERR(drvdata->ahbix_clk);
+ goto err_ahbix_clk;
+ }
+
+ ret = clk_set_rate(drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY);
+ if (ret) {
+ dev_err(dev, "error setting rate on ahbix_clk: %d\n", ret);
+ goto err_ahbix_clk;
+ }
+ dev_dbg(dev, "set ahbix_clk rate to %lu\n",
+ clk_get_rate(drvdata->ahbix_clk));
+
+ ret = clk_prepare_enable(drvdata->ahbix_clk);
+ if (ret) {
+ dev_err(dev, "error enabling ahbix_clk: %d\n", ret);
+ goto err_ahbix_clk;
+ }
+
+err_ahbix_clk:
+ return ret;
+}
+
+static int ipq806x_lpass_exit(struct platform_device *pdev)
+{
+ struct lpass_data *drvdata = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(drvdata->ahbix_clk);
+
+ return 0;
+}
+
+static int ipq806x_lpass_alloc_dma_channel(struct lpass_data *drvdata, int dir, unsigned int dai_id)
+{
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ return IPQ806X_LPAIF_RDMA_CHAN_MI2S;
+ else /* Capture currently not implemented */
+ return -EINVAL;
+}
+
+static int ipq806x_lpass_free_dma_channel(struct lpass_data *drvdata, int chan, unsigned int dai_id)
+{
+ return 0;
+}
+
+static const struct lpass_variant ipq806x_data = {
+ .i2sctrl_reg_base = 0x0010,
+ .i2sctrl_reg_stride = 0x04,
+ .i2s_ports = 5,
+ .irq_reg_base = 0x3000,
+ .irq_reg_stride = 0x1000,
+ .irq_ports = 3,
+ .rdma_reg_base = 0x6000,
+ .rdma_reg_stride = 0x1000,
+ .rdma_channels = 4,
+ .wrdma_reg_base = 0xB000,
+ .wrdma_reg_stride = 0x1000,
+ .wrdma_channel_start = 5,
+ .wrdma_channels = 4,
+ .loopback = REG_FIELD_ID(0x0010, 15, 15, 5, 0x4),
+ .spken = REG_FIELD_ID(0x0010, 14, 14, 5, 0x4),
+ .spkmode = REG_FIELD_ID(0x0010, 10, 13, 5, 0x4),
+ .spkmono = REG_FIELD_ID(0x0010, 9, 9, 5, 0x4),
+ .micen = REG_FIELD_ID(0x0010, 8, 8, 5, 0x4),
+ .micmode = REG_FIELD_ID(0x0010, 4, 7, 5, 0x4),
+ .micmono = REG_FIELD_ID(0x0010, 3, 3, 5, 0x4),
+ .wssrc = REG_FIELD_ID(0x0010, 2, 2, 5, 0x4),
+ .bitwidth = REG_FIELD_ID(0x0010, 0, 1, 5, 0x4),
+
+ .rdma_dyncclk = REG_FIELD_ID(0x6000, 12, 12, 4, 0x1000),
+ .rdma_bursten = REG_FIELD_ID(0x6000, 11, 11, 4, 0x1000),
+ .rdma_wpscnt = REG_FIELD_ID(0x6000, 8, 10, 4, 0x1000),
+ .rdma_intf = REG_FIELD_ID(0x6000, 4, 7, 4, 0x1000),
+ .rdma_fifowm = REG_FIELD_ID(0x6000, 1, 3, 4, 0x1000),
+ .rdma_enable = REG_FIELD_ID(0x6000, 0, 0, 4, 0x1000),
+
+ .wrdma_dyncclk = REG_FIELD_ID(0xB000, 12, 12, 4, 0x1000),
+ .wrdma_bursten = REG_FIELD_ID(0xB000, 11, 11, 4, 0x1000),
+ .wrdma_wpscnt = REG_FIELD_ID(0xB000, 8, 10, 4, 0x1000),
+ .wrdma_intf = REG_FIELD_ID(0xB000, 4, 7, 4, 0x1000),
+ .wrdma_fifowm = REG_FIELD_ID(0xB000, 1, 3, 4, 0x1000),
+ .wrdma_enable = REG_FIELD_ID(0xB000, 0, 0, 4, 0x1000),
+
+ .dai_driver = &ipq806x_lpass_cpu_dai_driver,
+ .num_dai = 1,
+ .dai_osr_clk_names = (const char *[]) {
+ "mi2s-osr-clk",
+ },
+ .dai_bit_clk_names = (const char *[]) {
+ "mi2s-bit-clk",
+ },
+ .init = ipq806x_lpass_init,
+ .exit = ipq806x_lpass_exit,
+ .alloc_dma_channel = ipq806x_lpass_alloc_dma_channel,
+ .free_dma_channel = ipq806x_lpass_free_dma_channel,
+};
+
+static const struct of_device_id ipq806x_lpass_cpu_device_id[] __maybe_unused = {
+ { .compatible = "qcom,lpass-cpu", .data = &ipq806x_data },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ipq806x_lpass_cpu_device_id);
+
+static struct platform_driver ipq806x_lpass_cpu_platform_driver = {
+ .driver = {
+ .name = "lpass-cpu",
+ .of_match_table = of_match_ptr(ipq806x_lpass_cpu_device_id),
+ },
+ .probe = asoc_qcom_lpass_cpu_platform_probe,
+ .remove = asoc_qcom_lpass_cpu_platform_remove,
+};
+module_platform_driver(ipq806x_lpass_cpu_platform_driver);
+
+MODULE_DESCRIPTION("QTi LPASS CPU Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/lpass-lpaif-reg.h b/sound/soc/qcom/lpass-lpaif-reg.h
new file mode 100644
index 000000000..6d9d9d1f6
--- /dev/null
+++ b/sound/soc/qcom/lpass-lpaif-reg.h
@@ -0,0 +1,319 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
+ */
+
+#ifndef __LPASS_LPAIF_REG_H__
+#define __LPASS_LPAIF_REG_H__
+
+/* LPAIF I2S */
+
+#define LPAIF_I2SCTL_REG_ADDR(v, addr, port) \
+ (v->i2sctrl_reg_base + (addr) + v->i2sctrl_reg_stride * (port))
+
+#define LPAIF_I2SCTL_REG(v, port) LPAIF_I2SCTL_REG_ADDR(v, 0x0, (port))
+
+#define LPAIF_I2SCTL_LOOPBACK_DISABLE 0
+#define LPAIF_I2SCTL_LOOPBACK_ENABLE 1
+
+#define LPAIF_I2SCTL_SPKEN_DISABLE 0
+#define LPAIF_I2SCTL_SPKEN_ENABLE 1
+
+#define LPAIF_I2SCTL_MODE_NONE 0
+#define LPAIF_I2SCTL_MODE_SD0 1
+#define LPAIF_I2SCTL_MODE_SD1 2
+#define LPAIF_I2SCTL_MODE_SD2 3
+#define LPAIF_I2SCTL_MODE_SD3 4
+#define LPAIF_I2SCTL_MODE_QUAD01 5
+#define LPAIF_I2SCTL_MODE_QUAD23 6
+#define LPAIF_I2SCTL_MODE_6CH 7
+#define LPAIF_I2SCTL_MODE_8CH 8
+#define LPAIF_I2SCTL_MODE_10CH 9
+#define LPAIF_I2SCTL_MODE_12CH 10
+#define LPAIF_I2SCTL_MODE_14CH 11
+#define LPAIF_I2SCTL_MODE_16CH 12
+#define LPAIF_I2SCTL_MODE_SD4 13
+#define LPAIF_I2SCTL_MODE_SD5 14
+#define LPAIF_I2SCTL_MODE_SD6 15
+#define LPAIF_I2SCTL_MODE_SD7 16
+#define LPAIF_I2SCTL_MODE_QUAD45 17
+#define LPAIF_I2SCTL_MODE_QUAD47 18
+#define LPAIF_I2SCTL_MODE_8CH_2 19
+
+#define LPAIF_I2SCTL_SPKMODE(mode) mode
+
+#define LPAIF_I2SCTL_SPKMONO_STEREO 0
+#define LPAIF_I2SCTL_SPKMONO_MONO 1
+
+#define LPAIF_I2SCTL_MICEN_DISABLE 0
+#define LPAIF_I2SCTL_MICEN_ENABLE 1
+
+#define LPAIF_I2SCTL_MICMODE(mode) mode
+
+#define LPAIF_I2SCTL_MICMONO_STEREO 0
+#define LPAIF_I2SCTL_MICMONO_MONO 1
+
+#define LPAIF_I2SCTL_WSSRC_INTERNAL 0
+#define LPAIF_I2SCTL_WSSRC_EXTERNAL 1
+
+#define LPAIF_I2SCTL_BITWIDTH_16 0
+#define LPAIF_I2SCTL_BITWIDTH_24 1
+#define LPAIF_I2SCTL_BITWIDTH_32 2
+
+#define LPAIF_I2SCTL_RESET_STATE 0x003C0004
+#define LPAIF_DMACTL_RESET_STATE 0x00200000
+
+
+/* LPAIF IRQ */
+#define LPAIF_IRQ_REG_ADDR(v, addr, port) \
+ (v->irq_reg_base + (addr) + v->irq_reg_stride * (port))
+
+#define LPAIF_IRQ_PORT_HOST 0
+
+#define LPAIF_IRQEN_REG(v, port) LPAIF_IRQ_REG_ADDR(v, 0x0, (port))
+#define LPAIF_IRQSTAT_REG(v, port) LPAIF_IRQ_REG_ADDR(v, 0x4, (port))
+#define LPAIF_IRQCLEAR_REG(v, port) LPAIF_IRQ_REG_ADDR(v, 0xC, (port))
+
+/* LPAIF RXTX IRQ */
+#define LPAIF_RXTX_IRQ_REG_ADDR(v, addr, port) \
+ (v->rxtx_irq_reg_base + (addr) + v->rxtx_irq_reg_stride * (port))
+
+#define LPAIF_RXTX_IRQEN_REG(v, port) LPAIF_RXTX_IRQ_REG_ADDR(v, 0x0, port)
+#define LPAIF_RXTX_IRQSTAT_REG(v, port) LPAIF_RXTX_IRQ_REG_ADDR(v, 0x4, port)
+#define LPAIF_RXTX_IRQCLEAR_REG(v, port) LPAIF_RXTX_IRQ_REG_ADDR(v, 0xC, port)
+
+/* LPAIF VA IRQ */
+#define LPAIF_VA_IRQ_REG_ADDR(v, addr, port) \
+ (v->va_irq_reg_base + (addr) + v->va_irq_reg_stride * (port))
+
+#define LPAIF_VA_IRQEN_REG(v, port) LPAIF_VA_IRQ_REG_ADDR(v, 0x0, port)
+#define LPAIF_VA_IRQSTAT_REG(v, port) LPAIF_VA_IRQ_REG_ADDR(v, 0x4, port)
+#define LPAIF_VA_IRQCLEAR_REG(v, port) LPAIF_VA_IRQ_REG_ADDR(v, 0xC, port)
+
+#define LPASS_HDMITX_APP_IRQ_REG_ADDR(v, addr) \
+ ((v->hdmi_irq_reg_base) + (addr))
+
+#define LPASS_HDMITX_APP_IRQEN_REG(v) LPASS_HDMITX_APP_IRQ_REG_ADDR(v, 0x4)
+#define LPASS_HDMITX_APP_IRQSTAT_REG(v) LPASS_HDMITX_APP_IRQ_REG_ADDR(v, 0x8)
+#define LPASS_HDMITX_APP_IRQCLEAR_REG(v) LPASS_HDMITX_APP_IRQ_REG_ADDR(v, 0xC)
+
+#define LPAIF_IRQ_BITSTRIDE 3
+
+#define LPAIF_IRQ_PER(chan) (1 << (LPAIF_IRQ_BITSTRIDE * (chan)))
+#define LPAIF_IRQ_XRUN(chan) (2 << (LPAIF_IRQ_BITSTRIDE * (chan)))
+#define LPAIF_IRQ_ERR(chan) (4 << (LPAIF_IRQ_BITSTRIDE * (chan)))
+
+#define LPAIF_IRQ_ALL(chan) (7 << (LPAIF_IRQ_BITSTRIDE * (chan)))
+#define LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(chan) (1 << (14 + chan))
+#define LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(chan) (1 << (24 + chan))
+#define LPAIF_IRQ_HDMI_METADONE BIT(23)
+
+/* LPAIF DMA */
+#define LPAIF_HDMI_RDMA_REG_ADDR(v, addr, chan) \
+ (v->hdmi_rdma_reg_base + (addr) + v->hdmi_rdma_reg_stride * (chan))
+
+#define LPAIF_HDMI_RDMACTL_AUDINTF(id) (id << LPAIF_RDMACTL_AUDINTF_SHIFT)
+
+#define LPAIF_HDMI_RDMACTL_REG(v, chan) LPAIF_HDMI_RDMA_REG_ADDR(v, 0x00, (chan))
+#define LPAIF_HDMI_RDMABASE_REG(v, chan) LPAIF_HDMI_RDMA_REG_ADDR(v, 0x04, (chan))
+#define LPAIF_HDMI_RDMABUFF_REG(v, chan) LPAIF_HDMI_RDMA_REG_ADDR(v, 0x08, (chan))
+#define LPAIF_HDMI_RDMACURR_REG(v, chan) LPAIF_HDMI_RDMA_REG_ADDR(v, 0x0C, (chan))
+#define LPAIF_HDMI_RDMAPER_REG(v, chan) LPAIF_HDMI_RDMA_REG_ADDR(v, 0x10, (chan))
+#define LPAIF_HDMI_RDMAPERCNT_REG(v, chan) LPAIF_HDMI_RDMA_REG_ADDR(v, 0x14, (chan))
+
+#define LPAIF_RDMA_REG_ADDR(v, addr, chan) \
+ (v->rdma_reg_base + (addr) + v->rdma_reg_stride * (chan))
+
+#define LPAIF_RDMACTL_AUDINTF(id) (id << LPAIF_RDMACTL_AUDINTF_SHIFT)
+
+#define LPAIF_RDMACTL_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x00, (chan))
+#define LPAIF_RDMABASE_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x04, (chan))
+#define LPAIF_RDMABUFF_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x08, (chan))
+#define LPAIF_RDMACURR_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x0C, (chan))
+#define LPAIF_RDMAPER_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x10, (chan))
+#define LPAIF_RDMAPERCNT_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x14, (chan))
+
+#define LPAIF_WRDMA_REG_ADDR(v, addr, chan) \
+ (v->wrdma_reg_base + (addr) + \
+ v->wrdma_reg_stride * (chan - v->wrdma_channel_start))
+
+#define LPAIF_WRDMACTL_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x00, (chan))
+#define LPAIF_WRDMABASE_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x04, (chan))
+#define LPAIF_WRDMABUFF_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x08, (chan))
+#define LPAIF_WRDMACURR_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x0C, (chan))
+#define LPAIF_WRDMAPER_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x10, (chan))
+#define LPAIF_WRDMAPERCNT_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x14, (chan))
+
+#define LPAIF_INTFDMA_REG(v, chan, reg, dai_id) \
+ ((dai_id == LPASS_DP_RX) ? \
+ LPAIF_HDMI_RDMA##reg##_REG(v, chan) : \
+ LPAIF_RDMA##reg##_REG(v, chan))
+
+#define __LPAIF_DMA_REG(v, chan, dir, reg, dai_id) \
+ ((dir == SNDRV_PCM_STREAM_PLAYBACK) ? \
+ (LPAIF_INTFDMA_REG(v, chan, reg, dai_id)) : \
+ LPAIF_WRDMA##reg##_REG(v, chan))
+
+#define LPAIF_DMACTL_REG(v, chan, dir, dai_id) \
+ (is_cdc_dma_port(dai_id) ? \
+ __LPAIF_CDC_DMA_REG(v, chan, dir, CTL, dai_id) : \
+ __LPAIF_DMA_REG(v, chan, dir, CTL, dai_id))
+#define LPAIF_DMABASE_REG(v, chan, dir, dai_id) \
+ (is_cdc_dma_port(dai_id) ? \
+ __LPAIF_CDC_DMA_REG(v, chan, dir, BASE, dai_id) : \
+ __LPAIF_DMA_REG(v, chan, dir, BASE, dai_id))
+#define LPAIF_DMABUFF_REG(v, chan, dir, dai_id) \
+ (is_cdc_dma_port(dai_id) ? \
+ __LPAIF_CDC_DMA_REG(v, chan, dir, BUFF, dai_id) : \
+ __LPAIF_DMA_REG(v, chan, dir, BUFF, dai_id))
+#define LPAIF_DMACURR_REG(v, chan, dir, dai_id) \
+ (is_cdc_dma_port(dai_id) ? \
+ __LPAIF_CDC_DMA_REG(v, chan, dir, CURR, dai_id) : \
+ __LPAIF_DMA_REG(v, chan, dir, CURR, dai_id))
+#define LPAIF_DMAPER_REG(v, chan, dir, dai_id) \
+ (is_cdc_dma_port(dai_id) ? \
+ __LPAIF_CDC_DMA_REG(v, chan, dir, PER, dai_id) : \
+ __LPAIF_DMA_REG(v, chan, dir, PER, dai_id))
+#define LPAIF_DMAPERCNT_REG(v, chan, dir, dai_id) \
+ (is_cdc_dma_port(dai_id) ? \
+ __LPAIF_CDC_DMA_REG(v, chan, dir, PERCNT, dai_id) : \
+ __LPAIF_DMA_REG(v, chan, dir, PERCNT, dai_id))
+
+#define LPAIF_CDC_RDMA_REG_ADDR(v, addr, chan, dai_id) \
+ (is_rxtx_cdc_dma_port(dai_id) ? \
+ (v->rxtx_rdma_reg_base + (addr) + v->rxtx_rdma_reg_stride * (chan)) : \
+ (v->va_rdma_reg_base + (addr) + v->va_rdma_reg_stride * (chan)))
+
+#define LPAIF_CDC_RXTX_RDMACTL_REG(v, chan, dai_id) \
+ LPAIF_CDC_RDMA_REG_ADDR(v, 0x00, (chan), dai_id)
+#define LPAIF_CDC_RXTX_RDMABASE_REG(v, chan, dai_id) \
+ LPAIF_CDC_RDMA_REG_ADDR(v, 0x04, (chan), dai_id)
+#define LPAIF_CDC_RXTX_RDMABUFF_REG(v, chan, dai_id) \
+ LPAIF_CDC_RDMA_REG_ADDR(v, 0x08, (chan), dai_id)
+#define LPAIF_CDC_RXTX_RDMACURR_REG(v, chan, dai_id) \
+ LPAIF_CDC_RDMA_REG_ADDR(v, 0x0C, (chan), dai_id)
+#define LPAIF_CDC_RXTX_RDMAPER_REG(v, chan, dai_id) \
+ LPAIF_CDC_RDMA_REG_ADDR(v, 0x10, (chan), dai_id)
+#define LPAIF_CDC_RXTX_RDMA_INTF_REG(v, chan, dai_id) \
+ LPAIF_CDC_RDMA_REG_ADDR(v, 0x50, (chan), dai_id)
+
+#define LPAIF_CDC_VA_RDMACTL_REG(v, chan, dai_id) LPAIF_CDC_RDMA_REG_ADDR(v, 0x00, (chan), dai_id)
+#define LPAIF_CDC_VA_RDMABASE_REG(v, chan, dai_id) LPAIF_CDC_RDMA_REG_ADDR(v, 0x04, (chan), dai_id)
+#define LPAIF_CDC_VA_RDMABUFF_REG(v, chan, dai_id) LPAIF_CDC_RDMA_REG_ADDR(v, 0x08, (chan), dai_id)
+#define LPAIF_CDC_VA_RDMACURR_REG(v, chan, dai_id) LPAIF_CDC_RDMA_REG_ADDR(v, 0x0C, (chan), dai_id)
+#define LPAIF_CDC_VA_RDMAPER_REG(v, chan, dai_id) LPAIF_CDC_RDMA_REG_ADDR(v, 0x10, (chan), dai_id)
+#define LPAIF_CDC_VA_RDMA_INTF_REG(v, chan, dai_id) \
+ LPAIF_CDC_RDMA_REG_ADDR(v, 0x50, (chan), dai_id)
+
+#define LPAIF_CDC_WRDMA_REG_ADDR(v, addr, chan, dai_id) \
+ (is_rxtx_cdc_dma_port(dai_id) ? \
+ (v->rxtx_wrdma_reg_base + (addr) + \
+ v->rxtx_wrdma_reg_stride * (chan - v->rxtx_wrdma_channel_start)) : \
+ (v->va_wrdma_reg_base + (addr) + \
+ v->va_wrdma_reg_stride * (chan - v->va_wrdma_channel_start)))
+
+#define LPAIF_CDC_RXTX_WRDMACTL_REG(v, chan, dai_id) \
+ LPAIF_CDC_WRDMA_REG_ADDR(v, 0x00, (chan), dai_id)
+#define LPAIF_CDC_RXTX_WRDMABASE_REG(v, chan, dai_id) \
+ LPAIF_CDC_WRDMA_REG_ADDR(v, 0x04, (chan), dai_id)
+#define LPAIF_CDC_RXTX_WRDMABUFF_REG(v, chan, dai_id) \
+ LPAIF_CDC_WRDMA_REG_ADDR(v, 0x08, (chan), dai_id)
+#define LPAIF_CDC_RXTX_WRDMACURR_REG(v, chan, dai_id) \
+ LPAIF_CDC_WRDMA_REG_ADDR(v, 0x0C, (chan), dai_id)
+#define LPAIF_CDC_RXTX_WRDMAPER_REG(v, chan, dai_id) \
+ LPAIF_CDC_WRDMA_REG_ADDR(v, 0x10, (chan), dai_id)
+#define LPAIF_CDC_RXTX_WRDMA_INTF_REG(v, chan, dai_id) \
+ LPAIF_CDC_WRDMA_REG_ADDR(v, 0x50, (chan), dai_id)
+
+#define LPAIF_CDC_VA_WRDMACTL_REG(v, chan, dai_id) \
+ LPAIF_CDC_WRDMA_REG_ADDR(v, 0x00, (chan), dai_id)
+#define LPAIF_CDC_VA_WRDMABASE_REG(v, chan, dai_id) \
+ LPAIF_CDC_WRDMA_REG_ADDR(v, 0x04, (chan), dai_id)
+#define LPAIF_CDC_VA_WRDMABUFF_REG(v, chan, dai_id) \
+ LPAIF_CDC_WRDMA_REG_ADDR(v, 0x08, (chan), dai_id)
+#define LPAIF_CDC_VA_WRDMACURR_REG(v, chan, dai_id) \
+ LPAIF_CDC_WRDMA_REG_ADDR(v, 0x0C, (chan), dai_id)
+#define LPAIF_CDC_VA_WRDMAPER_REG(v, chan, dai_id) \
+ LPAIF_CDC_WRDMA_REG_ADDR(v, 0x10, (chan), dai_id)
+#define LPAIF_CDC_VA_WRDMA_INTF_REG(v, chan, dai_id) \
+ LPAIF_CDC_WRDMA_REG_ADDR(v, 0x50, (chan), dai_id)
+
+#define __LPAIF_CDC_RDDMA_REG(v, chan, dir, reg, dai_id) \
+ (is_rxtx_cdc_dma_port(dai_id) ? LPAIF_CDC_RXTX_RDMA##reg##_REG(v, chan, dai_id) : \
+ LPAIF_CDC_VA_RDMA##reg##_REG(v, chan, dai_id))
+
+#define __LPAIF_CDC_WRDMA_REG(v, chan, dir, reg, dai_id) \
+ (is_rxtx_cdc_dma_port(dai_id) ? LPAIF_CDC_RXTX_WRDMA##reg##_REG(v, chan, dai_id) : \
+ LPAIF_CDC_VA_WRDMA##reg##_REG(v, chan, dai_id))
+
+#define __LPAIF_CDC_DMA_REG(v, chan, dir, reg, dai_id) \
+ ((dir == SNDRV_PCM_STREAM_PLAYBACK) ? \
+ __LPAIF_CDC_RDDMA_REG(v, chan, dir, reg, dai_id) : \
+ __LPAIF_CDC_WRDMA_REG(v, chan, dir, reg, dai_id))
+
+#define LPAIF_CDC_INTF_REG(v, chan, dir, dai_id) \
+ ((dir == SNDRV_PCM_STREAM_PLAYBACK) ? \
+ LPAIF_CDC_RDMA_INTF_REG(v, chan, dai_id) : \
+ LPAIF_CDC_WRDMA_INTF_REG(v, chan, dai_id))
+
+#define LPAIF_INTF_REG(v, chan, dir, dai_id) \
+ (is_cdc_dma_port(dai_id) ? \
+ LPAIF_CDC_INTF_REG(v, chan, dir, dai_id) : \
+ LPAIF_DMACTL_REG(v, chan, dir, dai_id))
+
+#define LPAIF_DMACTL_BURSTEN_SINGLE 0
+#define LPAIF_DMACTL_BURSTEN_INCR4 1
+
+#define LPAIF_DMACTL_WPSCNT_ONE 0
+#define LPAIF_DMACTL_WPSCNT_TWO 1
+#define LPAIF_DMACTL_WPSCNT_THREE 2
+#define LPAIF_DMACTL_WPSCNT_FOUR 3
+#define LPAIF_DMACTL_WPSCNT_SIX 5
+#define LPAIF_DMACTL_WPSCNT_EIGHT 7
+#define LPAIF_DMACTL_WPSCNT_TEN 9
+#define LPAIF_DMACTL_WPSCNT_TWELVE 11
+#define LPAIF_DMACTL_WPSCNT_FOURTEEN 13
+#define LPAIF_DMACTL_WPSCNT_SIXTEEN 15
+
+#define LPAIF_DMACTL_AUDINTF(id) id
+
+#define LPAIF_DMACTL_FIFOWM_1 0
+#define LPAIF_DMACTL_FIFOWM_2 1
+#define LPAIF_DMACTL_FIFOWM_3 2
+#define LPAIF_DMACTL_FIFOWM_4 3
+#define LPAIF_DMACTL_FIFOWM_5 4
+#define LPAIF_DMACTL_FIFOWM_6 5
+#define LPAIF_DMACTL_FIFOWM_7 6
+#define LPAIF_DMACTL_FIFOWM_8 7
+#define LPAIF_DMACTL_FIFOWM_9 8
+#define LPAIF_DMACTL_FIFOWM_10 9
+#define LPAIF_DMACTL_FIFOWM_11 10
+#define LPAIF_DMACTL_FIFOWM_12 11
+#define LPAIF_DMACTL_FIFOWM_13 12
+#define LPAIF_DMACTL_FIFOWM_14 13
+#define LPAIF_DMACTL_FIFOWM_15 14
+#define LPAIF_DMACTL_FIFOWM_16 15
+#define LPAIF_DMACTL_FIFOWM_17 16
+#define LPAIF_DMACTL_FIFOWM_18 17
+#define LPAIF_DMACTL_FIFOWM_19 18
+#define LPAIF_DMACTL_FIFOWM_20 19
+#define LPAIF_DMACTL_FIFOWM_21 20
+#define LPAIF_DMACTL_FIFOWM_22 21
+#define LPAIF_DMACTL_FIFOWM_23 22
+#define LPAIF_DMACTL_FIFOWM_24 23
+#define LPAIF_DMACTL_FIFOWM_25 24
+#define LPAIF_DMACTL_FIFOWM_26 25
+#define LPAIF_DMACTL_FIFOWM_27 26
+#define LPAIF_DMACTL_FIFOWM_28 27
+#define LPAIF_DMACTL_FIFOWM_29 28
+#define LPAIF_DMACTL_FIFOWM_30 29
+#define LPAIF_DMACTL_FIFOWM_31 30
+#define LPAIF_DMACTL_FIFOWM_32 31
+
+#define LPAIF_DMACTL_ENABLE_OFF 0
+#define LPAIF_DMACTL_ENABLE_ON 1
+
+#define LPAIF_DMACTL_DYNCLK_OFF 0
+#define LPAIF_DMACTL_DYNCLK_ON 1
+
+#endif /* __LPASS_LPAIF_REG_H__ */
diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c
new file mode 100644
index 000000000..e162627d6
--- /dev/null
+++ b/sound/soc/qcom/lpass-platform.c
@@ -0,0 +1,1396 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
+ *
+ * lpass-platform.c -- ALSA SoC platform driver for QTi LPASS
+ */
+
+#include <dt-bindings/sound/qcom,lpass.h>
+#include <linux/dma-mapping.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/pcm_params.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include "lpass-lpaif-reg.h"
+#include "lpass.h"
+
+#define DRV_NAME "lpass-platform"
+
+#define LPASS_PLATFORM_BUFFER_SIZE (24 * 2 * 1024)
+#define LPASS_PLATFORM_PERIODS 2
+#define LPASS_RXTX_CDC_DMA_LPM_BUFF_SIZE (8 * 1024)
+#define LPASS_VA_CDC_DMA_LPM_BUFF_SIZE (12 * 1024)
+#define LPASS_CDC_DMA_REGISTER_FIELDS_MAX 15
+
+static const struct snd_pcm_hardware lpass_platform_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16 |
+ SNDRV_PCM_FMTBIT_S24 |
+ SNDRV_PCM_FMTBIT_S32,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 1,
+ .channels_max = 8,
+ .buffer_bytes_max = LPASS_PLATFORM_BUFFER_SIZE,
+ .period_bytes_max = LPASS_PLATFORM_BUFFER_SIZE /
+ LPASS_PLATFORM_PERIODS,
+ .period_bytes_min = LPASS_PLATFORM_BUFFER_SIZE /
+ LPASS_PLATFORM_PERIODS,
+ .periods_min = LPASS_PLATFORM_PERIODS,
+ .periods_max = LPASS_PLATFORM_PERIODS,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware lpass_platform_rxtx_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16 |
+ SNDRV_PCM_FMTBIT_S24 |
+ SNDRV_PCM_FMTBIT_S32,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 1,
+ .channels_max = 8,
+ .buffer_bytes_max = LPASS_RXTX_CDC_DMA_LPM_BUFF_SIZE,
+ .period_bytes_max = LPASS_RXTX_CDC_DMA_LPM_BUFF_SIZE /
+ LPASS_PLATFORM_PERIODS,
+ .period_bytes_min = LPASS_RXTX_CDC_DMA_LPM_BUFF_SIZE /
+ LPASS_PLATFORM_PERIODS,
+ .periods_min = LPASS_PLATFORM_PERIODS,
+ .periods_max = LPASS_PLATFORM_PERIODS,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware lpass_platform_va_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16 |
+ SNDRV_PCM_FMTBIT_S24 |
+ SNDRV_PCM_FMTBIT_S32,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 1,
+ .channels_max = 8,
+ .buffer_bytes_max = LPASS_VA_CDC_DMA_LPM_BUFF_SIZE,
+ .period_bytes_max = LPASS_VA_CDC_DMA_LPM_BUFF_SIZE /
+ LPASS_PLATFORM_PERIODS,
+ .period_bytes_min = LPASS_VA_CDC_DMA_LPM_BUFF_SIZE /
+ LPASS_PLATFORM_PERIODS,
+ .periods_min = LPASS_PLATFORM_PERIODS,
+ .periods_max = LPASS_PLATFORM_PERIODS,
+ .fifo_size = 0,
+};
+
+static int lpass_platform_alloc_rxtx_dmactl_fields(struct device *dev,
+ struct regmap *map)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ struct lpaif_dmactl *rd_dmactl, *wr_dmactl;
+ int rval;
+
+ rd_dmactl = devm_kzalloc(dev, sizeof(*rd_dmactl), GFP_KERNEL);
+ if (!rd_dmactl)
+ return -ENOMEM;
+
+ wr_dmactl = devm_kzalloc(dev, sizeof(*wr_dmactl), GFP_KERNEL);
+ if (!wr_dmactl)
+ return -ENOMEM;
+
+ drvdata->rxtx_rd_dmactl = rd_dmactl;
+ drvdata->rxtx_wr_dmactl = wr_dmactl;
+
+ rval = devm_regmap_field_bulk_alloc(dev, map, &rd_dmactl->intf,
+ &v->rxtx_rdma_intf, LPASS_CDC_DMA_REGISTER_FIELDS_MAX);
+ if (rval)
+ return rval;
+
+ return devm_regmap_field_bulk_alloc(dev, map, &wr_dmactl->intf,
+ &v->rxtx_wrdma_intf, LPASS_CDC_DMA_REGISTER_FIELDS_MAX);
+}
+
+static int lpass_platform_alloc_va_dmactl_fields(struct device *dev,
+ struct regmap *map)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ struct lpaif_dmactl *wr_dmactl;
+
+ wr_dmactl = devm_kzalloc(dev, sizeof(*wr_dmactl), GFP_KERNEL);
+ if (!wr_dmactl)
+ return -ENOMEM;
+
+ drvdata->va_wr_dmactl = wr_dmactl;
+ return devm_regmap_field_bulk_alloc(dev, map, &wr_dmactl->intf,
+ &v->va_wrdma_intf, LPASS_CDC_DMA_REGISTER_FIELDS_MAX);
+}
+
+
+static int lpass_platform_alloc_dmactl_fields(struct device *dev,
+ struct regmap *map)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ struct lpaif_dmactl *rd_dmactl, *wr_dmactl;
+ int rval;
+
+ drvdata->rd_dmactl = devm_kzalloc(dev, sizeof(struct lpaif_dmactl),
+ GFP_KERNEL);
+ if (drvdata->rd_dmactl == NULL)
+ return -ENOMEM;
+
+ drvdata->wr_dmactl = devm_kzalloc(dev, sizeof(struct lpaif_dmactl),
+ GFP_KERNEL);
+ if (drvdata->wr_dmactl == NULL)
+ return -ENOMEM;
+
+ rd_dmactl = drvdata->rd_dmactl;
+ wr_dmactl = drvdata->wr_dmactl;
+
+ rval = devm_regmap_field_bulk_alloc(dev, map, &rd_dmactl->intf,
+ &v->rdma_intf, 6);
+ if (rval)
+ return rval;
+
+ return devm_regmap_field_bulk_alloc(dev, map, &wr_dmactl->intf,
+ &v->wrdma_intf, 6);
+}
+
+static int lpass_platform_alloc_hdmidmactl_fields(struct device *dev,
+ struct regmap *map)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+ const struct lpass_variant *v = drvdata->variant;
+ struct lpaif_dmactl *rd_dmactl;
+
+ rd_dmactl = devm_kzalloc(dev, sizeof(struct lpaif_dmactl), GFP_KERNEL);
+ if (rd_dmactl == NULL)
+ return -ENOMEM;
+
+ drvdata->hdmi_rd_dmactl = rd_dmactl;
+
+ return devm_regmap_field_bulk_alloc(dev, map, &rd_dmactl->bursten,
+ &v->hdmi_rdma_bursten, 8);
+}
+
+static int lpass_platform_pcmops_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ const struct lpass_variant *v = drvdata->variant;
+ int ret, dma_ch, dir = substream->stream;
+ struct lpass_pcm_data *data;
+ struct regmap *map;
+ unsigned int dai_id = cpu_dai->driver->id;
+
+ data = kzalloc_obj(*data);
+ if (!data)
+ return -ENOMEM;
+
+ data->i2s_port = cpu_dai->driver->id;
+ runtime->private_data = data;
+
+ if (v->alloc_dma_channel)
+ dma_ch = v->alloc_dma_channel(drvdata, dir, dai_id);
+ else
+ dma_ch = 0;
+
+ if (dma_ch < 0) {
+ kfree(data);
+ return dma_ch;
+ }
+
+ switch (dai_id) {
+ case MI2S_PRIMARY ... MI2S_QUINARY:
+ map = drvdata->lpaif_map;
+ drvdata->substream[dma_ch] = substream;
+ break;
+ case LPASS_DP_RX:
+ map = drvdata->hdmiif_map;
+ drvdata->hdmi_substream[dma_ch] = substream;
+ break;
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ map = drvdata->rxtx_lpaif_map;
+ drvdata->rxtx_substream[dma_ch] = substream;
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ map = drvdata->va_lpaif_map;
+ drvdata->va_substream[dma_ch] = substream;
+ break;
+ default:
+ break;
+ }
+
+ data->dma_ch = dma_ch;
+ switch (dai_id) {
+ case MI2S_PRIMARY ... MI2S_QUINARY:
+ case LPASS_DP_RX:
+ ret = regmap_write(map, LPAIF_DMACTL_REG(v, dma_ch, dir, data->i2s_port), 0);
+ if (ret) {
+ kfree(data);
+ dev_err(soc_runtime->dev, "error writing to rdmactl reg: %d\n", ret);
+ return ret;
+ }
+ snd_soc_set_runtime_hwparams(substream, &lpass_platform_pcm_hardware);
+ runtime->dma_bytes = lpass_platform_pcm_hardware.buffer_bytes_max;
+ break;
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ snd_soc_set_runtime_hwparams(substream, &lpass_platform_rxtx_hardware);
+ runtime->dma_bytes = lpass_platform_rxtx_hardware.buffer_bytes_max;
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ snd_soc_set_runtime_hwparams(substream, &lpass_platform_va_hardware);
+ runtime->dma_bytes = lpass_platform_va_hardware.buffer_bytes_max;
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ break;
+ default:
+ break;
+ }
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0) {
+ kfree(data);
+ dev_err(soc_runtime->dev, "setting constraints failed: %d\n",
+ ret);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int lpass_platform_pcmops_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ const struct lpass_variant *v = drvdata->variant;
+ struct lpass_pcm_data *data;
+ unsigned int dai_id = cpu_dai->driver->id;
+
+ data = runtime->private_data;
+
+ switch (dai_id) {
+ case MI2S_PRIMARY ... MI2S_QUINARY:
+ drvdata->substream[data->dma_ch] = NULL;
+ break;
+ case LPASS_DP_RX:
+ drvdata->hdmi_substream[data->dma_ch] = NULL;
+ break;
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ drvdata->rxtx_substream[data->dma_ch] = NULL;
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ drvdata->va_substream[data->dma_ch] = NULL;
+ break;
+ default:
+ break;
+ }
+
+ if (v->free_dma_channel)
+ v->free_dma_channel(drvdata, data->dma_ch, dai_id);
+
+ kfree(data);
+ return 0;
+}
+
+static struct lpaif_dmactl *__lpass_get_dmactl_handle(const struct snd_pcm_substream *substream,
+ struct snd_soc_component *component)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ struct lpaif_dmactl *dmactl = NULL;
+
+ switch (cpu_dai->driver->id) {
+ case MI2S_PRIMARY ... MI2S_QUINARY:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dmactl = drvdata->rd_dmactl;
+ else
+ dmactl = drvdata->wr_dmactl;
+ break;
+ case LPASS_DP_RX:
+ dmactl = drvdata->hdmi_rd_dmactl;
+ break;
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ dmactl = drvdata->rxtx_rd_dmactl;
+ break;
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ dmactl = drvdata->rxtx_wr_dmactl;
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ dmactl = drvdata->va_wr_dmactl;
+ break;
+ }
+
+ return dmactl;
+}
+
+static int __lpass_get_id(const struct snd_pcm_substream *substream,
+ struct snd_soc_component *component)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct lpass_pcm_data *pcm_data = rt->private_data;
+ const struct lpass_variant *v = drvdata->variant;
+ int id;
+
+ switch (cpu_dai->driver->id) {
+ case MI2S_PRIMARY ... MI2S_QUINARY:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ id = pcm_data->dma_ch;
+ else
+ id = pcm_data->dma_ch - v->wrdma_channel_start;
+ break;
+ case LPASS_DP_RX:
+ id = pcm_data->dma_ch;
+ break;
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ id = pcm_data->dma_ch;
+ break;
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ id = pcm_data->dma_ch - v->rxtx_wrdma_channel_start;
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ id = pcm_data->dma_ch - v->va_wrdma_channel_start;
+ break;
+ }
+
+ return id;
+}
+
+static struct regmap *__lpass_get_regmap_handle(const struct snd_pcm_substream *substream,
+ struct snd_soc_component *component)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ struct regmap *map = NULL;
+
+ switch (cpu_dai->driver->id) {
+ case MI2S_PRIMARY ... MI2S_QUINARY:
+ map = drvdata->lpaif_map;
+ break;
+ case LPASS_DP_RX:
+ map = drvdata->hdmiif_map;
+ break;
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ map = drvdata->rxtx_lpaif_map;
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ map = drvdata->va_lpaif_map;
+ break;
+ }
+
+ return map;
+}
+
+static int lpass_platform_pcmops_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 = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct lpass_pcm_data *pcm_data = rt->private_data;
+ const struct lpass_variant *v = drvdata->variant;
+ snd_pcm_format_t format = params_format(params);
+ unsigned int channels = params_channels(params);
+ unsigned int regval;
+ struct lpaif_dmactl *dmactl;
+ int id;
+ int bitwidth;
+ int ret, dma_port = pcm_data->i2s_port + v->dmactl_audif_start;
+ unsigned int dai_id = cpu_dai->driver->id;
+
+ dmactl = __lpass_get_dmactl_handle(substream, component);
+ id = __lpass_get_id(substream, component);
+
+ bitwidth = snd_pcm_format_width(format);
+ if (bitwidth < 0) {
+ dev_err(soc_runtime->dev, "invalid bit width given: %d\n",
+ bitwidth);
+ return bitwidth;
+ }
+
+ ret = regmap_fields_write(dmactl->bursten, id, LPAIF_DMACTL_BURSTEN_INCR4);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error updating bursten field: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_fields_write(dmactl->fifowm, id, LPAIF_DMACTL_FIFOWM_8);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error updating fifowm field: %d\n", ret);
+ return ret;
+ }
+
+ switch (dai_id) {
+ case LPASS_DP_RX:
+ ret = regmap_fields_write(dmactl->burst8, id,
+ LPAIF_DMACTL_BURSTEN_INCR4);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error updating burst8en field: %d\n", ret);
+ return ret;
+ }
+ ret = regmap_fields_write(dmactl->burst16, id,
+ LPAIF_DMACTL_BURSTEN_INCR4);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error updating burst16en field: %d\n", ret);
+ return ret;
+ }
+ ret = regmap_fields_write(dmactl->dynburst, id,
+ LPAIF_DMACTL_BURSTEN_INCR4);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error updating dynbursten field: %d\n", ret);
+ return ret;
+ }
+ break;
+ case MI2S_PRIMARY:
+ case MI2S_SECONDARY:
+ case MI2S_TERTIARY:
+ case MI2S_QUATERNARY:
+ case MI2S_QUINARY:
+ ret = regmap_fields_write(dmactl->intf, id,
+ LPAIF_DMACTL_AUDINTF(dma_port));
+ if (ret) {
+ dev_err(soc_runtime->dev, "error updating audio interface field: %d\n",
+ ret);
+ return ret;
+ }
+
+ break;
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX0:
+ break;
+ default:
+ dev_err(soc_runtime->dev, "%s: invalid interface: %d\n", __func__, dai_id);
+ break;
+ }
+ switch (bitwidth) {
+ case 16:
+ switch (channels) {
+ case 1:
+ case 2:
+ regval = LPAIF_DMACTL_WPSCNT_ONE;
+ break;
+ case 4:
+ regval = LPAIF_DMACTL_WPSCNT_TWO;
+ break;
+ case 6:
+ regval = LPAIF_DMACTL_WPSCNT_THREE;
+ break;
+ case 8:
+ regval = LPAIF_DMACTL_WPSCNT_FOUR;
+ break;
+ default:
+ dev_err(soc_runtime->dev, "invalid PCM config given: bw=%d, ch=%u\n",
+ bitwidth, channels);
+ return -EINVAL;
+ }
+ break;
+ case 24:
+ case 32:
+ switch (channels) {
+ case 1:
+ regval = LPAIF_DMACTL_WPSCNT_ONE;
+ break;
+ case 2:
+ regval = (dai_id == LPASS_DP_RX ?
+ LPAIF_DMACTL_WPSCNT_ONE :
+ LPAIF_DMACTL_WPSCNT_TWO);
+ break;
+ case 4:
+ regval = (dai_id == LPASS_DP_RX ?
+ LPAIF_DMACTL_WPSCNT_TWO :
+ LPAIF_DMACTL_WPSCNT_FOUR);
+ break;
+ case 6:
+ regval = (dai_id == LPASS_DP_RX ?
+ LPAIF_DMACTL_WPSCNT_THREE :
+ LPAIF_DMACTL_WPSCNT_SIX);
+ break;
+ case 8:
+ regval = (dai_id == LPASS_DP_RX ?
+ LPAIF_DMACTL_WPSCNT_FOUR :
+ LPAIF_DMACTL_WPSCNT_EIGHT);
+ break;
+ default:
+ dev_err(soc_runtime->dev, "invalid PCM config given: bw=%d, ch=%u\n",
+ bitwidth, channels);
+ return -EINVAL;
+ }
+ break;
+ default:
+ dev_err(soc_runtime->dev, "invalid PCM config given: bw=%d, ch=%u\n",
+ bitwidth, channels);
+ return -EINVAL;
+ }
+
+ ret = regmap_fields_write(dmactl->wpscnt, id, regval);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error writing to dmactl reg: %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lpass_platform_pcmops_hw_free(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct lpass_pcm_data *pcm_data = rt->private_data;
+ const struct lpass_variant *v = drvdata->variant;
+ unsigned int reg;
+ int ret;
+ struct regmap *map;
+ unsigned int dai_id = cpu_dai->driver->id;
+
+ if (is_cdc_dma_port(dai_id))
+ return 0;
+ map = __lpass_get_regmap_handle(substream, component);
+
+ reg = LPAIF_DMACTL_REG(v, pcm_data->dma_ch, substream->stream, dai_id);
+ ret = regmap_write(map, reg, 0);
+ if (ret)
+ dev_err(soc_runtime->dev, "error writing to rdmactl reg: %d\n",
+ ret);
+
+ return ret;
+}
+
+static int lpass_platform_pcmops_prepare(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct lpass_pcm_data *pcm_data = rt->private_data;
+ const struct lpass_variant *v = drvdata->variant;
+ struct lpaif_dmactl *dmactl;
+ struct regmap *map;
+ int ret, id, ch, dir = substream->stream;
+ unsigned int dai_id = cpu_dai->driver->id;
+
+ ch = pcm_data->dma_ch;
+
+ dmactl = __lpass_get_dmactl_handle(substream, component);
+ id = __lpass_get_id(substream, component);
+ map = __lpass_get_regmap_handle(substream, component);
+
+ ret = regmap_write(map, LPAIF_DMABASE_REG(v, ch, dir, dai_id),
+ runtime->dma_addr);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error writing to rdmabase reg: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = regmap_write(map, LPAIF_DMABUFF_REG(v, ch, dir, dai_id),
+ (snd_pcm_lib_buffer_bytes(substream) >> 2) - 1);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error writing to rdmabuff reg: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = regmap_write(map, LPAIF_DMAPER_REG(v, ch, dir, dai_id),
+ (snd_pcm_lib_period_bytes(substream) >> 2) - 1);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error writing to rdmaper reg: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (is_cdc_dma_port(dai_id)) {
+ ret = regmap_fields_write(dmactl->fifowm, id, LPAIF_DMACTL_FIFOWM_8);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error writing fifowm field to dmactl reg: %d, id: %d\n",
+ ret, id);
+ return ret;
+ }
+ }
+ ret = regmap_fields_write(dmactl->enable, id, LPAIF_DMACTL_ENABLE_ON);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error writing to rdmactl reg: %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lpass_platform_pcmops_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct lpass_pcm_data *pcm_data = rt->private_data;
+ const struct lpass_variant *v = drvdata->variant;
+ struct lpaif_dmactl *dmactl;
+ struct regmap *map;
+ int ret, ch, id;
+ unsigned int reg_irqclr = 0, val_irqclr = 0;
+ unsigned int reg_irqen = 0, val_irqen = 0, val_mask = 0;
+ unsigned int dai_id = cpu_dai->driver->id;
+
+ ch = pcm_data->dma_ch;
+ dmactl = __lpass_get_dmactl_handle(substream, component);
+ id = __lpass_get_id(substream, component);
+ map = __lpass_get_regmap_handle(substream, component);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = regmap_fields_write(dmactl->enable, id,
+ LPAIF_DMACTL_ENABLE_ON);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error writing to rdmactl reg: %d\n", ret);
+ return ret;
+ }
+ switch (dai_id) {
+ case LPASS_DP_RX:
+ ret = regmap_fields_write(dmactl->dyncclk, id,
+ LPAIF_DMACTL_DYNCLK_ON);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error writing to rdmactl reg: %d\n", ret);
+ return ret;
+ }
+ reg_irqclr = LPASS_HDMITX_APP_IRQCLEAR_REG(v);
+ val_irqclr = (LPAIF_IRQ_ALL(ch) |
+ LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(ch) |
+ LPAIF_IRQ_HDMI_METADONE |
+ LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(ch));
+
+ reg_irqen = LPASS_HDMITX_APP_IRQEN_REG(v);
+ val_mask = (LPAIF_IRQ_ALL(ch) |
+ LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(ch) |
+ LPAIF_IRQ_HDMI_METADONE |
+ LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(ch));
+ val_irqen = (LPAIF_IRQ_ALL(ch) |
+ LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(ch) |
+ LPAIF_IRQ_HDMI_METADONE |
+ LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(ch));
+ break;
+ case MI2S_PRIMARY:
+ case MI2S_SECONDARY:
+ case MI2S_TERTIARY:
+ case MI2S_QUATERNARY:
+ case MI2S_QUINARY:
+ reg_irqclr = LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST);
+ val_irqclr = LPAIF_IRQ_ALL(ch);
+
+
+ reg_irqen = LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST);
+ val_mask = LPAIF_IRQ_ALL(ch);
+ val_irqen = LPAIF_IRQ_ALL(ch);
+ break;
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ ret = regmap_fields_write(dmactl->dyncclk, id, LPAIF_DMACTL_DYNCLK_ON);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error writing to rdmactl reg field: %d\n", ret);
+ return ret;
+ }
+ reg_irqclr = LPAIF_RXTX_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST);
+ val_irqclr = LPAIF_IRQ_ALL(ch);
+
+ reg_irqen = LPAIF_RXTX_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST);
+ val_mask = LPAIF_IRQ_ALL(ch);
+ val_irqen = LPAIF_IRQ_ALL(ch);
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ ret = regmap_fields_write(dmactl->dyncclk, id, LPAIF_DMACTL_DYNCLK_ON);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error writing to rdmactl reg field: %d\n", ret);
+ return ret;
+ }
+ reg_irqclr = LPAIF_VA_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST);
+ val_irqclr = LPAIF_IRQ_ALL(ch);
+
+ reg_irqen = LPAIF_VA_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST);
+ val_mask = LPAIF_IRQ_ALL(ch);
+ val_irqen = LPAIF_IRQ_ALL(ch);
+ break;
+ default:
+ dev_err(soc_runtime->dev, "%s: invalid %d interface\n", __func__, dai_id);
+ return -EINVAL;
+ }
+
+ ret = regmap_write_bits(map, reg_irqclr, val_irqclr, val_irqclr);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error writing to irqclear reg: %d\n", ret);
+ return ret;
+ }
+ ret = regmap_update_bits(map, reg_irqen, val_mask, val_irqen);
+ if (ret) {
+ dev_err(soc_runtime->dev, "error writing to irqen reg: %d\n", ret);
+ return ret;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = regmap_fields_write(dmactl->enable, id,
+ LPAIF_DMACTL_ENABLE_OFF);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error writing to rdmactl reg: %d\n", ret);
+ return ret;
+ }
+ switch (dai_id) {
+ case LPASS_DP_RX:
+ ret = regmap_fields_write(dmactl->dyncclk, id,
+ LPAIF_DMACTL_DYNCLK_OFF);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error writing to rdmactl reg: %d\n", ret);
+ return ret;
+ }
+ reg_irqen = LPASS_HDMITX_APP_IRQEN_REG(v);
+ val_mask = (LPAIF_IRQ_ALL(ch) |
+ LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(ch) |
+ LPAIF_IRQ_HDMI_METADONE |
+ LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(ch));
+ val_irqen = 0;
+ break;
+ case MI2S_PRIMARY:
+ case MI2S_SECONDARY:
+ case MI2S_TERTIARY:
+ case MI2S_QUATERNARY:
+ case MI2S_QUINARY:
+ reg_irqen = LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST);
+ val_mask = LPAIF_IRQ_ALL(ch);
+ val_irqen = 0;
+ break;
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ ret = regmap_fields_write(dmactl->dyncclk, id, LPAIF_DMACTL_DYNCLK_OFF);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error writing to rdmactl reg field: %d\n", ret);
+ return ret;
+ }
+
+ reg_irqclr = LPAIF_RXTX_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST);
+ val_irqclr = LPAIF_IRQ_ALL(ch);
+
+ reg_irqen = LPAIF_RXTX_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST);
+ val_mask = LPAIF_IRQ_ALL(ch);
+ val_irqen = LPAIF_IRQ_ALL(ch);
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ ret = regmap_fields_write(dmactl->dyncclk, id, LPAIF_DMACTL_DYNCLK_OFF);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error writing to rdmactl reg field: %d\n", ret);
+ return ret;
+ }
+
+ reg_irqclr = LPAIF_VA_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST);
+ val_irqclr = LPAIF_IRQ_ALL(ch);
+
+ reg_irqen = LPAIF_VA_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST);
+ val_mask = LPAIF_IRQ_ALL(ch);
+ val_irqen = LPAIF_IRQ_ALL(ch);
+ break;
+ default:
+ dev_err(soc_runtime->dev, "%s: invalid %d interface\n", __func__, dai_id);
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(map, reg_irqen, val_mask, val_irqen);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error writing to irqen reg: %d\n", ret);
+ return ret;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t lpass_platform_pcmops_pointer(
+ struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct lpass_pcm_data *pcm_data = rt->private_data;
+ const struct lpass_variant *v = drvdata->variant;
+ unsigned int base_addr, curr_addr;
+ int ret, ch, dir = substream->stream;
+ struct regmap *map;
+ unsigned int dai_id = cpu_dai->driver->id;
+
+ map = __lpass_get_regmap_handle(substream, component);
+ ch = pcm_data->dma_ch;
+
+ ret = regmap_read(map,
+ LPAIF_DMABASE_REG(v, ch, dir, dai_id), &base_addr);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error reading from rdmabase reg: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_read(map,
+ LPAIF_DMACURR_REG(v, ch, dir, dai_id), &curr_addr);
+ if (ret) {
+ dev_err(soc_runtime->dev,
+ "error reading from rdmacurr reg: %d\n", ret);
+ return ret;
+ }
+
+ return bytes_to_frames(substream->runtime, curr_addr - base_addr);
+}
+
+static int lpass_platform_cdc_dma_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long size, offset;
+
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+ size = vma->vm_end - vma->vm_start;
+ offset = vma->vm_pgoff << PAGE_SHIFT;
+ return io_remap_pfn_range(vma, vma->vm_start,
+ (runtime->dma_addr + offset) >> PAGE_SHIFT,
+ size, vma->vm_page_prot);
+
+}
+
+static int lpass_platform_pcmops_mmap(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ unsigned int dai_id = cpu_dai->driver->id;
+
+ if (is_cdc_dma_port(dai_id))
+ return lpass_platform_cdc_dma_mmap(substream, vma);
+
+ return snd_pcm_lib_default_mmap(substream, vma);
+}
+
+static irqreturn_t lpass_dma_interrupt_handler(
+ struct snd_pcm_substream *substream,
+ struct lpass_data *drvdata,
+ int chan, u32 interrupts)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ const struct lpass_variant *v = drvdata->variant;
+ irqreturn_t ret = IRQ_NONE;
+ int rv;
+ unsigned int reg, val, mask;
+ struct regmap *map;
+ unsigned int dai_id = cpu_dai->driver->id;
+
+ mask = LPAIF_IRQ_ALL(chan);
+ switch (dai_id) {
+ case LPASS_DP_RX:
+ map = drvdata->hdmiif_map;
+ reg = LPASS_HDMITX_APP_IRQCLEAR_REG(v);
+ val = (LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(chan) |
+ LPAIF_IRQ_HDMI_METADONE |
+ LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(chan));
+ break;
+ case MI2S_PRIMARY:
+ case MI2S_SECONDARY:
+ case MI2S_TERTIARY:
+ case MI2S_QUATERNARY:
+ case MI2S_QUINARY:
+ map = drvdata->lpaif_map;
+ reg = LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST);
+ val = 0;
+ break;
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ map = drvdata->rxtx_lpaif_map;
+ reg = LPAIF_RXTX_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST);
+ val = 0;
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ map = drvdata->va_lpaif_map;
+ reg = LPAIF_VA_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST);
+ val = 0;
+ break;
+ default:
+ dev_err(soc_runtime->dev, "%s: invalid %d interface\n", __func__, dai_id);
+ return -EINVAL;
+ }
+ if (interrupts & LPAIF_IRQ_PER(chan)) {
+ rv = regmap_write_bits(map, reg, mask, (LPAIF_IRQ_PER(chan) | val));
+ if (rv) {
+ dev_err(soc_runtime->dev,
+ "error writing to irqclear reg: %d\n", rv);
+ return IRQ_NONE;
+ }
+ snd_pcm_period_elapsed(substream);
+ ret = IRQ_HANDLED;
+ }
+
+ if (interrupts & LPAIF_IRQ_XRUN(chan)) {
+ rv = regmap_write_bits(map, reg, mask, (LPAIF_IRQ_XRUN(chan) | val));
+ if (rv) {
+ dev_err(soc_runtime->dev,
+ "error writing to irqclear reg: %d\n", rv);
+ return IRQ_NONE;
+ }
+ dev_warn_ratelimited(soc_runtime->dev, "xrun warning\n");
+
+ snd_pcm_stop_xrun(substream);
+ ret = IRQ_HANDLED;
+ }
+
+ if (interrupts & LPAIF_IRQ_ERR(chan)) {
+ rv = regmap_write_bits(map, reg, mask, (LPAIF_IRQ_ERR(chan) | val));
+ if (rv) {
+ dev_err(soc_runtime->dev,
+ "error writing to irqclear reg: %d\n", rv);
+ return IRQ_NONE;
+ }
+ dev_err(soc_runtime->dev, "bus access error\n");
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
+ ret = IRQ_HANDLED;
+ }
+
+ if (interrupts & val) {
+ rv = regmap_write(map, reg, val);
+ if (rv) {
+ dev_err(soc_runtime->dev,
+ "error writing to irqclear reg: %d\n", rv);
+ return IRQ_NONE;
+ }
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+static irqreturn_t lpass_platform_lpaif_irq(int irq, void *data)
+{
+ struct lpass_data *drvdata = data;
+ const struct lpass_variant *v = drvdata->variant;
+ unsigned int irqs;
+ int rv, chan;
+
+ rv = regmap_read(drvdata->lpaif_map,
+ LPAIF_IRQSTAT_REG(v, LPAIF_IRQ_PORT_HOST), &irqs);
+ if (rv) {
+ pr_err("error reading from irqstat reg: %d\n", rv);
+ return IRQ_NONE;
+ }
+
+ /* Handle per channel interrupts */
+ for (chan = 0; chan < LPASS_MAX_DMA_CHANNELS; chan++) {
+ if (irqs & LPAIF_IRQ_ALL(chan) && drvdata->substream[chan]) {
+ rv = lpass_dma_interrupt_handler(
+ drvdata->substream[chan],
+ drvdata, chan, irqs);
+ if (rv != IRQ_HANDLED)
+ return rv;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t lpass_platform_hdmiif_irq(int irq, void *data)
+{
+ struct lpass_data *drvdata = data;
+ const struct lpass_variant *v = drvdata->variant;
+ unsigned int irqs;
+ int rv, chan;
+
+ rv = regmap_read(drvdata->hdmiif_map,
+ LPASS_HDMITX_APP_IRQSTAT_REG(v), &irqs);
+ if (rv) {
+ pr_err("error reading from irqstat reg: %d\n", rv);
+ return IRQ_NONE;
+ }
+
+ /* Handle per channel interrupts */
+ for (chan = 0; chan < LPASS_MAX_HDMI_DMA_CHANNELS; chan++) {
+ if (irqs & (LPAIF_IRQ_ALL(chan) | LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(chan) |
+ LPAIF_IRQ_HDMI_METADONE |
+ LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(chan))
+ && drvdata->hdmi_substream[chan]) {
+ rv = lpass_dma_interrupt_handler(
+ drvdata->hdmi_substream[chan],
+ drvdata, chan, irqs);
+ if (rv != IRQ_HANDLED)
+ return rv;
+ }
+ }
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t lpass_platform_rxtxif_irq(int irq, void *data)
+{
+ struct lpass_data *drvdata = data;
+ const struct lpass_variant *v = drvdata->variant;
+ unsigned int irqs;
+ irqreturn_t rv;
+ int chan;
+
+ rv = regmap_read(drvdata->rxtx_lpaif_map,
+ LPAIF_RXTX_IRQSTAT_REG(v, LPAIF_IRQ_PORT_HOST), &irqs);
+
+ /* Handle per channel interrupts */
+ for (chan = 0; chan < LPASS_MAX_CDC_DMA_CHANNELS; chan++) {
+ if (irqs & LPAIF_IRQ_ALL(chan) && drvdata->rxtx_substream[chan]) {
+ rv = lpass_dma_interrupt_handler(
+ drvdata->rxtx_substream[chan],
+ drvdata, chan, irqs);
+ if (rv != IRQ_HANDLED)
+ return rv;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t lpass_platform_vaif_irq(int irq, void *data)
+{
+ struct lpass_data *drvdata = data;
+ const struct lpass_variant *v = drvdata->variant;
+ unsigned int irqs;
+ irqreturn_t rv;
+ int chan;
+
+ rv = regmap_read(drvdata->va_lpaif_map,
+ LPAIF_VA_IRQSTAT_REG(v, LPAIF_IRQ_PORT_HOST), &irqs);
+
+ /* Handle per channel interrupts */
+ for (chan = 0; chan < LPASS_MAX_VA_CDC_DMA_CHANNELS; chan++) {
+ if (irqs & LPAIF_IRQ_ALL(chan) && drvdata->va_substream[chan]) {
+ rv = lpass_dma_interrupt_handler(
+ drvdata->va_substream[chan],
+ drvdata, chan, irqs);
+ if (rv != IRQ_HANDLED)
+ return rv;
+ }
+ }
+ return IRQ_HANDLED;
+}
+
+static int lpass_platform_prealloc_cdc_dma_buffer(struct snd_soc_component *component,
+ struct snd_pcm *pcm, int dai_id)
+{
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
+ substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ else
+ substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+
+ buf = &substream->dma_buffer;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+
+ /* Assign Codec DMA buffer pointers */
+ buf->dev.type = SNDRV_DMA_TYPE_CONTINUOUS;
+
+ switch (dai_id) {
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ buf->bytes = lpass_platform_rxtx_hardware.buffer_bytes_max;
+ buf->addr = drvdata->rxtx_cdc_dma_lpm_buf;
+ break;
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ buf->bytes = lpass_platform_rxtx_hardware.buffer_bytes_max;
+ buf->addr = drvdata->rxtx_cdc_dma_lpm_buf + LPASS_RXTX_CDC_DMA_LPM_BUFF_SIZE;
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ buf->bytes = lpass_platform_va_hardware.buffer_bytes_max;
+ buf->addr = drvdata->va_cdc_dma_lpm_buf;
+ break;
+ default:
+ break;
+ }
+
+ buf->area = (unsigned char * __force)memremap(buf->addr, buf->bytes, MEMREMAP_WC);
+
+ return 0;
+}
+
+static int lpass_platform_pcm_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *soc_runtime)
+{
+ struct snd_pcm *pcm = soc_runtime->pcm;
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ unsigned int dai_id = cpu_dai->driver->id;
+
+ size_t size = lpass_platform_pcm_hardware.buffer_bytes_max;
+
+ /*
+ * Lpass codec dma can access only lpass lpm hardware memory.
+ * ioremap is for HLOS to access hardware memory.
+ */
+ if (is_cdc_dma_port(dai_id))
+ return lpass_platform_prealloc_cdc_dma_buffer(component, pcm, dai_id);
+
+ return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_NONCOHERENT,
+ component->dev, size);
+}
+
+static int lpass_platform_pcmops_suspend(struct snd_soc_component *component)
+{
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ struct regmap *map;
+
+ if (drvdata->hdmi_port_enable) {
+ map = drvdata->hdmiif_map;
+ regcache_cache_only(map, true);
+ regcache_mark_dirty(map);
+ }
+
+ map = drvdata->lpaif_map;
+ regcache_cache_only(map, true);
+ regcache_mark_dirty(map);
+
+ return 0;
+}
+
+static int lpass_platform_pcmops_resume(struct snd_soc_component *component)
+{
+ struct lpass_data *drvdata = snd_soc_component_get_drvdata(component);
+ struct regmap *map;
+ int ret;
+
+ if (drvdata->hdmi_port_enable) {
+ map = drvdata->hdmiif_map;
+ regcache_cache_only(map, false);
+ ret = regcache_sync(map);
+ if (ret)
+ return ret;
+ }
+
+ map = drvdata->lpaif_map;
+ regcache_cache_only(map, false);
+
+ return regcache_sync(map);
+}
+
+static int lpass_platform_copy(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int channel,
+ unsigned long pos, struct iov_iter *buf,
+ unsigned long bytes)
+{
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0);
+ unsigned int dai_id = cpu_dai->driver->id;
+ int ret = 0;
+
+ void __iomem *dma_buf = (void __iomem *) (rt->dma_area + pos +
+ channel * (rt->dma_bytes / rt->channels));
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (is_cdc_dma_port(dai_id)) {
+ if (copy_from_iter_toio(dma_buf, bytes, buf) != bytes)
+ ret = -EFAULT;
+ } else {
+ if (copy_from_iter((void __force *)dma_buf, bytes, buf) != bytes)
+ ret = -EFAULT;
+ }
+ } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ if (is_cdc_dma_port(dai_id)) {
+ if (copy_to_iter_fromio(dma_buf, bytes, buf) != bytes)
+ ret = -EFAULT;
+ } else {
+ if (copy_to_iter((void __force *)dma_buf, bytes, buf) != bytes)
+ ret = -EFAULT;
+ }
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_component_driver lpass_component_driver = {
+ .name = DRV_NAME,
+ .open = lpass_platform_pcmops_open,
+ .close = lpass_platform_pcmops_close,
+ .hw_params = lpass_platform_pcmops_hw_params,
+ .hw_free = lpass_platform_pcmops_hw_free,
+ .prepare = lpass_platform_pcmops_prepare,
+ .trigger = lpass_platform_pcmops_trigger,
+ .pointer = lpass_platform_pcmops_pointer,
+ .mmap = lpass_platform_pcmops_mmap,
+ .pcm_new = lpass_platform_pcm_new,
+ .suspend = lpass_platform_pcmops_suspend,
+ .resume = lpass_platform_pcmops_resume,
+ .copy = lpass_platform_copy,
+
+};
+
+int asoc_qcom_lpass_platform_register(struct platform_device *pdev)
+{
+ struct lpass_data *drvdata = platform_get_drvdata(pdev);
+ const struct lpass_variant *v = drvdata->variant;
+ int ret;
+
+ drvdata->lpaif_irq = platform_get_irq_byname(pdev, "lpass-irq-lpaif");
+ if (drvdata->lpaif_irq < 0)
+ return -ENODEV;
+
+ /* ensure audio hardware is disabled */
+ ret = regmap_write(drvdata->lpaif_map,
+ LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), 0);
+ if (ret) {
+ dev_err(&pdev->dev, "error writing to irqen reg: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_request_irq(&pdev->dev, drvdata->lpaif_irq,
+ lpass_platform_lpaif_irq, IRQF_TRIGGER_RISING,
+ "lpass-irq-lpaif", drvdata);
+ if (ret) {
+ dev_err(&pdev->dev, "irq request failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = lpass_platform_alloc_dmactl_fields(&pdev->dev,
+ drvdata->lpaif_map);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "error initializing dmactl fields: %d\n", ret);
+ return ret;
+ }
+
+ if (drvdata->codec_dma_enable) {
+ ret = regmap_write(drvdata->rxtx_lpaif_map,
+ LPAIF_RXTX_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), 0x0);
+ if (ret) {
+ dev_err(&pdev->dev, "error writing to rxtx irqen reg: %d\n", ret);
+ return ret;
+ }
+ ret = regmap_write(drvdata->va_lpaif_map,
+ LPAIF_VA_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), 0x0);
+ if (ret) {
+ dev_err(&pdev->dev, "error writing to rxtx irqen reg: %d\n", ret);
+ return ret;
+ }
+ drvdata->rxtxif_irq = platform_get_irq_byname(pdev, "lpass-irq-rxtxif");
+ if (drvdata->rxtxif_irq < 0)
+ return -ENODEV;
+
+ ret = devm_request_irq(&pdev->dev, drvdata->rxtxif_irq,
+ lpass_platform_rxtxif_irq, 0, "lpass-irq-rxtxif", drvdata);
+ if (ret) {
+ dev_err(&pdev->dev, "rxtx irq request failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = lpass_platform_alloc_rxtx_dmactl_fields(&pdev->dev,
+ drvdata->rxtx_lpaif_map);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "error initializing rxtx dmactl fields: %d\n", ret);
+ return ret;
+ }
+
+ drvdata->vaif_irq = platform_get_irq_byname(pdev, "lpass-irq-vaif");
+ if (drvdata->vaif_irq < 0)
+ return -ENODEV;
+
+ ret = devm_request_irq(&pdev->dev, drvdata->vaif_irq,
+ lpass_platform_vaif_irq, 0, "lpass-irq-vaif", drvdata);
+ if (ret) {
+ dev_err(&pdev->dev, "va irq request failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = lpass_platform_alloc_va_dmactl_fields(&pdev->dev,
+ drvdata->va_lpaif_map);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "error initializing va dmactl fields: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (drvdata->hdmi_port_enable) {
+ drvdata->hdmiif_irq = platform_get_irq_byname(pdev, "lpass-irq-hdmi");
+ if (drvdata->hdmiif_irq < 0)
+ return -ENODEV;
+
+ ret = devm_request_irq(&pdev->dev, drvdata->hdmiif_irq,
+ lpass_platform_hdmiif_irq, 0, "lpass-irq-hdmi", drvdata);
+ if (ret) {
+ dev_err(&pdev->dev, "irq hdmi request failed: %d\n", ret);
+ return ret;
+ }
+ ret = regmap_write(drvdata->hdmiif_map,
+ LPASS_HDMITX_APP_IRQEN_REG(v), 0);
+ if (ret) {
+ dev_err(&pdev->dev, "error writing to hdmi irqen reg: %d\n", ret);
+ return ret;
+ }
+
+ ret = lpass_platform_alloc_hdmidmactl_fields(&pdev->dev,
+ drvdata->hdmiif_map);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "error initializing hdmidmactl fields: %d\n", ret);
+ return ret;
+ }
+ }
+ return devm_snd_soc_register_component(&pdev->dev,
+ &lpass_component_driver, NULL, 0);
+}
+EXPORT_SYMBOL_GPL(asoc_qcom_lpass_platform_register);
+
+MODULE_DESCRIPTION("QTi LPASS Platform Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/lpass-sc7180.c b/sound/soc/qcom/lpass-sc7180.c
new file mode 100644
index 000000000..7c6a9b0fd
--- /dev/null
+++ b/sound/soc/qcom/lpass-sc7180.c
@@ -0,0 +1,325 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ *
+ * lpass-sc7180.c -- ALSA SoC platform-machine driver for QTi LPASS
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <dt-bindings/sound/sc7180-lpass.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include "lpass-lpaif-reg.h"
+#include "lpass.h"
+
+static struct snd_soc_dai_driver sc7180_lpass_cpu_dai_driver[] = {
+ {
+ .id = MI2S_PRIMARY,
+ .name = "Primary MI2S",
+ .playback = {
+ .stream_name = "Primary Playback",
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .capture = {
+ .stream_name = "Primary Capture",
+ .formats = SNDRV_PCM_FMTBIT_S16 |
+ SNDRV_PCM_FMTBIT_S32,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = &asoc_qcom_lpass_cpu_dai_ops,
+ }, {
+ .id = MI2S_SECONDARY,
+ .name = "Secondary MI2S",
+ .playback = {
+ .stream_name = "Secondary Playback",
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = &asoc_qcom_lpass_cpu_dai_ops2,
+ }, {
+ .id = LPASS_DP_RX,
+ .name = "Hdmi",
+ .playback = {
+ .stream_name = "Hdmi Playback",
+ .formats = SNDRV_PCM_FMTBIT_S24,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = &asoc_qcom_lpass_hdmi_dai_ops,
+ },
+};
+
+static int sc7180_lpass_alloc_dma_channel(struct lpass_data *drvdata,
+ int direction, unsigned int dai_id)
+{
+ const struct lpass_variant *v = drvdata->variant;
+ int chan = 0;
+
+ if (dai_id == LPASS_DP_RX) {
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ chan = find_first_zero_bit(&drvdata->hdmi_dma_ch_bit_map,
+ v->hdmi_rdma_channels);
+
+ if (chan >= v->hdmi_rdma_channels)
+ return -EBUSY;
+ }
+ set_bit(chan, &drvdata->hdmi_dma_ch_bit_map);
+ } else {
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ chan = find_first_zero_bit(&drvdata->dma_ch_bit_map,
+ v->rdma_channels);
+
+ if (chan >= v->rdma_channels)
+ return -EBUSY;
+ } else {
+ chan = find_next_zero_bit(&drvdata->dma_ch_bit_map,
+ v->wrdma_channel_start +
+ v->wrdma_channels,
+ v->wrdma_channel_start);
+
+ if (chan >= v->wrdma_channel_start + v->wrdma_channels)
+ return -EBUSY;
+ }
+
+ set_bit(chan, &drvdata->dma_ch_bit_map);
+ }
+ return chan;
+}
+
+static int sc7180_lpass_free_dma_channel(struct lpass_data *drvdata, int chan, unsigned int dai_id)
+{
+ if (dai_id == LPASS_DP_RX)
+ clear_bit(chan, &drvdata->hdmi_dma_ch_bit_map);
+ else
+ clear_bit(chan, &drvdata->dma_ch_bit_map);
+
+ return 0;
+}
+
+static int sc7180_lpass_init(struct platform_device *pdev)
+{
+ struct lpass_data *drvdata = platform_get_drvdata(pdev);
+ const struct lpass_variant *variant = drvdata->variant;
+ struct device *dev = &pdev->dev;
+ int ret, i;
+
+ drvdata->clks = devm_kcalloc(dev, variant->num_clks,
+ sizeof(*drvdata->clks), GFP_KERNEL);
+ if (!drvdata->clks)
+ return -ENOMEM;
+
+ drvdata->num_clks = variant->num_clks;
+
+ for (i = 0; i < drvdata->num_clks; i++)
+ drvdata->clks[i].id = variant->clk_name[i];
+
+ ret = devm_clk_bulk_get(dev, drvdata->num_clks, drvdata->clks);
+ if (ret) {
+ dev_err(dev, "Failed to get clocks %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_bulk_prepare_enable(drvdata->num_clks, drvdata->clks);
+ if (ret) {
+ dev_err(dev, "sc7180 clk_enable failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sc7180_lpass_exit(struct platform_device *pdev)
+{
+ struct lpass_data *drvdata = platform_get_drvdata(pdev);
+
+ clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks);
+ return 0;
+}
+
+static int sc7180_lpass_dev_resume(struct device *dev)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+
+ return clk_bulk_prepare_enable(drvdata->num_clks, drvdata->clks);
+}
+
+static int sc7180_lpass_dev_suspend(struct device *dev)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+
+ clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks);
+ return 0;
+}
+
+static const struct dev_pm_ops sc7180_lpass_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(sc7180_lpass_dev_suspend, sc7180_lpass_dev_resume)
+};
+
+static const struct lpass_variant sc7180_data = {
+ .i2sctrl_reg_base = 0x1000,
+ .i2sctrl_reg_stride = 0x1000,
+ .i2s_ports = 3,
+ .irq_reg_base = 0x9000,
+ .irq_reg_stride = 0x1000,
+ .irq_ports = 3,
+ .rdma_reg_base = 0xC000,
+ .rdma_reg_stride = 0x1000,
+ .rdma_channels = 5,
+ .hdmi_rdma_reg_base = 0x64000,
+ .hdmi_rdma_reg_stride = 0x1000,
+ .hdmi_rdma_channels = 4,
+ .dmactl_audif_start = 1,
+ .wrdma_reg_base = 0x18000,
+ .wrdma_reg_stride = 0x1000,
+ .wrdma_channel_start = 5,
+ .wrdma_channels = 4,
+
+ .loopback = REG_FIELD_ID(0x1000, 17, 17, 3, 0x1000),
+ .spken = REG_FIELD_ID(0x1000, 16, 16, 3, 0x1000),
+ .spkmode = REG_FIELD_ID(0x1000, 11, 15, 3, 0x1000),
+ .spkmono = REG_FIELD_ID(0x1000, 10, 10, 3, 0x1000),
+ .micen = REG_FIELD_ID(0x1000, 9, 9, 3, 0x1000),
+ .micmode = REG_FIELD_ID(0x1000, 4, 8, 3, 0x1000),
+ .micmono = REG_FIELD_ID(0x1000, 3, 3, 3, 0x1000),
+ .wssrc = REG_FIELD_ID(0x1000, 2, 2, 3, 0x1000),
+ .bitwidth = REG_FIELD_ID(0x1000, 0, 1, 3, 0x1000),
+
+ .rdma_dyncclk = REG_FIELD_ID(0xC000, 21, 21, 5, 0x1000),
+ .rdma_bursten = REG_FIELD_ID(0xC000, 20, 20, 5, 0x1000),
+ .rdma_wpscnt = REG_FIELD_ID(0xC000, 16, 19, 5, 0x1000),
+ .rdma_intf = REG_FIELD_ID(0xC000, 12, 15, 5, 0x1000),
+ .rdma_fifowm = REG_FIELD_ID(0xC000, 1, 5, 5, 0x1000),
+ .rdma_enable = REG_FIELD_ID(0xC000, 0, 0, 5, 0x1000),
+
+ .wrdma_dyncclk = REG_FIELD_ID(0x18000, 22, 22, 4, 0x1000),
+ .wrdma_bursten = REG_FIELD_ID(0x18000, 21, 21, 4, 0x1000),
+ .wrdma_wpscnt = REG_FIELD_ID(0x18000, 17, 20, 4, 0x1000),
+ .wrdma_intf = REG_FIELD_ID(0x18000, 12, 16, 4, 0x1000),
+ .wrdma_fifowm = REG_FIELD_ID(0x18000, 1, 5, 4, 0x1000),
+ .wrdma_enable = REG_FIELD_ID(0x18000, 0, 0, 4, 0x1000),
+
+ .hdmi_tx_ctl_addr = 0x1000,
+ .hdmi_legacy_addr = 0x1008,
+ .hdmi_vbit_addr = 0x610c0,
+ .hdmi_ch_lsb_addr = 0x61048,
+ .hdmi_ch_msb_addr = 0x6104c,
+ .ch_stride = 0x8,
+ .hdmi_parity_addr = 0x61034,
+ .hdmi_dmactl_addr = 0x61038,
+ .hdmi_dma_stride = 0x4,
+ .hdmi_DP_addr = 0x610c8,
+ .hdmi_sstream_addr = 0x6101c,
+ .hdmi_irq_reg_base = 0x63000,
+ .hdmi_irq_ports = 1,
+
+ .hdmi_rdma_dyncclk = REG_FIELD_ID(0x64000, 14, 14, 4, 0x1000),
+ .hdmi_rdma_bursten = REG_FIELD_ID(0x64000, 13, 13, 4, 0x1000),
+ .hdmi_rdma_burst8 = REG_FIELD_ID(0x64000, 15, 15, 4, 0x1000),
+ .hdmi_rdma_burst16 = REG_FIELD_ID(0x64000, 16, 16, 4, 0x1000),
+ .hdmi_rdma_dynburst = REG_FIELD_ID(0x64000, 18, 18, 4, 0x1000),
+ .hdmi_rdma_wpscnt = REG_FIELD_ID(0x64000, 10, 12, 4, 0x1000),
+ .hdmi_rdma_fifowm = REG_FIELD_ID(0x64000, 1, 5, 4, 0x1000),
+ .hdmi_rdma_enable = REG_FIELD_ID(0x64000, 0, 0, 4, 0x1000),
+
+ .sstream_en = REG_FIELD(0x6101c, 0, 0),
+ .dma_sel = REG_FIELD(0x6101c, 1, 2),
+ .auto_bbit_en = REG_FIELD(0x6101c, 3, 3),
+ .layout = REG_FIELD(0x6101c, 4, 4),
+ .layout_sp = REG_FIELD(0x6101c, 5, 8),
+ .set_sp_on_en = REG_FIELD(0x6101c, 10, 10),
+ .dp_audio = REG_FIELD(0x6101c, 11, 11),
+ .dp_staffing_en = REG_FIELD(0x6101c, 12, 12),
+ .dp_sp_b_hw_en = REG_FIELD(0x6101c, 13, 13),
+
+ .mute = REG_FIELD(0x610c8, 0, 0),
+ .as_sdp_cc = REG_FIELD(0x610c8, 1, 3),
+ .as_sdp_ct = REG_FIELD(0x610c8, 4, 7),
+ .aif_db4 = REG_FIELD(0x610c8, 8, 15),
+ .frequency = REG_FIELD(0x610c8, 16, 21),
+ .mst_index = REG_FIELD(0x610c8, 28, 29),
+ .dptx_index = REG_FIELD(0x610c8, 30, 31),
+
+ .soft_reset = REG_FIELD(0x1000, 31, 31),
+ .force_reset = REG_FIELD(0x1000, 30, 30),
+
+ .use_hw_chs = REG_FIELD(0x61038, 0, 0),
+ .use_hw_usr = REG_FIELD(0x61038, 1, 1),
+ .hw_chs_sel = REG_FIELD(0x61038, 2, 4),
+ .hw_usr_sel = REG_FIELD(0x61038, 5, 6),
+
+ .replace_vbit = REG_FIELD(0x610c0, 0, 0),
+ .vbit_stream = REG_FIELD(0x610c0, 1, 1),
+
+ .legacy_en = REG_FIELD(0x1008, 0, 0),
+ .calc_en = REG_FIELD(0x61034, 0, 0),
+ .lsb_bits = REG_FIELD(0x61048, 0, 31),
+ .msb_bits = REG_FIELD(0x6104c, 0, 31),
+
+
+ .clk_name = (const char*[]) {
+ "pcnoc-sway-clk",
+ "audio-core",
+ "pcnoc-mport-clk",
+ },
+ .num_clks = 3,
+ .dai_driver = sc7180_lpass_cpu_dai_driver,
+ .num_dai = ARRAY_SIZE(sc7180_lpass_cpu_dai_driver),
+ .dai_osr_clk_names = (const char *[]) {
+ "mclk0",
+ "null",
+ },
+ .dai_bit_clk_names = (const char *[]) {
+ "mi2s-bit-clk0",
+ "mi2s-bit-clk1",
+ },
+ .init = sc7180_lpass_init,
+ .exit = sc7180_lpass_exit,
+ .alloc_dma_channel = sc7180_lpass_alloc_dma_channel,
+ .free_dma_channel = sc7180_lpass_free_dma_channel,
+};
+
+static const struct of_device_id sc7180_lpass_cpu_device_id[] __maybe_unused = {
+ {.compatible = "qcom,sc7180-lpass-cpu", .data = &sc7180_data},
+ {}
+};
+MODULE_DEVICE_TABLE(of, sc7180_lpass_cpu_device_id);
+
+static struct platform_driver sc7180_lpass_cpu_platform_driver = {
+ .driver = {
+ .name = "sc7180-lpass-cpu",
+ .of_match_table = of_match_ptr(sc7180_lpass_cpu_device_id),
+ .pm = pm_ptr(&sc7180_lpass_pm_ops),
+ },
+ .probe = asoc_qcom_lpass_cpu_platform_probe,
+ .remove = asoc_qcom_lpass_cpu_platform_remove,
+ .shutdown = asoc_qcom_lpass_cpu_platform_shutdown,
+};
+
+module_platform_driver(sc7180_lpass_cpu_platform_driver);
+
+MODULE_DESCRIPTION("SC7180 LPASS CPU DRIVER");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/lpass-sc7280.c b/sound/soc/qcom/lpass-sc7280.c
new file mode 100644
index 000000000..817c824f9
--- /dev/null
+++ b/sound/soc/qcom/lpass-sc7280.c
@@ -0,0 +1,455 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
+ *
+ * lpass-sc7180.c -- ALSA SoC platform-machine driver for QTi LPASS
+ */
+
+#include <linux/module.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <linux/pm.h>
+
+#include <dt-bindings/sound/sc7180-lpass.h>
+
+#include "lpass-lpaif-reg.h"
+#include "lpass.h"
+
+static struct snd_soc_dai_driver sc7280_lpass_cpu_dai_driver[] = {
+ {
+ .id = MI2S_PRIMARY,
+ .name = "Primary MI2S",
+ .playback = {
+ .stream_name = "Primary Playback",
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .capture = {
+ .stream_name = "Primary Capture",
+ .formats = SNDRV_PCM_FMTBIT_S16 |
+ SNDRV_PCM_FMTBIT_S32,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = &asoc_qcom_lpass_cpu_dai_ops,
+ }, {
+ .id = MI2S_SECONDARY,
+ .name = "Secondary MI2S",
+ .playback = {
+ .stream_name = "Secondary MI2S Playback",
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = &asoc_qcom_lpass_cpu_dai_ops,
+ }, {
+ .id = LPASS_DP_RX,
+ .name = "Hdmi",
+ .playback = {
+ .stream_name = "DP Playback",
+ .formats = SNDRV_PCM_FMTBIT_S24,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = &asoc_qcom_lpass_hdmi_dai_ops,
+ }, {
+ .id = LPASS_CDC_DMA_RX0,
+ .name = "CDC DMA RX",
+ .playback = {
+ .stream_name = "WCD Playback",
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = &asoc_qcom_lpass_cdc_dma_dai_ops,
+ }, {
+ .id = LPASS_CDC_DMA_TX3,
+ .name = "CDC DMA TX",
+ .capture = {
+ .stream_name = "WCD Capture",
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 1,
+ },
+ .ops = &asoc_qcom_lpass_cdc_dma_dai_ops,
+ }, {
+ .id = LPASS_CDC_DMA_VA_TX0,
+ .name = "CDC DMA VA",
+ .capture = {
+ .stream_name = "DMIC Capture",
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 4,
+ },
+ .ops = &asoc_qcom_lpass_cdc_dma_dai_ops,
+ },
+};
+
+static int sc7280_lpass_alloc_dma_channel(struct lpass_data *drvdata,
+ int direction, unsigned int dai_id)
+{
+ const struct lpass_variant *v = drvdata->variant;
+ int chan = 0;
+
+ switch (dai_id) {
+ case MI2S_PRIMARY ... MI2S_QUINARY:
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ chan = find_first_zero_bit(&drvdata->dma_ch_bit_map,
+ v->rdma_channels);
+
+ if (chan >= v->rdma_channels)
+ return -EBUSY;
+ } else {
+ chan = find_next_zero_bit(&drvdata->dma_ch_bit_map,
+ v->wrdma_channel_start +
+ v->wrdma_channels,
+ v->wrdma_channel_start);
+
+ if (chan >= v->wrdma_channel_start + v->wrdma_channels)
+ return -EBUSY;
+ }
+ set_bit(chan, &drvdata->dma_ch_bit_map);
+ break;
+ case LPASS_DP_RX:
+ chan = find_first_zero_bit(&drvdata->hdmi_dma_ch_bit_map,
+ v->hdmi_rdma_channels);
+ if (chan >= v->hdmi_rdma_channels)
+ return -EBUSY;
+ set_bit(chan, &drvdata->hdmi_dma_ch_bit_map);
+ break;
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ chan = find_first_zero_bit(&drvdata->rxtx_dma_ch_bit_map,
+ v->rxtx_rdma_channels);
+ if (chan >= v->rxtx_rdma_channels)
+ return -EBUSY;
+ break;
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ chan = find_next_zero_bit(&drvdata->rxtx_dma_ch_bit_map,
+ v->rxtx_wrdma_channel_start +
+ v->rxtx_wrdma_channels,
+ v->rxtx_wrdma_channel_start);
+ if (chan >= v->rxtx_wrdma_channel_start + v->rxtx_wrdma_channels)
+ return -EBUSY;
+ set_bit(chan, &drvdata->rxtx_dma_ch_bit_map);
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ chan = find_next_zero_bit(&drvdata->va_dma_ch_bit_map,
+ v->va_wrdma_channel_start +
+ v->va_wrdma_channels,
+ v->va_wrdma_channel_start);
+ if (chan >= v->va_wrdma_channel_start + v->va_wrdma_channels)
+ return -EBUSY;
+ set_bit(chan, &drvdata->va_dma_ch_bit_map);
+ break;
+ default:
+ break;
+ }
+
+ return chan;
+}
+
+static int sc7280_lpass_free_dma_channel(struct lpass_data *drvdata, int chan, unsigned int dai_id)
+{
+ switch (dai_id) {
+ case MI2S_PRIMARY ... MI2S_QUINARY:
+ clear_bit(chan, &drvdata->dma_ch_bit_map);
+ break;
+ case LPASS_DP_RX:
+ clear_bit(chan, &drvdata->hdmi_dma_ch_bit_map);
+ break;
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ clear_bit(chan, &drvdata->rxtx_dma_ch_bit_map);
+ break;
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ clear_bit(chan, &drvdata->va_dma_ch_bit_map);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int sc7280_lpass_init(struct platform_device *pdev)
+{
+ struct lpass_data *drvdata = platform_get_drvdata(pdev);
+ const struct lpass_variant *variant = drvdata->variant;
+ struct device *dev = &pdev->dev;
+ int ret, i;
+
+ drvdata->clks = devm_kcalloc(dev, variant->num_clks,
+ sizeof(*drvdata->clks), GFP_KERNEL);
+ if (!drvdata->clks)
+ return -ENOMEM;
+
+ drvdata->num_clks = variant->num_clks;
+
+ for (i = 0; i < drvdata->num_clks; i++)
+ drvdata->clks[i].id = variant->clk_name[i];
+
+ ret = devm_clk_bulk_get(dev, drvdata->num_clks, drvdata->clks);
+ if (ret) {
+ dev_err(dev, "Failed to get clocks %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_bulk_prepare_enable(drvdata->num_clks, drvdata->clks);
+ if (ret) {
+ dev_err(dev, "sc7280 clk_enable failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sc7280_lpass_exit(struct platform_device *pdev)
+{
+ struct lpass_data *drvdata = platform_get_drvdata(pdev);
+
+ clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks);
+ return 0;
+}
+
+static int sc7280_lpass_dev_resume(struct device *dev)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+
+ return clk_bulk_prepare_enable(drvdata->num_clks, drvdata->clks);
+}
+
+static int sc7280_lpass_dev_suspend(struct device *dev)
+{
+ struct lpass_data *drvdata = dev_get_drvdata(dev);
+
+ clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks);
+ return 0;
+}
+
+static const struct dev_pm_ops sc7280_lpass_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(sc7280_lpass_dev_suspend, sc7280_lpass_dev_resume)
+};
+
+static const struct lpass_variant sc7280_data = {
+ .i2sctrl_reg_base = 0x1000,
+ .i2sctrl_reg_stride = 0x1000,
+ .i2s_ports = 3,
+ .irq_reg_base = 0x9000,
+ .irq_reg_stride = 0x1000,
+ .irq_ports = 3,
+ .rdma_reg_base = 0xC000,
+ .rdma_reg_stride = 0x1000,
+ .rdma_channels = 5,
+ .rxtx_rdma_reg_base = 0xC000,
+ .rxtx_rdma_reg_stride = 0x1000,
+ .rxtx_rdma_channels = 8,
+ .hdmi_rdma_reg_base = 0x64000,
+ .hdmi_rdma_reg_stride = 0x1000,
+ .hdmi_rdma_channels = 4,
+ .dmactl_audif_start = 1,
+ .wrdma_reg_base = 0x18000,
+ .wrdma_reg_stride = 0x1000,
+ .wrdma_channel_start = 5,
+ .wrdma_channels = 4,
+ .rxtx_irq_reg_base = 0x9000,
+ .rxtx_irq_reg_stride = 0x1000,
+ .rxtx_irq_ports = 3,
+ .rxtx_wrdma_reg_base = 0x18000,
+ .rxtx_wrdma_reg_stride = 0x1000,
+ .rxtx_wrdma_channel_start = 5,
+ .rxtx_wrdma_channels = 6,
+ .va_wrdma_reg_base = 0x18000,
+ .va_wrdma_reg_stride = 0x1000,
+ .va_wrdma_channel_start = 5,
+ .va_wrdma_channels = 3,
+ .va_irq_reg_base = 0x9000,
+ .va_irq_reg_stride = 0x1000,
+ .va_irq_ports = 3,
+
+ .loopback = REG_FIELD_ID(0x1000, 17, 17, 3, 0x1000),
+ .spken = REG_FIELD_ID(0x1000, 16, 16, 3, 0x1000),
+ .spkmode = REG_FIELD_ID(0x1000, 11, 15, 3, 0x1000),
+ .spkmono = REG_FIELD_ID(0x1000, 10, 10, 3, 0x1000),
+ .micen = REG_FIELD_ID(0x1000, 9, 9, 3, 0x1000),
+ .micmode = REG_FIELD_ID(0x1000, 4, 8, 3, 0x1000),
+ .micmono = REG_FIELD_ID(0x1000, 3, 3, 3, 0x1000),
+ .wssrc = REG_FIELD_ID(0x1000, 2, 2, 3, 0x1000),
+ .bitwidth = REG_FIELD_ID(0x1000, 0, 1, 3, 0x1000),
+
+ .rdma_dyncclk = REG_FIELD_ID(0xC000, 21, 21, 5, 0x1000),
+ .rdma_bursten = REG_FIELD_ID(0xC000, 20, 20, 5, 0x1000),
+ .rdma_wpscnt = REG_FIELD_ID(0xC000, 16, 19, 5, 0x1000),
+ .rdma_intf = REG_FIELD_ID(0xC000, 12, 15, 5, 0x1000),
+ .rdma_fifowm = REG_FIELD_ID(0xC000, 1, 5, 5, 0x1000),
+ .rdma_enable = REG_FIELD_ID(0xC000, 0, 0, 5, 0x1000),
+
+ .wrdma_dyncclk = REG_FIELD_ID(0x18000, 22, 22, 4, 0x1000),
+ .wrdma_bursten = REG_FIELD_ID(0x18000, 21, 21, 4, 0x1000),
+ .wrdma_wpscnt = REG_FIELD_ID(0x18000, 17, 20, 4, 0x1000),
+ .wrdma_intf = REG_FIELD_ID(0x18000, 12, 16, 4, 0x1000),
+ .wrdma_fifowm = REG_FIELD_ID(0x18000, 1, 5, 4, 0x1000),
+ .wrdma_enable = REG_FIELD_ID(0x18000, 0, 0, 4, 0x1000),
+
+ .rxtx_rdma_enable = REG_FIELD_ID(0xC000, 0, 0, 7, 0x1000),
+ .rxtx_rdma_fifowm = REG_FIELD_ID(0xC000, 1, 11, 7, 0x1000),
+ .rxtx_rdma_intf = REG_FIELD_ID(0xC000, 12, 15, 7, 0x1000),
+ .rxtx_rdma_wpscnt = REG_FIELD_ID(0xC000, 16, 19, 7, 0x1000),
+ .rxtx_rdma_bursten = REG_FIELD_ID(0xC000, 20, 20, 7, 0x1000),
+ .rxtx_rdma_dyncclk = REG_FIELD_ID(0xC000, 21, 21, 7, 0x1000),
+
+ .rxtx_rdma_codec_ch = REG_FIELD_ID(0xC050, 0, 7, 7, 0x1000),
+ .rxtx_rdma_codec_intf = REG_FIELD_ID(0xC050, 16, 19, 7, 0x1000),
+ .rxtx_rdma_codec_fs_delay = REG_FIELD_ID(0xC050, 21, 24, 7, 0x1000),
+ .rxtx_rdma_codec_fs_sel = REG_FIELD_ID(0xC050, 25, 27, 7, 0x1000),
+ .rxtx_rdma_codec_pack = REG_FIELD_ID(0xC050, 29, 29, 5, 0x1000),
+ .rxtx_rdma_codec_enable = REG_FIELD_ID(0xC050, 30, 30, 7, 0x1000),
+
+ .rxtx_wrdma_enable = REG_FIELD_ID(0x18000, 0, 0, 5, 0x1000),
+ .rxtx_wrdma_fifowm = REG_FIELD_ID(0x18000, 1, 11, 5, 0x1000),
+ .rxtx_wrdma_intf = REG_FIELD_ID(0x18000, 12, 16, 5, 0x1000),
+ .rxtx_wrdma_wpscnt = REG_FIELD_ID(0x18000, 17, 20, 5, 0x1000),
+ .rxtx_wrdma_bursten = REG_FIELD_ID(0x18000, 21, 21, 5, 0x1000),
+ .rxtx_wrdma_dyncclk = REG_FIELD_ID(0x18000, 22, 22, 5, 0x1000),
+
+ .rxtx_wrdma_codec_ch = REG_FIELD_ID(0x18050, 0, 7, 5, 0x1000),
+ .rxtx_wrdma_codec_intf = REG_FIELD_ID(0x18050, 16, 19, 5, 0x1000),
+ .rxtx_wrdma_codec_fs_delay = REG_FIELD_ID(0x18050, 21, 24, 5, 0x1000),
+ .rxtx_wrdma_codec_fs_sel = REG_FIELD_ID(0x18050, 25, 27, 5, 0x1000),
+ .rxtx_wrdma_codec_pack = REG_FIELD_ID(0x18050, 29, 29, 5, 0x1000),
+ .rxtx_wrdma_codec_enable = REG_FIELD_ID(0x18050, 30, 30, 5, 0x1000),
+
+ .va_wrdma_enable = REG_FIELD_ID(0x18000, 0, 0, 5, 0x1000),
+ .va_wrdma_fifowm = REG_FIELD_ID(0x18000, 1, 11, 5, 0x1000),
+ .va_wrdma_intf = REG_FIELD_ID(0x18000, 12, 16, 5, 0x1000),
+ .va_wrdma_wpscnt = REG_FIELD_ID(0x18000, 17, 20, 5, 0x1000),
+ .va_wrdma_bursten = REG_FIELD_ID(0x18000, 21, 21, 5, 0x1000),
+ .va_wrdma_dyncclk = REG_FIELD_ID(0x18000, 22, 22, 5, 0x1000),
+
+ .va_wrdma_codec_ch = REG_FIELD_ID(0x18050, 0, 7, 5, 0x1000),
+ .va_wrdma_codec_intf = REG_FIELD_ID(0x18050, 16, 19, 5, 0x1000),
+ .va_wrdma_codec_fs_delay = REG_FIELD_ID(0x18050, 21, 24, 5, 0x1000),
+ .va_wrdma_codec_fs_sel = REG_FIELD_ID(0x18050, 25, 27, 5, 0x1000),
+ .va_wrdma_codec_pack = REG_FIELD_ID(0x18050, 29, 29, 5, 0x1000),
+ .va_wrdma_codec_enable = REG_FIELD_ID(0x18050, 30, 30, 5, 0x1000),
+
+ .hdmi_tx_ctl_addr = 0x1000,
+ .hdmi_legacy_addr = 0x1008,
+ .hdmi_vbit_addr = 0x610c0,
+ .hdmi_ch_lsb_addr = 0x61048,
+ .hdmi_ch_msb_addr = 0x6104c,
+ .ch_stride = 0x8,
+ .hdmi_parity_addr = 0x61034,
+ .hdmi_dmactl_addr = 0x61038,
+ .hdmi_dma_stride = 0x4,
+ .hdmi_DP_addr = 0x610c8,
+ .hdmi_sstream_addr = 0x6101c,
+ .hdmi_irq_reg_base = 0x63000,
+ .hdmi_irq_ports = 1,
+
+ .hdmi_rdma_dyncclk = REG_FIELD_ID(0x64000, 14, 14, 4, 0x1000),
+ .hdmi_rdma_bursten = REG_FIELD_ID(0x64000, 13, 13, 4, 0x1000),
+ .hdmi_rdma_burst8 = REG_FIELD_ID(0x64000, 15, 15, 4, 0x1000),
+ .hdmi_rdma_burst16 = REG_FIELD_ID(0x64000, 16, 16, 4, 0x1000),
+ .hdmi_rdma_dynburst = REG_FIELD_ID(0x64000, 18, 18, 4, 0x1000),
+ .hdmi_rdma_wpscnt = REG_FIELD_ID(0x64000, 10, 12, 4, 0x1000),
+ .hdmi_rdma_fifowm = REG_FIELD_ID(0x64000, 1, 5, 4, 0x1000),
+ .hdmi_rdma_enable = REG_FIELD_ID(0x64000, 0, 0, 4, 0x1000),
+
+ .sstream_en = REG_FIELD(0x6101c, 0, 0),
+ .dma_sel = REG_FIELD(0x6101c, 1, 2),
+ .auto_bbit_en = REG_FIELD(0x6101c, 3, 3),
+ .layout = REG_FIELD(0x6101c, 4, 4),
+ .layout_sp = REG_FIELD(0x6101c, 5, 8),
+ .set_sp_on_en = REG_FIELD(0x6101c, 10, 10),
+ .dp_audio = REG_FIELD(0x6101c, 11, 11),
+ .dp_staffing_en = REG_FIELD(0x6101c, 12, 12),
+ .dp_sp_b_hw_en = REG_FIELD(0x6101c, 13, 13),
+
+ .mute = REG_FIELD(0x610c8, 0, 0),
+ .as_sdp_cc = REG_FIELD(0x610c8, 1, 3),
+ .as_sdp_ct = REG_FIELD(0x610c8, 4, 7),
+ .aif_db4 = REG_FIELD(0x610c8, 8, 15),
+ .frequency = REG_FIELD(0x610c8, 16, 21),
+ .mst_index = REG_FIELD(0x610c8, 28, 29),
+ .dptx_index = REG_FIELD(0x610c8, 30, 31),
+
+ .soft_reset = REG_FIELD(0x1000, 31, 31),
+ .force_reset = REG_FIELD(0x1000, 30, 30),
+
+ .use_hw_chs = REG_FIELD(0x61038, 0, 0),
+ .use_hw_usr = REG_FIELD(0x61038, 1, 1),
+ .hw_chs_sel = REG_FIELD(0x61038, 2, 4),
+ .hw_usr_sel = REG_FIELD(0x61038, 5, 6),
+
+ .replace_vbit = REG_FIELD(0x610c0, 0, 0),
+ .vbit_stream = REG_FIELD(0x610c0, 1, 1),
+
+ .legacy_en = REG_FIELD(0x1008, 0, 0),
+ .calc_en = REG_FIELD(0x61034, 0, 0),
+ .lsb_bits = REG_FIELD(0x61048, 0, 31),
+ .msb_bits = REG_FIELD(0x6104c, 0, 31),
+
+ .clk_name = (const char*[]) {
+ "core_cc_sysnoc_mport_core"
+ },
+ .num_clks = 1,
+
+ .dai_driver = sc7280_lpass_cpu_dai_driver,
+ .num_dai = ARRAY_SIZE(sc7280_lpass_cpu_dai_driver),
+ .dai_osr_clk_names = (const char *[]) {
+ "audio_cc_ext_mclk0",
+ "null"
+ },
+ .dai_bit_clk_names = (const char *[]) {
+ "core_cc_ext_if0_ibit",
+ "core_cc_ext_if1_ibit"
+ },
+ .init = sc7280_lpass_init,
+ .exit = sc7280_lpass_exit,
+ .alloc_dma_channel = sc7280_lpass_alloc_dma_channel,
+ .free_dma_channel = sc7280_lpass_free_dma_channel,
+};
+
+static const struct of_device_id sc7280_lpass_cpu_device_id[] = {
+ {.compatible = "qcom,sc7280-lpass-cpu", .data = &sc7280_data},
+ {}
+};
+MODULE_DEVICE_TABLE(of, sc7280_lpass_cpu_device_id);
+
+static struct platform_driver sc7280_lpass_cpu_platform_driver = {
+ .driver = {
+ .name = "sc7280-lpass-cpu",
+ .of_match_table = of_match_ptr(sc7280_lpass_cpu_device_id),
+ .pm = pm_ptr(&sc7280_lpass_pm_ops),
+ },
+ .probe = asoc_qcom_lpass_cpu_platform_probe,
+ .remove = asoc_qcom_lpass_cpu_platform_remove,
+ .shutdown = asoc_qcom_lpass_cpu_platform_shutdown,
+};
+
+module_platform_driver(sc7280_lpass_cpu_platform_driver);
+
+MODULE_DESCRIPTION("SC7280 LPASS CPU DRIVER");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h
new file mode 100644
index 000000000..68b71039b
--- /dev/null
+++ b/sound/soc/qcom/lpass.h
@@ -0,0 +1,411 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2010-2011,2013-2015,2020 The Linux Foundation. All rights reserved.
+ *
+ * lpass.h - Definitions for the QTi LPASS
+ */
+
+#ifndef __LPASS_H__
+#define __LPASS_H__
+
+#include <linux/clk.h>
+#include <linux/compiler.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <dt-bindings/sound/qcom,lpass.h>
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include "common.h"
+#include "lpass-hdmi.h"
+
+#define LPASS_AHBIX_CLOCK_FREQUENCY 131072000
+#define LPASS_MAX_PORTS (LPASS_MAX_PORT)
+#define LPASS_MAX_MI2S_PORTS (8)
+#define LPASS_MAX_DMA_CHANNELS (8)
+#define LPASS_MAX_HDMI_DMA_CHANNELS (4)
+#define LPASS_MAX_CDC_DMA_CHANNELS (8)
+#define LPASS_MAX_VA_CDC_DMA_CHANNELS (8)
+#define LPASS_CDC_DMA_INTF_ONE_CHANNEL (0x01)
+#define LPASS_CDC_DMA_INTF_TWO_CHANNEL (0x03)
+#define LPASS_CDC_DMA_INTF_FOUR_CHANNEL (0x0F)
+#define LPASS_CDC_DMA_INTF_SIX_CHANNEL (0x3F)
+#define LPASS_CDC_DMA_INTF_EIGHT_CHANNEL (0xFF)
+
+#define LPASS_ACTIVE_PDS (4)
+#define LPASS_PROXY_PDS (8)
+
+#define QCOM_REGMAP_FIELD_ALLOC(d, m, f, mf) \
+ do { \
+ mf = devm_regmap_field_alloc(d, m, f); \
+ if (IS_ERR(mf)) \
+ return -EINVAL; \
+ } while (0)
+
+static inline bool is_cdc_dma_port(int dai_id)
+{
+ switch (dai_id) {
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8:
+ return true;
+ }
+ return false;
+}
+
+static inline bool is_rxtx_cdc_dma_port(int dai_id)
+{
+ switch (dai_id) {
+ case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9:
+ case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8:
+ return true;
+ }
+ return false;
+}
+
+struct lpaif_i2sctl {
+ struct regmap_field *loopback;
+ struct regmap_field *spken;
+ struct regmap_field *spkmode;
+ struct regmap_field *spkmono;
+ struct regmap_field *micen;
+ struct regmap_field *micmode;
+ struct regmap_field *micmono;
+ struct regmap_field *wssrc;
+ struct regmap_field *bitwidth;
+};
+
+
+struct lpaif_dmactl {
+ struct regmap_field *intf;
+ struct regmap_field *bursten;
+ struct regmap_field *wpscnt;
+ struct regmap_field *fifowm;
+ struct regmap_field *enable;
+ struct regmap_field *dyncclk;
+ struct regmap_field *burst8;
+ struct regmap_field *burst16;
+ struct regmap_field *dynburst;
+ struct regmap_field *codec_enable;
+ struct regmap_field *codec_pack;
+ struct regmap_field *codec_intf;
+ struct regmap_field *codec_fs_sel;
+ struct regmap_field *codec_channel;
+ struct regmap_field *codec_fs_delay;
+};
+
+/* Both the CPU DAI and platform drivers will access this data */
+struct lpass_data {
+
+ /* AHB-I/X bus clocks inside the low-power audio subsystem (LPASS) */
+ struct clk *ahbix_clk;
+
+ /* MI2S system clock */
+ struct clk *mi2s_osr_clk[LPASS_MAX_MI2S_PORTS];
+
+ /* MI2S bit clock (derived from system clock by a divider */
+ struct clk *mi2s_bit_clk[LPASS_MAX_MI2S_PORTS];
+
+ struct clk *codec_mem0;
+ struct clk *codec_mem1;
+ struct clk *codec_mem2;
+ struct clk *va_mem0;
+
+ /* MI2S SD lines to use for playback/capture */
+ unsigned int mi2s_playback_sd_mode[LPASS_MAX_MI2S_PORTS];
+ unsigned int mi2s_capture_sd_mode[LPASS_MAX_MI2S_PORTS];
+
+ /* The state of MI2S prepare dai_ops was called */
+ bool mi2s_was_prepared[LPASS_MAX_MI2S_PORTS];
+
+ int hdmi_port_enable;
+ int codec_dma_enable;
+
+ /* low-power audio interface (LPAIF) registers */
+ void __iomem *lpaif;
+ void __iomem *hdmiif;
+ void __iomem *rxtx_lpaif;
+ void __iomem *va_lpaif;
+
+ u32 rxtx_cdc_dma_lpm_buf;
+ u32 va_cdc_dma_lpm_buf;
+
+ /* regmap backed by the low-power audio interface (LPAIF) registers */
+ struct regmap *lpaif_map;
+ struct regmap *hdmiif_map;
+ struct regmap *rxtx_lpaif_map;
+ struct regmap *va_lpaif_map;
+
+ /* interrupts from the low-power audio interface (LPAIF) */
+ int lpaif_irq;
+ int hdmiif_irq;
+ int rxtxif_irq;
+ int vaif_irq;
+
+ /* SOC specific variations in the LPASS IP integration */
+ const struct lpass_variant *variant;
+
+ /* bit map to keep track of static channel allocations */
+ unsigned long dma_ch_bit_map;
+ unsigned long hdmi_dma_ch_bit_map;
+ unsigned long rxtx_dma_ch_bit_map;
+ unsigned long va_dma_ch_bit_map;
+
+ /* used it for handling interrupt per dma channel */
+ struct snd_pcm_substream *substream[LPASS_MAX_DMA_CHANNELS];
+ struct snd_pcm_substream *hdmi_substream[LPASS_MAX_HDMI_DMA_CHANNELS];
+ struct snd_pcm_substream *rxtx_substream[LPASS_MAX_CDC_DMA_CHANNELS];
+ struct snd_pcm_substream *va_substream[LPASS_MAX_CDC_DMA_CHANNELS];
+
+ /* SOC specific clock list */
+ struct clk_bulk_data *clks;
+ int num_clks;
+
+ /* Regmap fields of I2SCTL & DMACTL registers bitfields */
+ struct lpaif_i2sctl *i2sctl;
+ struct lpaif_dmactl *rd_dmactl;
+ struct lpaif_dmactl *wr_dmactl;
+ struct lpaif_dmactl *hdmi_rd_dmactl;
+
+ /* Regmap fields of CODEC DMA CTRL registers */
+ struct lpaif_dmactl *rxtx_rd_dmactl;
+ struct lpaif_dmactl *rxtx_wr_dmactl;
+ struct lpaif_dmactl *va_wr_dmactl;
+
+ /* Regmap fields of HDMI_CTRL registers*/
+ struct regmap_field *hdmitx_legacy_en;
+ struct regmap_field *hdmitx_parity_calc_en;
+ struct regmap_field *hdmitx_ch_msb[LPASS_MAX_HDMI_DMA_CHANNELS];
+ struct regmap_field *hdmitx_ch_lsb[LPASS_MAX_HDMI_DMA_CHANNELS];
+ struct lpass_hdmi_tx_ctl *tx_ctl;
+ struct lpass_vbit_ctrl *vbit_ctl;
+ struct lpass_hdmitx_dmactl *hdmi_tx_dmactl[LPASS_MAX_HDMI_DMA_CHANNELS];
+ struct lpass_dp_metadata_ctl *meta_ctl;
+ struct lpass_sstream_ctl *sstream_ctl;
+};
+
+/* Vairant data per each SOC */
+struct lpass_variant {
+ u32 irq_reg_base;
+ u32 irq_reg_stride;
+ u32 irq_ports;
+ u32 rdma_reg_base;
+ u32 rdma_reg_stride;
+ u32 rdma_channels;
+ u32 hdmi_rdma_reg_base;
+ u32 hdmi_rdma_reg_stride;
+ u32 hdmi_rdma_channels;
+ u32 wrdma_reg_base;
+ u32 wrdma_reg_stride;
+ u32 wrdma_channels;
+ u32 rxtx_irq_reg_base;
+ u32 rxtx_irq_reg_stride;
+ u32 rxtx_irq_ports;
+ u32 rxtx_rdma_reg_base;
+ u32 rxtx_rdma_reg_stride;
+ u32 rxtx_rdma_channels;
+ u32 rxtx_wrdma_reg_base;
+ u32 rxtx_wrdma_reg_stride;
+ u32 rxtx_wrdma_channels;
+ u32 va_irq_reg_base;
+ u32 va_irq_reg_stride;
+ u32 va_irq_ports;
+ u32 va_rdma_reg_base;
+ u32 va_rdma_reg_stride;
+ u32 va_rdma_channels;
+ u32 va_wrdma_reg_base;
+ u32 va_wrdma_reg_stride;
+ u32 va_wrdma_channels;
+ u32 i2sctrl_reg_base;
+ u32 i2sctrl_reg_stride;
+ u32 i2s_ports;
+
+ /* I2SCTL Register fields */
+ struct reg_field loopback;
+ struct reg_field spken;
+ struct reg_field spkmode;
+ struct reg_field spkmono;
+ struct reg_field micen;
+ struct reg_field micmode;
+ struct reg_field micmono;
+ struct reg_field wssrc;
+ struct reg_field bitwidth;
+
+ u32 hdmi_irq_reg_base;
+ u32 hdmi_irq_reg_stride;
+ u32 hdmi_irq_ports;
+
+ /* HDMI specific controls */
+ u32 hdmi_tx_ctl_addr;
+ u32 hdmi_legacy_addr;
+ u32 hdmi_vbit_addr;
+ u32 hdmi_ch_lsb_addr;
+ u32 hdmi_ch_msb_addr;
+ u32 ch_stride;
+ u32 hdmi_parity_addr;
+ u32 hdmi_dmactl_addr;
+ u32 hdmi_dma_stride;
+ u32 hdmi_DP_addr;
+ u32 hdmi_sstream_addr;
+
+ /* HDMI SSTREAM CTRL fields */
+ struct reg_field sstream_en;
+ struct reg_field dma_sel;
+ struct reg_field auto_bbit_en;
+ struct reg_field layout;
+ struct reg_field layout_sp;
+ struct reg_field set_sp_on_en;
+ struct reg_field dp_audio;
+ struct reg_field dp_staffing_en;
+ struct reg_field dp_sp_b_hw_en;
+
+ /* HDMI DP METADATA CTL fields */
+ struct reg_field mute;
+ struct reg_field as_sdp_cc;
+ struct reg_field as_sdp_ct;
+ struct reg_field aif_db4;
+ struct reg_field frequency;
+ struct reg_field mst_index;
+ struct reg_field dptx_index;
+
+ /* HDMI TX CTRL fields */
+ struct reg_field soft_reset;
+ struct reg_field force_reset;
+
+ /* HDMI TX DMA CTRL */
+ struct reg_field use_hw_chs;
+ struct reg_field use_hw_usr;
+ struct reg_field hw_chs_sel;
+ struct reg_field hw_usr_sel;
+
+ /* HDMI VBIT CTRL */
+ struct reg_field replace_vbit;
+ struct reg_field vbit_stream;
+
+ /* HDMI TX LEGACY */
+ struct reg_field legacy_en;
+
+ /* HDMI TX PARITY */
+ struct reg_field calc_en;
+
+ /* HDMI CH LSB */
+ struct reg_field lsb_bits;
+
+ /* HDMI CH MSB */
+ struct reg_field msb_bits;
+
+ struct reg_field hdmi_rdma_bursten;
+ struct reg_field hdmi_rdma_wpscnt;
+ struct reg_field hdmi_rdma_fifowm;
+ struct reg_field hdmi_rdma_enable;
+ struct reg_field hdmi_rdma_dyncclk;
+ struct reg_field hdmi_rdma_burst8;
+ struct reg_field hdmi_rdma_burst16;
+ struct reg_field hdmi_rdma_dynburst;
+
+ /* RD_DMA Register fields */
+ struct reg_field rdma_intf;
+ struct reg_field rdma_bursten;
+ struct reg_field rdma_wpscnt;
+ struct reg_field rdma_fifowm;
+ struct reg_field rdma_enable;
+ struct reg_field rdma_dyncclk;
+
+ /* WR_DMA Register fields */
+ struct reg_field wrdma_intf;
+ struct reg_field wrdma_bursten;
+ struct reg_field wrdma_wpscnt;
+ struct reg_field wrdma_fifowm;
+ struct reg_field wrdma_enable;
+ struct reg_field wrdma_dyncclk;
+
+ /* CDC RXTX RD_DMA */
+ struct reg_field rxtx_rdma_intf;
+ struct reg_field rxtx_rdma_bursten;
+ struct reg_field rxtx_rdma_wpscnt;
+ struct reg_field rxtx_rdma_fifowm;
+ struct reg_field rxtx_rdma_enable;
+ struct reg_field rxtx_rdma_dyncclk;
+ struct reg_field rxtx_rdma_burst8;
+ struct reg_field rxtx_rdma_burst16;
+ struct reg_field rxtx_rdma_dynburst;
+ struct reg_field rxtx_rdma_codec_enable;
+ struct reg_field rxtx_rdma_codec_pack;
+ struct reg_field rxtx_rdma_codec_intf;
+ struct reg_field rxtx_rdma_codec_fs_sel;
+ struct reg_field rxtx_rdma_codec_ch;
+ struct reg_field rxtx_rdma_codec_fs_delay;
+
+ /* CDC RXTX WR_DMA */
+ struct reg_field rxtx_wrdma_intf;
+ struct reg_field rxtx_wrdma_bursten;
+ struct reg_field rxtx_wrdma_wpscnt;
+ struct reg_field rxtx_wrdma_fifowm;
+ struct reg_field rxtx_wrdma_enable;
+ struct reg_field rxtx_wrdma_dyncclk;
+ struct reg_field rxtx_wrdma_burst8;
+ struct reg_field rxtx_wrdma_burst16;
+ struct reg_field rxtx_wrdma_dynburst;
+ struct reg_field rxtx_wrdma_codec_enable;
+ struct reg_field rxtx_wrdma_codec_pack;
+ struct reg_field rxtx_wrdma_codec_intf;
+ struct reg_field rxtx_wrdma_codec_fs_sel;
+ struct reg_field rxtx_wrdma_codec_ch;
+ struct reg_field rxtx_wrdma_codec_fs_delay;
+
+ /* CDC VA WR_DMA */
+ struct reg_field va_wrdma_intf;
+ struct reg_field va_wrdma_bursten;
+ struct reg_field va_wrdma_wpscnt;
+ struct reg_field va_wrdma_fifowm;
+ struct reg_field va_wrdma_enable;
+ struct reg_field va_wrdma_dyncclk;
+ struct reg_field va_wrdma_burst8;
+ struct reg_field va_wrdma_burst16;
+ struct reg_field va_wrdma_dynburst;
+ struct reg_field va_wrdma_codec_enable;
+ struct reg_field va_wrdma_codec_pack;
+ struct reg_field va_wrdma_codec_intf;
+ struct reg_field va_wrdma_codec_fs_sel;
+ struct reg_field va_wrdma_codec_ch;
+ struct reg_field va_wrdma_codec_fs_delay;
+
+ /**
+ * on SOCs like APQ8016 the channel control bits start
+ * at different offset to ipq806x
+ **/
+ u32 dmactl_audif_start;
+ u32 wrdma_channel_start;
+ u32 rxtx_wrdma_channel_start;
+ u32 va_wrdma_channel_start;
+
+ /* SOC specific initialization like clocks */
+ int (*init)(struct platform_device *pdev);
+ int (*exit)(struct platform_device *pdev);
+ int (*alloc_dma_channel)(struct lpass_data *data, int direction, unsigned int dai_id);
+ int (*free_dma_channel)(struct lpass_data *data, int ch, unsigned int dai_id);
+
+ /* SOC specific dais */
+ struct snd_soc_dai_driver *dai_driver;
+ int num_dai;
+ const char * const *dai_osr_clk_names;
+ const char * const *dai_bit_clk_names;
+
+ /* SOC specific clocks configuration */
+ const char **clk_name;
+ int num_clks;
+};
+
+struct lpass_pcm_data {
+ int dma_ch;
+ int i2s_port;
+};
+
+/* register the platform driver from the CPU DAI driver */
+int asoc_qcom_lpass_platform_register(struct platform_device *pdev);
+void asoc_qcom_lpass_cpu_platform_remove(struct platform_device *pdev);
+void asoc_qcom_lpass_cpu_platform_shutdown(struct platform_device *pdev);
+int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev);
+extern const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops;
+extern const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops2;
+extern const struct snd_soc_dai_ops asoc_qcom_lpass_cdc_dma_dai_ops;
+
+#endif /* __LPASS_H__ */
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile
new file mode 100644
index 000000000..67267304e
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/Makefile
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-q6dsp-common-y := q6dsp-common.o q6dsp-lpass-ports.o q6dsp-lpass-clocks.o
+snd-q6apm-y := q6apm.o audioreach.o topology.o
+
+obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += snd-q6dsp-common.o
+obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o
+obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o
+obj-$(CONFIG_SND_SOC_QDSP6_AFE_DAI) += q6afe-dai.o
+obj-$(CONFIG_SND_SOC_QDSP6_AFE_CLOCKS) += q6afe-clocks.o
+obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o
+obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o
+obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o
+obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.o
+
+obj-$(CONFIG_SND_SOC_QDSP6_APM) += snd-q6apm.o
+obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o
+obj-$(CONFIG_SND_SOC_QDSP6_APM_LPASS_DAI) += q6apm-lpass-dais.o
+obj-$(CONFIG_SND_SOC_QDSP6_PRM) += q6prm.o
+obj-$(CONFIG_SND_SOC_QDSP6_PRM_LPASS_CLOCKS) += q6prm-clocks.o
+obj-$(CONFIG_SND_SOC_QDSP6_USB) += q6usb.o
diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c
new file mode 100644
index 000000000..cce0ad31f
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/audioreach.c
@@ -0,0 +1,1614 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020, Linaro Limited
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/apr.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <dt-bindings/soc/qcom,gpr.h>
+#include "q6apm.h"
+#include "audioreach.h"
+
+/* SubGraph Config */
+struct apm_sub_graph_data {
+ struct apm_sub_graph_cfg sub_graph_cfg;
+ struct apm_prop_data perf_data;
+ struct apm_sg_prop_id_perf_mode perf;
+ struct apm_prop_data dir_data;
+ struct apm_sg_prop_id_direction dir;
+ struct apm_prop_data sid_data;
+ struct apm_sg_prop_id_scenario_id sid;
+
+} __packed;
+
+#define APM_SUB_GRAPH_CFG_NPROP 3
+
+struct apm_sub_graph_params {
+ struct apm_module_param_data param_data;
+ uint32_t num_sub_graphs;
+ struct apm_sub_graph_data sg_cfg[];
+} __packed;
+
+#define APM_SUB_GRAPH_PSIZE(p, n) ALIGN(struct_size(p, sg_cfg, n), 8)
+
+/* container config */
+struct apm_container_obj {
+ struct apm_container_cfg container_cfg;
+ /* Capability ID list */
+ struct apm_prop_data cap_data;
+ uint32_t num_capability_id;
+ uint32_t capability_id;
+
+ /* Container graph Position */
+ struct apm_prop_data pos_data;
+ struct apm_cont_prop_id_graph_pos pos;
+
+ /* Container Stack size */
+ struct apm_prop_data stack_data;
+ struct apm_cont_prop_id_stack_size stack;
+
+ /* Container proc domain id */
+ struct apm_prop_data domain_data;
+ struct apm_cont_prop_id_domain domain;
+} __packed;
+
+struct apm_container_params {
+ struct apm_module_param_data param_data;
+ uint32_t num_containers;
+ struct apm_container_obj cont_obj[];
+} __packed;
+
+#define APM_CONTAINER_PSIZE(p, n) ALIGN(struct_size(p, cont_obj, n), 8)
+
+/* Module List config */
+struct apm_mod_list_obj {
+ /* Modules list cfg */
+ uint32_t sub_graph_id;
+ uint32_t container_id;
+ uint32_t num_modules;
+ struct apm_module_obj mod_cfg[];
+} __packed;
+
+#define APM_MOD_LIST_OBJ_PSIZE(p, n) struct_size(p, mod_cfg, n)
+
+struct apm_module_list_params {
+ struct apm_module_param_data param_data;
+ uint32_t num_modules_list;
+ /* Module list config array */
+ struct apm_mod_list_obj mod_list_obj[];
+} __packed;
+
+
+/* Module Properties */
+struct apm_mod_prop_obj {
+ u32 instance_id;
+ u32 num_props;
+ struct apm_prop_data prop_data_1;
+ struct apm_module_prop_id_port_info prop_id_port;
+} __packed;
+
+struct apm_prop_list_params {
+ struct apm_module_param_data param_data;
+ u32 num_modules_prop_cfg;
+ struct apm_mod_prop_obj mod_prop_obj[];
+
+} __packed;
+
+#define APM_MOD_PROP_PSIZE(p, n) ALIGN(struct_size(p, mod_prop_obj, n), 8)
+
+/* Module Connections */
+struct apm_mod_conn_list_params {
+ struct apm_module_param_data param_data;
+ u32 num_connections;
+ struct apm_module_conn_obj conn_obj[];
+
+} __packed;
+
+#define APM_MOD_CONN_PSIZE(p, n) ALIGN(struct_size(p, conn_obj, n), 8)
+
+struct apm_graph_open_params {
+ struct apm_cmd_header *cmd_header;
+ struct apm_sub_graph_params *sg_data;
+ struct apm_container_params *cont_data;
+ struct apm_module_list_params *mod_list_data;
+ struct apm_prop_list_params *mod_prop_data;
+ struct apm_mod_conn_list_params *mod_conn_list_data;
+} __packed;
+
+struct apm_pcm_module_media_fmt_cmd {
+ struct apm_module_param_data param_data;
+ struct param_id_pcm_output_format_cfg header;
+ struct payload_pcm_output_format_cfg media_cfg;
+} __packed;
+
+struct apm_rd_shmem_module_config_cmd {
+ struct apm_module_param_data param_data;
+ struct param_id_rd_sh_mem_cfg cfg;
+} __packed;
+
+struct apm_sh_module_media_fmt_cmd {
+ struct media_format header;
+ struct payload_media_fmt_pcm cfg;
+} __packed;
+
+#define APM_SHMEM_FMT_CFG_PSIZE(ch) ALIGN( \
+ sizeof(struct apm_sh_module_media_fmt_cmd) + \
+ ch * sizeof(uint8_t), 8)
+
+/* num of channels as argument */
+#define APM_PCM_MODULE_FMT_CMD_PSIZE(ch) ALIGN( \
+ sizeof(struct apm_pcm_module_media_fmt_cmd) + \
+ ch * sizeof(uint8_t), 8)
+
+#define APM_PCM_OUT_FMT_CFG_PSIZE(p, n) ALIGN(struct_size(p, channel_mapping, n), 4)
+
+struct apm_i2s_module_intf_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_i2s_intf_cfg cfg;
+} __packed;
+
+#define APM_I2S_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_i2s_module_intf_cfg), 8)
+
+struct apm_audio_if_module_intf_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_audio_if_intf_cfg cfg;
+} __packed;
+
+#define APM_AUDIO_IF_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_audio_if_module_intf_cfg), 8)
+
+struct apm_module_hw_ep_mf_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_hw_ep_mf mf;
+} __packed;
+
+#define APM_HW_EP_CFG_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_mf_cfg), 8)
+
+#define APM_MFC_CFG_PSIZE(p, n) ALIGN(struct_size(p, channel_mapping, n), 4)
+
+struct apm_module_frame_size_factor_cfg {
+ struct apm_module_param_data param_data;
+ uint32_t frame_size_factor;
+} __packed;
+
+#define APM_FS_CFG_PSIZE ALIGN(sizeof(struct apm_module_frame_size_factor_cfg), 8)
+
+struct apm_module_hw_ep_frame_duration_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_hw_ep_frame_duration frame_duration;
+} __packed;
+
+#define APM_HW_EP_FRAME_DURATION_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_frame_duration_cfg), 8)
+
+struct apm_module_hw_ep_power_mode_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_hw_ep_power_mode_cfg power_mode;
+} __packed;
+
+#define APM_HW_EP_PMODE_CFG_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_power_mode_cfg), 8)
+
+struct apm_module_hw_ep_dma_data_align_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_hw_ep_dma_data_align align;
+} __packed;
+
+#define APM_HW_EP_DALIGN_CFG_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_dma_data_align_cfg), 8)
+
+struct apm_gain_module_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_gain_cfg gain_cfg;
+} __packed;
+
+#define APM_GAIN_CFG_PSIZE ALIGN(sizeof(struct apm_gain_module_cfg), 8)
+
+struct apm_codec_dma_module_intf_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_codec_dma_intf_cfg cfg;
+} __packed;
+
+#define APM_CDMA_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_codec_dma_module_intf_cfg), 8)
+
+struct apm_display_port_module_intf_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_display_port_intf_cfg cfg;
+} __packed;
+#define APM_DP_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_display_port_module_intf_cfg), 8)
+
+struct apm_module_sp_vi_op_mode_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_sp_vi_op_mode_cfg cfg;
+} __packed;
+
+#define APM_SP_VI_OP_MODE_CFG_PSIZE(ch) ALIGN( \
+ sizeof(struct apm_module_sp_vi_op_mode_cfg) + \
+ (ch) * sizeof(uint32_t), 8)
+
+struct apm_module_sp_vi_ex_mode_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_sp_vi_ex_mode_cfg cfg;
+} __packed;
+
+#define APM_SP_VI_EX_MODE_CFG_PSIZE ALIGN(sizeof(struct apm_module_sp_vi_ex_mode_cfg), 8)
+
+struct apm_module_sp_vi_channel_map_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_sp_vi_channel_map_cfg cfg;
+} __packed;
+
+#define APM_SP_VI_CH_MAP_CFG_PSIZE(ch) ALIGN( \
+ sizeof(struct apm_module_sp_vi_channel_map_cfg) + \
+ (ch) * sizeof(uint32_t), 8)
+
+static void *__audioreach_alloc_pkt(int payload_size, uint32_t opcode, uint32_t token,
+ uint32_t src_port, uint32_t dest_port, bool has_cmd_hdr)
+{
+ struct gpr_pkt *pkt;
+ void *p;
+ int pkt_size = GPR_HDR_SIZE + payload_size;
+
+ if (has_cmd_hdr)
+ pkt_size += APM_CMD_HDR_SIZE;
+
+ p = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return ERR_PTR(-ENOMEM);
+
+ pkt = p;
+ pkt->hdr.version = GPR_PKT_VER;
+ pkt->hdr.hdr_size = GPR_PKT_HEADER_WORD_SIZE;
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.dest_port = dest_port;
+ pkt->hdr.src_port = src_port;
+
+ pkt->hdr.dest_domain = GPR_DOMAIN_ID_ADSP;
+ pkt->hdr.src_domain = GPR_DOMAIN_ID_APPS;
+ pkt->hdr.token = token;
+ pkt->hdr.opcode = opcode;
+
+ if (has_cmd_hdr) {
+ struct apm_cmd_header *cmd_header;
+
+ p = p + GPR_HDR_SIZE;
+ cmd_header = p;
+ cmd_header->payload_size = payload_size;
+ }
+
+ return pkt;
+}
+
+void *audioreach_alloc_pkt(int payload_size, uint32_t opcode, uint32_t token,
+ uint32_t src_port, uint32_t dest_port)
+{
+ return __audioreach_alloc_pkt(payload_size, opcode, token, src_port, dest_port, false);
+}
+EXPORT_SYMBOL_GPL(audioreach_alloc_pkt);
+
+void *audioreach_alloc_apm_pkt(int pkt_size, uint32_t opcode, uint32_t token, uint32_t src_port)
+{
+ return __audioreach_alloc_pkt(pkt_size, opcode, token, src_port, APM_MODULE_INSTANCE_ID,
+ false);
+}
+EXPORT_SYMBOL_GPL(audioreach_alloc_apm_pkt);
+
+void *audioreach_alloc_cmd_pkt(int payload_size, uint32_t opcode, uint32_t token,
+ uint32_t src_port, uint32_t dest_port)
+{
+ return __audioreach_alloc_pkt(payload_size, opcode, token, src_port, dest_port, true);
+}
+EXPORT_SYMBOL_GPL(audioreach_alloc_cmd_pkt);
+
+void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, uint32_t token)
+{
+ return __audioreach_alloc_pkt(pkt_size, opcode, token, GPR_APM_MODULE_IID,
+ APM_MODULE_INSTANCE_ID, true);
+}
+EXPORT_SYMBOL_GPL(audioreach_alloc_apm_cmd_pkt);
+
+void audioreach_set_default_channel_mapping(u8 *ch_map, int num_channels)
+{
+ if (num_channels == 1) {
+ ch_map[0] = PCM_CHANNEL_FL;
+ } else if (num_channels == 2) {
+ ch_map[0] = PCM_CHANNEL_FL;
+ ch_map[1] = PCM_CHANNEL_FR;
+ } else if (num_channels == 4) {
+ ch_map[0] = PCM_CHANNEL_FL;
+ ch_map[1] = PCM_CHANNEL_FR;
+ ch_map[2] = PCM_CHANNEL_LS;
+ ch_map[3] = PCM_CHANNEL_RS;
+ }
+}
+EXPORT_SYMBOL_GPL(audioreach_set_default_channel_mapping);
+
+static void apm_populate_container_config(struct apm_container_obj *cfg,
+ const struct audioreach_container *cont)
+{
+
+ /* Container Config */
+ cfg->container_cfg.container_id = cont->container_id;
+ cfg->container_cfg.num_prop = 4;
+
+ /* Capability list */
+ cfg->cap_data.prop_id = APM_CONTAINER_PROP_ID_CAPABILITY_LIST;
+ cfg->cap_data.prop_size = APM_CONTAINER_PROP_ID_CAPABILITY_SIZE;
+ cfg->num_capability_id = 1;
+ cfg->capability_id = cont->capability_id;
+
+ /* Graph Position */
+ cfg->pos_data.prop_id = APM_CONTAINER_PROP_ID_GRAPH_POS;
+ cfg->pos_data.prop_size = sizeof(struct apm_cont_prop_id_graph_pos);
+ cfg->pos.graph_pos = cont->graph_pos;
+
+ /* Stack size */
+ cfg->stack_data.prop_id = APM_CONTAINER_PROP_ID_STACK_SIZE;
+ cfg->stack_data.prop_size = sizeof(struct apm_cont_prop_id_stack_size);
+ cfg->stack.stack_size = cont->stack_size;
+
+ /* Proc domain */
+ cfg->domain_data.prop_id = APM_CONTAINER_PROP_ID_PROC_DOMAIN;
+ cfg->domain_data.prop_size = sizeof(struct apm_cont_prop_id_domain);
+ cfg->domain.proc_domain = cont->proc_domain;
+}
+
+static void apm_populate_sub_graph_config(struct apm_sub_graph_data *cfg,
+ const struct audioreach_sub_graph *sg)
+{
+ cfg->sub_graph_cfg.sub_graph_id = sg->sub_graph_id;
+ cfg->sub_graph_cfg.num_sub_graph_prop = APM_SUB_GRAPH_CFG_NPROP;
+
+ /* Perf Mode */
+ cfg->perf_data.prop_id = APM_SUB_GRAPH_PROP_ID_PERF_MODE;
+ cfg->perf_data.prop_size = APM_SG_PROP_ID_PERF_MODE_SIZE;
+ cfg->perf.perf_mode = sg->perf_mode;
+
+ /* Direction */
+ cfg->dir_data.prop_id = APM_SUB_GRAPH_PROP_ID_DIRECTION;
+ cfg->dir_data.prop_size = APM_SG_PROP_ID_DIR_SIZE;
+ cfg->dir.direction = sg->direction;
+
+ /* Scenario ID */
+ cfg->sid_data.prop_id = APM_SUB_GRAPH_PROP_ID_SCENARIO_ID;
+ cfg->sid_data.prop_size = APM_SG_PROP_ID_SID_SIZE;
+ cfg->sid.scenario_id = sg->scenario_id;
+}
+
+static void apm_populate_module_prop_obj(struct apm_mod_prop_obj *obj,
+ const struct audioreach_module *module)
+{
+
+ obj->instance_id = module->instance_id;
+ obj->num_props = 1;
+ obj->prop_data_1.prop_id = APM_MODULE_PROP_ID_PORT_INFO;
+ obj->prop_data_1.prop_size = APM_MODULE_PROP_ID_PORT_INFO_SZ;
+ obj->prop_id_port.max_ip_port = module->max_ip_port;
+ obj->prop_id_port.max_op_port = module->max_op_port;
+}
+
+static void apm_populate_module_list_obj(struct apm_mod_list_obj *obj,
+ const struct audioreach_container *container,
+ int sub_graph_id)
+{
+ struct audioreach_module *module;
+ int i;
+
+ obj->sub_graph_id = sub_graph_id;
+ obj->container_id = container->container_id;
+ obj->num_modules = container->num_modules;
+ i = 0;
+ list_for_each_entry(module, &container->modules_list, node) {
+ obj->mod_cfg[i].module_id = module->module_id;
+ obj->mod_cfg[i].instance_id = module->instance_id;
+ i++;
+ }
+}
+
+static void audioreach_populate_graph(struct q6apm *apm,
+ const struct audioreach_graph_info *info,
+ struct apm_graph_open_params *open,
+ const struct list_head *sg_list,
+ int num_sub_graphs)
+{
+ struct apm_mod_conn_list_params *mc_data = open->mod_conn_list_data;
+ struct apm_module_list_params *ml_data = open->mod_list_data;
+ struct apm_prop_list_params *mp_data = open->mod_prop_data;
+ struct apm_container_params *c_data = open->cont_data;
+ struct apm_sub_graph_params *sg_data = open->sg_data;
+ int ncontainer = 0, nmodule = 0, nconn = 0;
+ struct apm_mod_prop_obj *module_prop_obj;
+ struct audioreach_container *container;
+ struct apm_module_conn_obj *conn_obj;
+ struct audioreach_module *module;
+ struct audioreach_sub_graph *sg;
+ struct apm_container_obj *cobj;
+ struct apm_mod_list_obj *mlobj;
+ int i = 0;
+
+ mlobj = &ml_data->mod_list_obj[0];
+
+
+ if (info->dst_mod_inst_id && info->src_mod_inst_id) {
+ conn_obj = &mc_data->conn_obj[nconn];
+ conn_obj->src_mod_inst_id = info->src_mod_inst_id;
+ conn_obj->src_mod_op_port_id = info->src_mod_op_port_id;
+ conn_obj->dst_mod_inst_id = info->dst_mod_inst_id;
+ conn_obj->dst_mod_ip_port_id = info->dst_mod_ip_port_id;
+ nconn++;
+ }
+
+ list_for_each_entry(sg, sg_list, node) {
+ struct apm_sub_graph_data *sg_cfg = &sg_data->sg_cfg[i++];
+
+ apm_populate_sub_graph_config(sg_cfg, sg);
+
+ list_for_each_entry(container, &sg->container_list, node) {
+ cobj = &c_data->cont_obj[ncontainer];
+
+ apm_populate_container_config(cobj, container);
+ apm_populate_module_list_obj(mlobj, container, sg->sub_graph_id);
+
+ list_for_each_entry(module, &container->modules_list, node) {
+ int pn;
+
+ module_prop_obj = &mp_data->mod_prop_obj[nmodule++];
+ apm_populate_module_prop_obj(module_prop_obj, module);
+
+ if (!module->max_op_port)
+ continue;
+
+ for (pn = 0; pn < module->max_op_port; pn++) {
+ if (module->dst_mod_inst_id[pn]) {
+ conn_obj = &mc_data->conn_obj[nconn];
+ conn_obj->src_mod_inst_id = module->instance_id;
+ conn_obj->src_mod_op_port_id =
+ module->src_mod_op_port_id[pn];
+ conn_obj->dst_mod_inst_id =
+ module->dst_mod_inst_id[pn];
+ conn_obj->dst_mod_ip_port_id =
+ module->dst_mod_ip_port_id[pn];
+ nconn++;
+ }
+ }
+ }
+ mlobj = (void *) mlobj + APM_MOD_LIST_OBJ_PSIZE(mlobj,
+ container->num_modules);
+
+ ncontainer++;
+ }
+ }
+}
+
+void *audioreach_alloc_graph_pkt(struct q6apm *apm,
+ const struct audioreach_graph_info *info)
+{
+ int payload_size, sg_sz, cont_sz, ml_sz, mp_sz, mc_sz;
+ struct apm_module_param_data *param_data;
+ struct apm_container_params *cont_params;
+ struct audioreach_container *container;
+ struct apm_sub_graph_params *sg_params;
+ struct apm_mod_conn_list_params *mcon;
+ struct apm_graph_open_params params;
+ struct apm_prop_list_params *mprop;
+ struct audioreach_module *module;
+ struct audioreach_sub_graph *sgs;
+ struct apm_mod_list_obj *mlobj;
+ const struct list_head *sg_list;
+ int num_connections = 0;
+ int num_containers = 0;
+ int num_sub_graphs = 0;
+ int num_modules = 0;
+ int num_modules_list;
+ struct gpr_pkt *pkt;
+ void *p;
+
+ sg_list = &info->sg_list;
+ ml_sz = 0;
+
+ /* add FE-BE connections */
+ if (info->dst_mod_inst_id && info->src_mod_inst_id)
+ num_connections++;
+
+ list_for_each_entry(sgs, sg_list, node) {
+ num_sub_graphs++;
+ list_for_each_entry(container, &sgs->container_list, node) {
+ num_containers++;
+ num_modules += container->num_modules;
+ ml_sz = ml_sz + sizeof(struct apm_module_list_params) +
+ APM_MOD_LIST_OBJ_PSIZE(mlobj, container->num_modules);
+
+ list_for_each_entry(module, &container->modules_list, node) {
+ num_connections += module->num_connections;
+ }
+ }
+ }
+
+ num_modules_list = num_containers;
+ sg_sz = APM_SUB_GRAPH_PSIZE(sg_params, num_sub_graphs);
+ cont_sz = APM_CONTAINER_PSIZE(cont_params, num_containers);
+
+ ml_sz = ALIGN(ml_sz, 8);
+
+ mp_sz = APM_MOD_PROP_PSIZE(mprop, num_modules);
+ mc_sz = APM_MOD_CONN_PSIZE(mcon, num_connections);
+
+ payload_size = sg_sz + cont_sz + ml_sz + mp_sz + mc_sz;
+ pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_GRAPH_OPEN, 0);
+ if (IS_ERR(pkt))
+ return pkt;
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ /* SubGraph */
+ params.sg_data = p;
+ param_data = &params.sg_data->param_data;
+ param_data->module_instance_id = APM_MODULE_INSTANCE_ID;
+ param_data->param_id = APM_PARAM_ID_SUB_GRAPH_CONFIG;
+ param_data->param_size = sg_sz - APM_MODULE_PARAM_DATA_SIZE;
+ params.sg_data->num_sub_graphs = num_sub_graphs;
+ p += sg_sz;
+
+ /* Container */
+ params.cont_data = p;
+ param_data = &params.cont_data->param_data;
+ param_data->module_instance_id = APM_MODULE_INSTANCE_ID;
+ param_data->param_id = APM_PARAM_ID_CONTAINER_CONFIG;
+ param_data->param_size = cont_sz - APM_MODULE_PARAM_DATA_SIZE;
+ params.cont_data->num_containers = num_containers;
+ p += cont_sz;
+
+ /* Module List*/
+ params.mod_list_data = p;
+ param_data = &params.mod_list_data->param_data;
+ param_data->module_instance_id = APM_MODULE_INSTANCE_ID;
+ param_data->param_id = APM_PARAM_ID_MODULE_LIST;
+ param_data->param_size = ml_sz - APM_MODULE_PARAM_DATA_SIZE;
+ params.mod_list_data->num_modules_list = num_modules_list;
+ p += ml_sz;
+
+ /* Module Properties */
+ params.mod_prop_data = p;
+ param_data = &params.mod_prop_data->param_data;
+ param_data->module_instance_id = APM_MODULE_INSTANCE_ID;
+ param_data->param_id = APM_PARAM_ID_MODULE_PROP;
+ param_data->param_size = mp_sz - APM_MODULE_PARAM_DATA_SIZE;
+ params.mod_prop_data->num_modules_prop_cfg = num_modules;
+ p += mp_sz;
+
+ /* Module Connections */
+ params.mod_conn_list_data = p;
+ param_data = &params.mod_conn_list_data->param_data;
+ param_data->module_instance_id = APM_MODULE_INSTANCE_ID;
+ param_data->param_id = APM_PARAM_ID_MODULE_CONN;
+ param_data->param_size = mc_sz - APM_MODULE_PARAM_DATA_SIZE;
+ params.mod_conn_list_data->num_connections = num_connections;
+ p += mc_sz;
+
+ audioreach_populate_graph(apm, info, &params, sg_list, num_sub_graphs);
+
+ return pkt;
+}
+EXPORT_SYMBOL_GPL(audioreach_alloc_graph_pkt);
+
+int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev,
+ struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock,
+ gpr_port_t *port, wait_queue_head_t *cmd_wait,
+ const struct gpr_pkt *pkt, uint32_t rsp_opcode)
+{
+
+ const struct gpr_hdr *hdr = &pkt->hdr;
+ int rc;
+
+ mutex_lock(cmd_lock);
+ result->opcode = 0;
+ result->status = 0;
+
+ if (port)
+ rc = gpr_send_port_pkt(port, pkt);
+ else if (gdev)
+ rc = gpr_send_pkt(gdev, pkt);
+ else
+ rc = -EINVAL;
+
+ if (rc < 0)
+ goto err;
+
+ if (rsp_opcode)
+ rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode) ||
+ (result->opcode == rsp_opcode), 5 * HZ);
+ else
+ rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode), 5 * HZ);
+
+ if (!rc) {
+ dev_err(dev, "CMD timeout for [%x] opcode\n", hdr->opcode);
+ rc = -ETIMEDOUT;
+ } else if (result->status > 0) {
+ dev_err(dev, "DSP returned error[%x] %x\n", hdr->opcode, result->status);
+ rc = -EINVAL;
+ } else {
+ /* DSP successfully finished the command */
+ rc = 0;
+ }
+
+err:
+ mutex_unlock(cmd_lock);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync);
+
+int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt,
+ uint32_t rsp_opcode)
+{
+
+ return audioreach_send_cmd_sync(graph->dev, NULL, &graph->result, &graph->lock,
+ graph->port, &graph->cmd_wait, pkt, rsp_opcode);
+}
+EXPORT_SYMBOL_GPL(audioreach_graph_send_cmd_sync);
+
+static int audioreach_display_port_set_media_format(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *cfg)
+{
+ struct apm_display_port_module_intf_cfg *intf_cfg;
+ struct apm_module_frame_size_factor_cfg *fs_cfg;
+ struct apm_module_param_data *param_data;
+ struct apm_module_hw_ep_mf_cfg *hw_cfg;
+ int ic_sz = APM_DP_INTF_CFG_PSIZE;
+ int ep_sz = APM_HW_EP_CFG_PSIZE;
+ int fs_sz = APM_FS_CFG_PSIZE;
+ int size = ic_sz + ep_sz + fs_sz;
+ void *p;
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ hw_cfg = p;
+ param_data = &hw_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_HW_EP_MF_CFG;
+ param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE;
+
+ hw_cfg->mf.sample_rate = cfg->sample_rate;
+ hw_cfg->mf.bit_width = cfg->bit_width;
+ hw_cfg->mf.num_channels = cfg->num_channels;
+ hw_cfg->mf.data_format = module->data_format;
+ p += ep_sz;
+
+ fs_cfg = p;
+ param_data = &fs_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_HW_EP_FRAME_SIZE_FACTOR;
+ param_data->param_size = fs_sz - APM_MODULE_PARAM_DATA_SIZE;
+ fs_cfg->frame_size_factor = 1;
+ p += fs_sz;
+
+ intf_cfg = p;
+ param_data = &intf_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_DISPLAY_PORT_INTF_CFG;
+ param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE;
+
+ intf_cfg->cfg.channel_allocation = cfg->channel_allocation;
+ intf_cfg->cfg.mst_idx = 0;
+ intf_cfg->cfg.dptx_idx = cfg->dp_idx;
+
+ return q6apm_send_cmd_sync(graph->apm, pkt, 0);
+}
+
+/* LPASS Codec DMA port Module Media Format Setup */
+static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *cfg)
+{
+ struct apm_codec_dma_module_intf_cfg *intf_cfg;
+ struct apm_module_frame_size_factor_cfg *fs_cfg;
+ struct apm_module_hw_ep_power_mode_cfg *pm_cfg;
+ struct apm_module_param_data *param_data;
+ struct apm_module_hw_ep_mf_cfg *hw_cfg;
+ int ic_sz = APM_CDMA_INTF_CFG_PSIZE;
+ int ep_sz = APM_HW_EP_CFG_PSIZE;
+ int fs_sz = APM_FS_CFG_PSIZE;
+ int pm_sz = APM_HW_EP_PMODE_CFG_PSIZE;
+ int size = ic_sz + ep_sz + fs_sz + pm_sz;
+ void *p;
+ int i;
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ hw_cfg = p;
+ param_data = &hw_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_HW_EP_MF_CFG;
+ param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE;
+
+ hw_cfg->mf.sample_rate = cfg->sample_rate;
+ hw_cfg->mf.bit_width = cfg->bit_width;
+ hw_cfg->mf.num_channels = cfg->num_channels;
+ hw_cfg->mf.data_format = module->data_format;
+ p += ep_sz;
+
+ fs_cfg = p;
+ param_data = &fs_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_HW_EP_FRAME_SIZE_FACTOR;
+ param_data->param_size = fs_sz - APM_MODULE_PARAM_DATA_SIZE;
+ fs_cfg->frame_size_factor = 1;
+ p += fs_sz;
+
+ intf_cfg = p;
+ param_data = &intf_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_CODEC_DMA_INTF_CFG;
+ param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE;
+
+ intf_cfg->cfg.lpaif_type = module->hw_interface_type;
+ intf_cfg->cfg.intf_index = module->hw_interface_idx;
+ intf_cfg->cfg.active_channels_mask = 0;
+ /* Convert the physical channel mapping into a bit field */
+ for (i = 0; i < AR_PCM_MAX_NUM_CHANNEL; i++)
+ if (cfg->channel_map[i])
+ intf_cfg->cfg.active_channels_mask |= BIT(i);
+
+ p += ic_sz;
+
+ pm_cfg = p;
+ param_data = &pm_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_HW_EP_POWER_MODE_CFG;
+ param_data->param_size = pm_sz - APM_MODULE_PARAM_DATA_SIZE;
+ pm_cfg->power_mode.power_mode = 0;
+
+ return q6apm_send_cmd_sync(graph->apm, pkt, 0);
+}
+
+int audioreach_send_u32_param(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ uint32_t param_id, uint32_t param_val)
+{
+ struct apm_module_param_data *param_data;
+ uint32_t *param;
+ int payload_size = sizeof(uint32_t) + APM_MODULE_PARAM_DATA_SIZE;
+ void *p;
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return -ENOMEM;
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ param_data = p;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = param_id;
+ param_data->param_size = sizeof(uint32_t);
+
+ p = p + APM_MODULE_PARAM_DATA_SIZE;
+ param = p;
+ *param = param_val;
+
+ return q6apm_send_cmd_sync(graph->apm, pkt, 0);
+}
+EXPORT_SYMBOL_GPL(audioreach_send_u32_param);
+
+static int audioreach_sal_limiter_enable(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ bool enable)
+{
+ return audioreach_send_u32_param(graph, module, PARAM_ID_SAL_LIMITER_ENABLE, enable);
+}
+
+static int audioreach_sal_set_media_format(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *cfg)
+{
+ return audioreach_send_u32_param(graph, module, PARAM_ID_SAL_OUTPUT_CFG, cfg->bit_width);
+}
+
+static int audioreach_module_enable(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ bool enable)
+{
+ return audioreach_send_u32_param(graph, module, PARAM_ID_MODULE_ENABLE, enable);
+}
+
+static int audioreach_gapless_set_media_format(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *cfg)
+{
+ return audioreach_send_u32_param(graph, module, PARAM_ID_EARLY_EOS_DELAY,
+ EARLY_EOS_DELAY_MS);
+}
+
+static int audioreach_set_module_config(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *cfg)
+{
+ int size = le32_to_cpu(module->data->size);
+ void *p;
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ memcpy(p, module->data->data, size);
+
+ return q6apm_send_cmd_sync(graph->apm, pkt, 0);
+}
+
+static int audioreach_mfc_set_media_format(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *cfg)
+{
+ struct apm_module_param_data *param_data;
+ struct param_id_mfc_media_format *media_format;
+ uint32_t num_channels = cfg->num_channels;
+ int payload_size = APM_MFC_CFG_PSIZE(media_format, num_channels) +
+ APM_MODULE_PARAM_DATA_SIZE;
+ int i, j;
+ void *p;
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ param_data = p;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_MFC_OUTPUT_MEDIA_FORMAT;
+ param_data->param_size = APM_MFC_CFG_PSIZE(media_format, num_channels);
+ p = p + APM_MODULE_PARAM_DATA_SIZE;
+ media_format = p;
+
+ media_format->sample_rate = cfg->sample_rate;
+ media_format->bit_width = cfg->bit_width;
+ media_format->num_channels = cfg->num_channels;
+ /* Convert the physical mapping to a logical mapping of the channels */
+ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < cfg->num_channels; i++) {
+ if (!cfg->channel_map[i])
+ continue;
+ media_format->channel_mapping[j++] = cfg->channel_map[i];
+ }
+
+ return q6apm_send_cmd_sync(graph->apm, pkt, 0);
+}
+
+static int audioreach_set_compr_media_format(struct media_format *media_fmt_hdr,
+ void *p,
+ const struct audioreach_module_config *mcfg)
+{
+ struct payload_media_fmt_aac_t *aac_cfg;
+ struct payload_media_fmt_pcm *mp3_cfg;
+ struct payload_media_fmt_flac_t *flac_cfg;
+ struct payload_media_fmt_opus_t *opus_cfg;
+
+ switch (mcfg->fmt) {
+ case SND_AUDIOCODEC_MP3:
+ media_fmt_hdr->data_format = DATA_FORMAT_RAW_COMPRESSED;
+ media_fmt_hdr->fmt_id = MEDIA_FMT_ID_MP3;
+ media_fmt_hdr->payload_size = 0;
+ p = p + sizeof(*media_fmt_hdr);
+ mp3_cfg = p;
+ mp3_cfg->sample_rate = mcfg->sample_rate;
+ mp3_cfg->bit_width = mcfg->bit_width;
+ mp3_cfg->alignment = PCM_LSB_ALIGNED;
+ mp3_cfg->bits_per_sample = mcfg->bit_width;
+ mp3_cfg->q_factor = mcfg->bit_width - 1;
+ mp3_cfg->endianness = PCM_LITTLE_ENDIAN;
+ mp3_cfg->num_channels = mcfg->num_channels;
+ break;
+ case SND_AUDIOCODEC_AAC:
+ media_fmt_hdr->data_format = DATA_FORMAT_RAW_COMPRESSED;
+ media_fmt_hdr->fmt_id = MEDIA_FMT_ID_AAC;
+ media_fmt_hdr->payload_size = sizeof(struct payload_media_fmt_aac_t);
+ p = p + sizeof(*media_fmt_hdr);
+ aac_cfg = p;
+ aac_cfg->aac_fmt_flag = 0;
+ aac_cfg->audio_obj_type = 5;
+ aac_cfg->num_channels = mcfg->num_channels;
+ aac_cfg->total_size_of_PCE_bits = 0;
+ aac_cfg->sample_rate = mcfg->sample_rate;
+ break;
+ case SND_AUDIOCODEC_FLAC:
+ media_fmt_hdr->data_format = DATA_FORMAT_RAW_COMPRESSED;
+ media_fmt_hdr->fmt_id = MEDIA_FMT_ID_FLAC;
+ media_fmt_hdr->payload_size = sizeof(struct payload_media_fmt_flac_t);
+ p = p + sizeof(*media_fmt_hdr);
+ flac_cfg = p;
+ flac_cfg->sample_size = mcfg->codec.options.flac_d.sample_size;
+ flac_cfg->num_channels = mcfg->num_channels;
+ flac_cfg->min_blk_size = mcfg->codec.options.flac_d.min_blk_size;
+ flac_cfg->max_blk_size = mcfg->codec.options.flac_d.max_blk_size;
+ flac_cfg->sample_rate = mcfg->sample_rate;
+ flac_cfg->min_frame_size = mcfg->codec.options.flac_d.min_frame_size;
+ flac_cfg->max_frame_size = mcfg->codec.options.flac_d.max_frame_size;
+ break;
+ case SND_AUDIOCODEC_OPUS_RAW:
+ media_fmt_hdr->data_format = DATA_FORMAT_RAW_COMPRESSED;
+ media_fmt_hdr->fmt_id = MEDIA_FMT_ID_OPUS;
+ media_fmt_hdr->payload_size = sizeof(*opus_cfg);
+ p = p + sizeof(*media_fmt_hdr);
+ opus_cfg = p;
+ /* raw opus packets prepended with 4 bytes of length */
+ opus_cfg->bitstream_format = 1;
+ /*
+ * payload_type:
+ * 0 -- read metadata from opus stream;
+ * 1 -- metadata is provided by filling in the struct here.
+ */
+ opus_cfg->payload_type = 1;
+ opus_cfg->version = mcfg->codec.options.opus_d.version;
+ opus_cfg->num_channels = mcfg->codec.options.opus_d.num_channels;
+ opus_cfg->pre_skip = mcfg->codec.options.opus_d.pre_skip;
+ opus_cfg->sample_rate = mcfg->codec.options.opus_d.sample_rate;
+ opus_cfg->output_gain = mcfg->codec.options.opus_d.output_gain;
+ opus_cfg->mapping_family = mcfg->codec.options.opus_d.mapping_family;
+ opus_cfg->stream_count = mcfg->codec.options.opus_d.chan_map.stream_count;
+ opus_cfg->coupled_count = mcfg->codec.options.opus_d.chan_map.coupled_count;
+ memcpy(opus_cfg->channel_mapping, mcfg->codec.options.opus_d.chan_map.channel_map,
+ sizeof(opus_cfg->channel_mapping));
+ opus_cfg->reserved[0] = opus_cfg->reserved[1] = opus_cfg->reserved[2] = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int audioreach_compr_set_param(struct q6apm_graph *graph,
+ const struct audioreach_module_config *mcfg)
+{
+ struct media_format *header;
+ int rc;
+ void *p;
+ int iid = graph->shm_iid;
+ int payload_size = sizeof(struct apm_sh_module_media_fmt_cmd);
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_cmd_pkt(payload_size,
+ DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT,
+ 0, graph->port->id, iid);
+ if (IS_ERR(pkt))
+ return -ENOMEM;
+
+ p = (void *)pkt + GPR_HDR_SIZE;
+ header = p;
+ rc = audioreach_set_compr_media_format(header, p, mcfg);
+ if (rc)
+ return rc;
+
+ return gpr_send_port_pkt(graph->port, pkt);
+}
+EXPORT_SYMBOL_GPL(audioreach_compr_set_param);
+
+static int audioreach_i2s_set_media_format(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *cfg)
+{
+ struct apm_module_frame_size_factor_cfg *fs_cfg;
+ struct apm_module_param_data *param_data;
+ struct apm_i2s_module_intf_cfg *intf_cfg;
+ struct apm_module_hw_ep_mf_cfg *hw_cfg;
+ int ic_sz = APM_I2S_INTF_CFG_PSIZE;
+ int ep_sz = APM_HW_EP_CFG_PSIZE;
+ int fs_sz = APM_FS_CFG_PSIZE;
+ int size = ic_sz + ep_sz + fs_sz;
+ void *p;
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+ intf_cfg = p;
+
+ param_data = &intf_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_I2S_INTF_CFG;
+ param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE;
+
+ intf_cfg->cfg.lpaif_type = module->hw_interface_type;
+ intf_cfg->cfg.intf_idx = module->hw_interface_idx;
+ intf_cfg->cfg.sd_line_idx = module->sd_line_idx;
+
+ switch (cfg->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
+ intf_cfg->cfg.ws_src = CONFIG_I2S_WS_SRC_INTERNAL;
+ break;
+ case SND_SOC_DAIFMT_BC_FC:
+ /* CPU is slave */
+ intf_cfg->cfg.ws_src = CONFIG_I2S_WS_SRC_EXTERNAL;
+ break;
+ default:
+ break;
+ }
+
+ p += ic_sz;
+ hw_cfg = p;
+ param_data = &hw_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_HW_EP_MF_CFG;
+ param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE;
+
+ hw_cfg->mf.sample_rate = cfg->sample_rate;
+ hw_cfg->mf.bit_width = cfg->bit_width;
+ hw_cfg->mf.num_channels = cfg->num_channels;
+ hw_cfg->mf.data_format = module->data_format;
+
+ p += ep_sz;
+ fs_cfg = p;
+ param_data = &fs_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_HW_EP_FRAME_SIZE_FACTOR;
+ param_data->param_size = fs_sz - APM_MODULE_PARAM_DATA_SIZE;
+ fs_cfg->frame_size_factor = 1;
+
+ return q6apm_send_cmd_sync(graph->apm, pkt, 0);
+}
+
+static int audioreach_audio_if_set_media_format(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *cfg)
+{
+ struct apm_module_hw_ep_frame_duration_cfg *fd_cfg;
+ struct apm_module_param_data *param_data;
+ struct apm_audio_if_module_intf_cfg *intf_cfg;
+ struct apm_module_hw_ep_mf_cfg *hw_cfg;
+ int ic_sz = APM_AUDIO_IF_INTF_CFG_PSIZE;
+ int ep_sz = APM_HW_EP_CFG_PSIZE;
+ int fd_sz = APM_HW_EP_FRAME_DURATION_PSIZE;
+ int size = ic_sz + ep_sz + fd_sz;
+ u32 slot_mask = cfg->slot_mask ? cfg->slot_mask : module->slot_mask;
+ u16 nslots_per_frame = cfg->nslots_per_frame ?
+ (u16)cfg->nslots_per_frame : module->nslots_per_frame;
+ u16 slot_width = cfg->slot_width ? (u16)cfg->slot_width : module->slot_width;
+ void *p;
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+ intf_cfg = p;
+
+ param_data = &intf_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_AUDIO_IF_INTF_CFG;
+ param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE;
+ intf_cfg->cfg.qaif_type = module->qaif_type;
+ intf_cfg->cfg.intf_idx = (u16)module->hw_interface_idx;
+ intf_cfg->cfg.intf_mode = module->intf_mode;
+ intf_cfg->cfg.ctrl_data_out_enable = module->ctrl_data_out_enable;
+ intf_cfg->cfg.active_slot_mask = slot_mask;
+ intf_cfg->cfg.nslots_per_frame = nslots_per_frame;
+ intf_cfg->cfg.slot_width = slot_width;
+ intf_cfg->cfg.active_lane_mask = module->active_lane_mask;
+ intf_cfg->cfg.frame_sync_rate = module->frame_sync_rate;
+ intf_cfg->cfg.frame_sync_src = module->sync_src;
+ intf_cfg->cfg.frame_sync_mode = module->sync_mode;
+ intf_cfg->cfg.invert_frame_sync_pulse = module->ctrl_invert_sync_pulse;
+ intf_cfg->cfg.frame_sync_data_delay = module->ctrl_sync_data_delay;
+ intf_cfg->cfg.bit_clk_type = module->bit_clk_type;
+ intf_cfg->cfg.inv_int_bit_clk = module->inv_int_bit_clk;
+ intf_cfg->cfg.inv_ext_bit_clk = module->inv_ext_bit_clk;
+
+ p += ic_sz;
+ hw_cfg = p;
+ param_data = &hw_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_HW_EP_MF_CFG;
+ param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE;
+
+ hw_cfg->mf.sample_rate = cfg->sample_rate;
+ hw_cfg->mf.bit_width = cfg->bit_width;
+ hw_cfg->mf.num_channels = cfg->num_channels;
+ hw_cfg->mf.data_format = module->data_format;
+
+ p += ep_sz;
+ fd_cfg = p;
+ param_data = &fd_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_HW_EP_FRAME_DURATION;
+ param_data->param_size = fd_sz - APM_MODULE_PARAM_DATA_SIZE;
+ fd_cfg->frame_duration.frame_duration_in_us = AUDIO_IF_FRAME_DURATION_US;
+ fd_cfg->frame_duration.allow_frame_duration_normalization = AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE;
+ fd_cfg->frame_duration.min_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MIN_US;
+ fd_cfg->frame_duration.max_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MAX_US;
+
+ return q6apm_send_cmd_sync(graph->apm, pkt, 0);
+}
+
+static int audioreach_logging_set_media_format(struct q6apm_graph *graph,
+ const struct audioreach_module *module)
+{
+ struct apm_module_param_data *param_data;
+ struct data_logging_config *cfg;
+ int size = sizeof(*cfg) + APM_MODULE_PARAM_DATA_SIZE;
+ void *p;
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ param_data = p;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_DATA_LOGGING_CONFIG;
+ param_data->param_size = size - APM_MODULE_PARAM_DATA_SIZE;
+
+ p = p + APM_MODULE_PARAM_DATA_SIZE;
+ cfg = p;
+ cfg->log_code = module->log_code;
+ cfg->log_tap_point_id = module->log_tap_point_id;
+ cfg->mode = module->log_mode;
+
+ return q6apm_send_cmd_sync(graph->apm, pkt, 0);
+}
+
+static int audioreach_pcm_set_media_format(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *mcfg)
+{
+ struct payload_pcm_output_format_cfg *media_cfg;
+ uint32_t num_channels = mcfg->num_channels;
+ struct apm_pcm_module_media_fmt_cmd *cfg;
+ struct apm_module_param_data *param_data;
+ int payload_size;
+ int i, j;
+
+ if (num_channels > 4) {
+ dev_err(graph->dev, "Error: Invalid channels (%d)!\n", num_channels);
+ return -EINVAL;
+ }
+
+ payload_size = APM_PCM_MODULE_FMT_CMD_PSIZE(num_channels);
+
+ struct gpr_pkt *pkt __free(kfree) =
+ audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ cfg = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ param_data = &cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_PCM_OUTPUT_FORMAT_CFG;
+ param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE;
+
+ cfg->header.data_format = DATA_FORMAT_FIXED_POINT;
+ cfg->header.fmt_id = MEDIA_FMT_ID_PCM;
+ cfg->header.payload_size = APM_PCM_OUT_FMT_CFG_PSIZE(media_cfg, num_channels);
+
+ media_cfg = &cfg->media_cfg;
+ media_cfg->alignment = PCM_LSB_ALIGNED;
+ media_cfg->bit_width = mcfg->bit_width;
+ media_cfg->endianness = PCM_LITTLE_ENDIAN;
+ media_cfg->interleaved = module->interleave_type;
+ media_cfg->num_channels = mcfg->num_channels;
+ media_cfg->q_factor = mcfg->bit_width - 1;
+ media_cfg->bits_per_sample = mcfg->bit_width;
+ /* Convert the physical mapping to a logical mapping of the channels */
+ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < mcfg->num_channels; i++) {
+ if (!mcfg->channel_map[i])
+ continue;
+ media_cfg->channel_mapping[j++] = mcfg->channel_map[i];
+ }
+
+ return q6apm_send_cmd_sync(graph->apm, pkt, 0);
+}
+
+int audioreach_shmem_register_event(struct q6apm_graph *graph, int bytes, int num_levels)
+{
+ struct apm_module_register_events *event;
+ struct event_cfg_sh_mem_pull_push_mode_watermark_t *level;
+ int i, payload_size;
+ struct gpr_pkt *pkt __free(kfree) = NULL;
+ void *p;
+
+ if (num_levels <= 0 || bytes <= 0)
+ return -EINVAL;
+
+ payload_size = sizeof(*event) + sizeof(*level) + num_levels * sizeof(uint32_t);
+
+ pkt = audioreach_alloc_cmd_pkt(payload_size, APM_CMD_REGISTER_MODULE_EVENTS, 0,
+ graph->port->id, graph->shm_iid);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ event = p;
+ event->module_instance_id = graph->shm_iid;
+ event->event_id = EVENT_ID_SH_MEM_PULL_PUSH_MODE_WATERMARK;
+ event->is_register = 1;
+ event->event_config_payload_size = sizeof(*level) + num_levels * sizeof(uint32_t);
+ p += sizeof(*event);
+ level = p;
+ level->num_water_mark_levels = num_levels;
+
+ for (i = 0; i < num_levels; i++)
+ level->level[i] = (i + 1) * bytes;
+
+ return audioreach_graph_send_cmd_sync(graph, pkt, 0);
+}
+EXPORT_SYMBOL_GPL(audioreach_shmem_register_event);
+
+static int audioreach_shmem_set_media_format(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *mcfg)
+{
+ uint32_t num_channels = mcfg->num_channels;
+ struct apm_module_param_data *param_data;
+ struct payload_media_fmt_pcm *cfg;
+ struct media_format *header;
+ int rc, payload_size;
+ int i, j;
+ void *p;
+
+ if (num_channels > 4) {
+ dev_err(graph->dev, "Error: Invalid channels (%d)!\n", num_channels);
+ return -EINVAL;
+ }
+
+ payload_size = APM_SHMEM_FMT_CFG_PSIZE(num_channels) + APM_MODULE_PARAM_DATA_SIZE;
+
+ struct gpr_pkt *pkt __free(kfree) =
+ audioreach_alloc_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0,
+ graph->port->id, module->instance_id);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ param_data = p;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_MEDIA_FORMAT;
+ param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE;
+ p = p + APM_MODULE_PARAM_DATA_SIZE;
+
+ header = p;
+ if (mcfg->fmt == SND_AUDIOCODEC_PCM) {
+ header->data_format = DATA_FORMAT_FIXED_POINT;
+ header->fmt_id = MEDIA_FMT_ID_PCM;
+ header->payload_size = payload_size - sizeof(*header);
+
+ p = p + sizeof(*header);
+ cfg = p;
+ cfg->sample_rate = mcfg->sample_rate;
+ cfg->bit_width = mcfg->bit_width;
+ cfg->alignment = PCM_LSB_ALIGNED;
+ cfg->bits_per_sample = mcfg->bit_width;
+ cfg->q_factor = mcfg->bit_width - 1;
+ cfg->endianness = PCM_LITTLE_ENDIAN;
+ cfg->num_channels = mcfg->num_channels;
+ /* Convert the physical mapping to a logical mapping of the channels */
+ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < cfg->num_channels; i++) {
+ if (!mcfg->channel_map[i])
+ continue;
+ cfg->channel_mapping[j++] = mcfg->channel_map[i];
+ }
+ } else {
+ rc = audioreach_set_compr_media_format(header, p, mcfg);
+ if (rc)
+ return rc;
+ }
+
+ return audioreach_graph_send_cmd_sync(graph, pkt, 0);
+}
+
+int audioreach_gain_set_vol_ctrl(struct q6apm *apm,
+ const struct audioreach_module *module, int vol)
+{
+ struct param_id_vol_ctrl_master_gain *cfg;
+ struct apm_module_param_data *param_data;
+ int size = sizeof(*cfg) + APM_MODULE_PARAM_DATA_SIZE;
+ void *p;
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ param_data = p;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_VOL_CTRL_MASTER_GAIN;
+ param_data->param_size = size - APM_MODULE_PARAM_DATA_SIZE;
+
+ p = p + APM_MODULE_PARAM_DATA_SIZE;
+ cfg = p;
+ cfg->master_gain = vol;
+ return q6apm_send_cmd_sync(apm, pkt, 0);
+}
+EXPORT_SYMBOL_GPL(audioreach_gain_set_vol_ctrl);
+
+static int audioreach_gain_set(struct q6apm_graph *graph,
+ const struct audioreach_module *module)
+{
+ struct apm_module_param_data *param_data;
+ struct apm_gain_module_cfg *cfg;
+ int size = APM_GAIN_CFG_PSIZE;
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ cfg = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ param_data = &cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = APM_PARAM_ID_GAIN;
+ param_data->param_size = size - APM_MODULE_PARAM_DATA_SIZE;
+
+ cfg->gain_cfg.gain = module->gain;
+
+ return q6apm_send_cmd_sync(graph->apm, pkt, 0);
+}
+
+static int audioreach_speaker_protection(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ uint32_t operation_mode)
+{
+ return audioreach_send_u32_param(graph, module, PARAM_ID_SP_OP_MODE,
+ operation_mode);
+}
+
+static int audioreach_speaker_protection_vi(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *mcfg)
+{
+ u32 num_channels = mcfg->num_channels;
+ struct apm_module_sp_vi_op_mode_cfg *op_cfg;
+ struct apm_module_sp_vi_channel_map_cfg *cm_cfg;
+ struct apm_module_sp_vi_ex_mode_cfg *ex_cfg;
+ int op_sz, cm_sz, ex_sz;
+ struct apm_module_param_data *param_data;
+ int rc, i, payload_size, j;
+ struct gpr_pkt *pkt;
+ void *p;
+
+ if (num_channels > 2) {
+ dev_err(graph->dev, "Error: Invalid channels (%d)!\n", num_channels);
+ return -EINVAL;
+ }
+
+ op_sz = APM_SP_VI_OP_MODE_CFG_PSIZE(num_channels);
+ /* Channel mapping for Isense and Vsense, thus twice number of speakers. */
+ cm_sz = APM_SP_VI_CH_MAP_CFG_PSIZE(num_channels * 2);
+ ex_sz = APM_SP_VI_EX_MODE_CFG_PSIZE;
+
+ payload_size = op_sz + cm_sz + ex_sz;
+
+ pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ op_cfg = p;
+ param_data = &op_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_SP_VI_OP_MODE_CFG;
+ param_data->param_size = op_sz - APM_MODULE_PARAM_DATA_SIZE;
+
+ op_cfg->cfg.num_channels = num_channels;
+ op_cfg->cfg.operation_mode = PARAM_ID_SP_VI_OP_MODE_NORMAL;
+ p += op_sz;
+
+ cm_cfg = p;
+ param_data = &cm_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_SP_VI_CHANNEL_MAP_CFG;
+ param_data->param_size = cm_sz - APM_MODULE_PARAM_DATA_SIZE;
+
+ cm_cfg->cfg.num_channels = num_channels * 2;
+ /* Convert the physical mapping to a logical mapping of the channels */
+ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < num_channels; i++) {
+ if (!mcfg->channel_map[i])
+ continue;
+ /*
+ * Map speakers into Vsense and then Isense of each channel.
+ * E.g. for PCM_CHANNEL_FL and PCM_CHANNEL_FR to:
+ * [1, 2, 3, 4]
+ */
+ cm_cfg->cfg.channel_mapping[2 * j] = (mcfg->channel_map[i] - 1) * 2 + 1;
+ cm_cfg->cfg.channel_mapping[2 * j + 1] = (mcfg->channel_map[i] - 1) * 2 + 2;
+
+ ++j;
+ }
+
+ p += cm_sz;
+
+ ex_cfg = p;
+ param_data = &ex_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_SP_VI_EX_MODE_CFG;
+ param_data->param_size = ex_sz - APM_MODULE_PARAM_DATA_SIZE;
+
+ ex_cfg->cfg.factory_mode = 0;
+
+ rc = q6apm_send_cmd_sync(graph->apm, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+int audioreach_set_media_format(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *cfg)
+{
+ int rc;
+
+ switch (module->module_id) {
+ case MODULE_ID_DATA_LOGGING:
+ rc = audioreach_module_enable(graph, module, true);
+ if (!rc)
+ rc = audioreach_logging_set_media_format(graph, module);
+ break;
+ case MODULE_ID_PCM_DEC:
+ case MODULE_ID_PCM_ENC:
+ case MODULE_ID_PCM_CNV:
+ case MODULE_ID_PLACEHOLDER_DECODER:
+ case MODULE_ID_PLACEHOLDER_ENCODER:
+ rc = audioreach_pcm_set_media_format(graph, module, cfg);
+ break;
+ case MODULE_ID_DISPLAY_PORT_SINK:
+ rc = audioreach_display_port_set_media_format(graph, module, cfg);
+ break;
+ case MODULE_ID_SMECNS_V2:
+ rc = audioreach_set_module_config(graph, module, cfg);
+ break;
+ case MODULE_ID_I2S_SOURCE:
+ case MODULE_ID_I2S_SINK:
+ rc = audioreach_i2s_set_media_format(graph, module, cfg);
+ break;
+ case MODULE_ID_WR_SHARED_MEM_EP:
+ case MODULE_ID_SH_MEM_PULL_MODE:
+ rc = audioreach_shmem_set_media_format(graph, module, cfg);
+ break;
+ case MODULE_ID_GAIN:
+ rc = audioreach_gain_set(graph, module);
+ break;
+ case MODULE_ID_CODEC_DMA_SINK:
+ case MODULE_ID_CODEC_DMA_SOURCE:
+ rc = audioreach_codec_dma_set_media_format(graph, module, cfg);
+ break;
+ case MODULE_ID_SAL:
+ rc = audioreach_sal_set_media_format(graph, module, cfg);
+ if (!rc)
+ rc = audioreach_sal_limiter_enable(graph, module, true);
+ break;
+ case MODULE_ID_MFC:
+ rc = audioreach_mfc_set_media_format(graph, module, cfg);
+ break;
+ case MODULE_ID_GAPLESS:
+ rc = audioreach_gapless_set_media_format(graph, module, cfg);
+ break;
+ case MODULE_ID_SPEAKER_PROTECTION:
+ rc = audioreach_speaker_protection(graph, module,
+ PARAM_ID_SP_OP_MODE_NORMAL);
+ if (!rc)
+ rc = audioreach_module_enable(graph, module, true);
+
+ break;
+ case MODULE_ID_SPEAKER_PROTECTION_VI:
+ rc = audioreach_speaker_protection_vi(graph, module, cfg);
+ if (!rc)
+ rc = audioreach_module_enable(graph, module, true);
+ break;
+ case MODULE_ID_AUDIO_IF_SOURCE:
+ case MODULE_ID_AUDIO_IF_SINK:
+ rc = audioreach_audio_if_set_media_format(graph, module, cfg);
+ break;
+
+ default:
+ rc = 0;
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(audioreach_set_media_format);
+
+void audioreach_graph_free_buf(struct q6apm_graph *graph)
+{
+ struct audioreach_graph_data *port;
+
+ mutex_lock(&graph->lock);
+ port = &graph->rx_data;
+ port->num_periods = 0;
+ kfree(port->buf);
+ port->buf = NULL;
+
+ port = &graph->tx_data;
+ port->num_periods = 0;
+ kfree(port->buf);
+ port->buf = NULL;
+ mutex_unlock(&graph->lock);
+}
+EXPORT_SYMBOL_GPL(audioreach_graph_free_buf);
+
+int audioreach_setup_push_pull(struct q6apm_graph *graph, phys_addr_t bphys,
+ phys_addr_t pphys, uint32_t mem_map_handle,
+ uint32_t pos_buf_mem_map_handle, uint32_t size)
+{
+ struct param_id_sh_mem_pull_push_mode_cfg *cfg;
+ struct apm_module_param_data *param_data;
+ int payload_size;
+ struct gpr_pkt *pkt __free(kfree) = NULL;
+ void *p;
+
+ payload_size = sizeof(*cfg) + APM_MODULE_PARAM_DATA_SIZE;
+ pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ param_data = p;
+ param_data->module_instance_id = graph->shm_iid;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_SH_MEM_PULL_PUSH_MODE_CFG;
+ param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE;
+
+ p = p + APM_MODULE_PARAM_DATA_SIZE;
+ cfg = p;
+
+ cfg->shared_circ_buf_addr_lsw = lower_32_bits(bphys);
+ cfg->shared_circ_buf_addr_msw = upper_32_bits(bphys);
+ cfg->shared_circ_buf_size = size;
+ cfg->circ_buf_mem_map_handle = mem_map_handle;
+ cfg->shared_pos_buf_addr_lsw = lower_32_bits(pphys);
+ cfg->shared_pos_buf_addr_msw = upper_32_bits(pphys);
+ cfg->pos_buf_mem_map_handle = pos_buf_mem_map_handle;
+
+ return q6apm_send_cmd_sync(graph->apm, pkt, 0);
+}
+EXPORT_SYMBOL_GPL(audioreach_setup_push_pull);
+
+int audioreach_shared_memory_send_eos(struct q6apm_graph *graph)
+{
+ struct data_cmd_wr_sh_mem_ep_eos *eos;
+ int iid = graph->shm_iid;
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_cmd_pkt(sizeof(*eos),
+ DATA_CMD_WR_SH_MEM_EP_EOS, 0, graph->port->id, iid);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ eos = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ eos->policy = WR_SH_MEM_EP_EOS_POLICY_LAST;
+
+ return gpr_send_port_pkt(graph->port, pkt);
+}
+EXPORT_SYMBOL_GPL(audioreach_shared_memory_send_eos);
diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h
new file mode 100644
index 000000000..35541b2d8
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/audioreach.h
@@ -0,0 +1,1045 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __AUDIOREACH_H__
+#define __AUDIOREACH_H__
+#include <linux/types.h>
+#include <linux/soc/qcom/apr.h>
+#include <uapi/sound/snd_ar_tokens.h>
+#include <sound/soc.h>
+struct q6apm;
+struct q6apm_graph;
+
+/* Module IDs */
+#define MODULE_ID_WR_SHARED_MEM_EP 0x07001000
+#define MODULE_ID_RD_SHARED_MEM_EP 0x07001001
+#define MODULE_ID_GAIN 0x07001002
+#define MODULE_ID_PCM_CNV 0x07001003
+#define MODULE_ID_PCM_ENC 0x07001004
+#define MODULE_ID_PCM_DEC 0x07001005
+#define MODULE_ID_SH_MEM_PULL_MODE 0x07001006
+#define MODULE_ID_SH_MEM_PUSH_MODE 0x07001007
+#define MODULE_ID_PLACEHOLDER_ENCODER 0x07001008
+#define MODULE_ID_PLACEHOLDER_DECODER 0x07001009
+#define MODULE_ID_I2S_SINK 0x0700100A
+#define MODULE_ID_I2S_SOURCE 0x0700100B
+#define MODULE_ID_SAL 0x07001010
+#define MODULE_ID_MFC 0x07001015
+#define MODULE_ID_DATA_LOGGING 0x0700101A
+#define MODULE_ID_AAC_DEC 0x0700101F
+#define MODULE_ID_CODEC_DMA_SINK 0x07001023
+#define MODULE_ID_CODEC_DMA_SOURCE 0x07001024
+#define MODULE_ID_FLAC_DEC 0x0700102F
+#define MODULE_ID_SMECNS_V2 0x07001031
+#define MODULE_ID_MP3_DECODE 0x0700103B
+#define MODULE_ID_GAPLESS 0x0700104D
+#define MODULE_ID_DISPLAY_PORT_SINK 0x07001069
+#define MODULE_ID_SPEAKER_PROTECTION 0x070010E2
+#define MODULE_ID_SPEAKER_PROTECTION_VI 0x070010E3
+#define MODULE_ID_OPUS_DEC 0x07001174
+#define MODULE_ID_AUDIO_IF_SINK 0x0700117C
+#define MODULE_ID_AUDIO_IF_SOURCE 0x0700117D
+
+#define APM_CMD_GET_SPF_STATE 0x01001021
+#define APM_CMD_RSP_GET_SPF_STATE 0x02001007
+
+#define APM_MODULE_INSTANCE_ID 0x00000001
+#define PRM_MODULE_INSTANCE_ID 0x00000002
+#define AMDB_MODULE_INSTANCE_ID 0x00000003
+#define VCPM_MODULE_INSTANCE_ID 0x00000004
+#define AR_MODULE_INSTANCE_ID_START 0x00006000
+#define AR_MODULE_INSTANCE_ID_END 0x00007000
+#define AR_MODULE_DYNAMIC_INSTANCE_ID_START 0x00007000
+#define AR_MODULE_DYNAMIC_INSTANCE_ID_END 0x00008000
+#define AR_CONT_INSTANCE_ID_START 0x00005000
+#define AR_CONT_INSTANCE_ID_END 0x00006000
+#define AR_SG_INSTANCE_ID_START 0x00004000
+
+#define APM_CMD_GRAPH_OPEN 0x01001000
+#define APM_CMD_GRAPH_PREPARE 0x01001001
+#define APM_CMD_GRAPH_START 0x01001002
+#define APM_CMD_GRAPH_STOP 0x01001003
+#define APM_CMD_GRAPH_CLOSE 0x01001004
+#define APM_CMD_GRAPH_FLUSH 0x01001005
+#define APM_CMD_SET_CFG 0x01001006
+#define APM_CMD_GET_CFG 0x01001007
+#define APM_CMD_SHARED_MEM_MAP_REGIONS 0x0100100C
+#define APM_CMD_SHARED_MEM_UNMAP_REGIONS 0x0100100D
+#define APM_CMD_REGISTER_MODULE_EVENTS 0x0100100E
+#define APM_EVENT_MODULE_TO_CLIENT 0x03001000
+#define APM_CMD_RSP_SHARED_MEM_MAP_REGIONS 0x02001001
+#define APM_MMAP_TOKEN_GID_MASK GENMASK(15, 0)
+#define APM_MMAP_TOKEN_MAP_TYPE_POS_BUF BIT(16)
+#define APM_MMAP_TOKEN_MAP_TYPE_SHIFT 16
+#define APM_CMD_RSP_GET_CFG 0x02001000
+#define APM_CMD_CLOSE_ALL 0x01001013
+#define APM_CMD_REGISTER_SHARED_CFG 0x0100100A
+#define EVENT_ID_SH_MEM_PULL_PUSH_MODE_WATERMARK 0x0800101C
+
+/**
+ * struct event_cfg_sh_mem_pull_push_mode_watermark_t - Watermark config
+ * @num_water_mark_levels: Number of watermark levels.
+ * @level: Watermark levels.
+ *
+ * If @num_water_mark_levels is zero, no watermark levels are specified
+ * and watermark events are not supported.
+ */
+struct event_cfg_sh_mem_pull_push_mode_watermark_t {
+ uint32_t num_water_mark_levels;
+ uint32_t level[];
+} __packed;
+
+/**
+ * struct apm_module_register_events - Register or unregister module events
+ * @module_instance_id: Module instance identifier.
+ * @event_id: Module event identifier.
+ * @is_register: 1 to register the event, 0 to unregister it.
+ * @error_code: Error code for out-of-band command mode.
+ * @event_config_payload_size: Event configuration payload size in bytes.
+ * @reserved: Reserved for alignment; must be zero.
+ */
+struct apm_module_register_events {
+ uint32_t module_instance_id;
+ uint32_t event_id;
+ uint32_t is_register;
+ uint32_t error_code;
+ uint32_t event_config_payload_size;
+ uint32_t reserved;
+} __packed;
+
+/**
+ * struct apm_module_event - Module event descriptor
+ * @event_id: Module event identifier.
+ * @event_payload_size: Event payload size in bytes.
+ */
+struct apm_module_event {
+ uint32_t event_id;
+ uint32_t event_payload_size;
+} __packed;
+
+#define APM_MEMORY_MAP_SHMEM8_4K_POOL 3
+
+struct apm_cmd_shared_mem_map_regions {
+ uint16_t mem_pool_id;
+ uint16_t num_regions;
+ uint32_t property_flag;
+} __packed;
+
+struct apm_shared_map_region_payload {
+ uint32_t shm_addr_lsw;
+ uint32_t shm_addr_msw;
+ uint32_t mem_size_bytes;
+} __packed;
+
+struct apm_cmd_shared_mem_unmap_regions {
+ uint32_t mem_map_handle;
+} __packed;
+
+struct apm_cmd_rsp_shared_mem_map_regions {
+ uint32_t mem_map_handle;
+} __packed;
+
+/* APM module */
+#define APM_PARAM_ID_SUB_GRAPH_LIST 0x08001005
+
+#define APM_PARAM_ID_MODULE_LIST 0x08001002
+
+struct apm_param_id_modules_list {
+ uint32_t num_modules_list;
+} __packed;
+
+#define APM_PARAM_ID_MODULE_PROP 0x08001003
+
+struct apm_param_id_module_prop {
+ uint32_t num_modules_prop_cfg;
+} __packed;
+
+struct apm_module_prop_cfg {
+ uint32_t instance_id;
+ uint32_t num_props;
+} __packed;
+
+#define APM_PARAM_ID_MODULE_CONN 0x08001004
+
+struct apm_param_id_module_conn {
+ uint32_t num_connections;
+} __packed;
+
+struct apm_module_conn_obj {
+ uint32_t src_mod_inst_id;
+ uint32_t src_mod_op_port_id;
+ uint32_t dst_mod_inst_id;
+ uint32_t dst_mod_ip_port_id;
+} __packed;
+
+#define APM_PARAM_ID_GAIN 0x08001006
+
+struct param_id_gain_cfg {
+ uint16_t gain;
+ uint16_t reserved;
+} __packed;
+
+#define PARAM_ID_PCM_OUTPUT_FORMAT_CFG 0x08001008
+
+struct param_id_pcm_output_format_cfg {
+ uint32_t data_format;
+ uint32_t fmt_id;
+ uint32_t payload_size;
+} __packed;
+
+struct payload_pcm_output_format_cfg {
+ uint16_t bit_width;
+ uint16_t alignment;
+ uint16_t bits_per_sample;
+ uint16_t q_factor;
+ uint16_t endianness;
+ uint16_t interleaved;
+ uint16_t reserved;
+ uint16_t num_channels;
+ uint8_t channel_mapping[];
+} __packed;
+
+#define PARAM_ID_ENC_BITRATE 0x08001052
+
+struct param_id_enc_bitrate_param {
+ uint32_t bitrate;
+} __packed;
+
+#define DATA_FORMAT_FIXED_POINT 1
+#define DATA_FORMAT_GENERIC_COMPRESSED 5
+#define DATA_FORMAT_RAW_COMPRESSED 6
+#define PCM_LSB_ALIGNED 1
+#define PCM_MSB_ALIGNED 2
+#define PCM_LITTLE_ENDIAN 1
+#define PCM_BIT_ENDIAN 2
+
+#define MEDIA_FMT_ID_PCM 0x09001000
+#define MEDIA_FMT_ID_MP3 0x09001009
+#define SAMPLE_RATE_48K 48000
+#define BIT_WIDTH_16 16
+
+#define APM_PARAM_ID_PROP_PORT_INFO 0x08001015
+
+struct apm_modules_prop_info {
+ uint32_t max_ip_port;
+ uint32_t max_op_port;
+} __packed;
+
+/* Shared memory module */
+#define DATA_CMD_WR_SH_MEM_EP_DATA_BUFFER 0x04001000
+#define WR_SH_MEM_EP_TIMESTAMP_VALID_FLAG BIT(31)
+#define WR_SH_MEM_EP_LAST_BUFFER_FLAG BIT(30)
+#define WR_SH_MEM_EP_TS_CONTINUE_FLAG BIT(29)
+#define WR_SH_MEM_EP_EOF_FLAG BIT(4)
+
+struct apm_data_cmd_wr_sh_mem_ep_data_buffer {
+ uint32_t buf_addr_lsw;
+ uint32_t buf_addr_msw;
+ uint32_t mem_map_handle;
+ uint32_t buf_size;
+ uint32_t timestamp_lsw;
+ uint32_t timestamp_msw;
+ uint32_t flags;
+} __packed;
+
+#define DATA_CMD_WR_SH_MEM_EP_DATA_BUFFER_V2 0x0400100A
+
+struct apm_data_cmd_wr_sh_mem_ep_data_buffer_v2 {
+ uint32_t buf_addr_lsw;
+ uint32_t buf_addr_msw;
+ uint32_t mem_map_handle;
+ uint32_t buf_size;
+ uint32_t timestamp_lsw;
+ uint32_t timestamp_msw;
+ uint32_t flags;
+ uint32_t md_addr_lsw;
+ uint32_t md_addr_msw;
+ uint32_t md_map_handle;
+ uint32_t md_buf_size;
+} __packed;
+
+#define DATA_CMD_RSP_WR_SH_MEM_EP_DATA_BUFFER_DONE 0x05001000
+
+struct data_cmd_rsp_wr_sh_mem_ep_data_buffer_done {
+ uint32_t buf_addr_lsw;
+ uint32_t buf_addr_msw;
+ uint32_t mem_map_handle;
+ uint32_t status;
+
+} __packed;
+
+#define DATA_CMD_RSP_WR_SH_MEM_EP_DATA_BUFFER_DONE_V2 0x05001004
+
+struct data_cmd_rsp_wr_sh_mem_ep_data_buffer_done_v2 {
+ uint32_t buf_addr_lsw;
+ uint32_t buf_addr_msw;
+ uint32_t mem_map_handle;
+ uint32_t status;
+ uint32_t md_buf_addr_lsw;
+ uint32_t md_buf_addr_msw;
+ uint32_t md_mem_map_handle;
+ uint32_t md_status;
+} __packed;
+
+#define PARAM_ID_MEDIA_FORMAT 0x0800100C
+#define DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT 0x04001001
+
+struct apm_media_format {
+ uint32_t data_format;
+ uint32_t fmt_id;
+ uint32_t payload_size;
+} __packed;
+
+#define MEDIA_FMT_ID_FLAC 0x09001004
+
+struct payload_media_fmt_flac_t {
+ uint16_t num_channels;
+ uint16_t sample_size;
+ uint16_t min_blk_size;
+ uint16_t max_blk_size;
+ uint32_t sample_rate;
+ uint32_t min_frame_size;
+ uint32_t max_frame_size;
+} __packed;
+
+#define MEDIA_FMT_ID_AAC 0x09001001
+
+struct payload_media_fmt_aac_t {
+ uint16_t aac_fmt_flag;
+ uint16_t audio_obj_type;
+ uint16_t num_channels;
+ uint16_t total_size_of_PCE_bits;
+ uint32_t sample_rate;
+} __packed;
+
+#define MEDIA_FMT_ID_OPUS 0x09001039
+struct payload_media_fmt_opus_t {
+ uint16_t bitstream_format;
+ uint16_t payload_type;
+ uint8_t version;
+ uint8_t num_channels;
+ uint16_t pre_skip;
+ uint32_t sample_rate;
+ uint16_t output_gain;
+ uint8_t mapping_family;
+ uint8_t stream_count;
+ uint8_t coupled_count;
+ uint8_t channel_mapping[8];
+ uint8_t reserved[3];
+} __packed;
+
+#define DATA_CMD_WR_SH_MEM_EP_EOS 0x04001002
+#define WR_SH_MEM_EP_EOS_POLICY_LAST 1
+#define WR_SH_MEM_EP_EOS_POLICY_EACH 2
+
+struct data_cmd_wr_sh_mem_ep_eos {
+ uint32_t policy;
+
+} __packed;
+
+#define DATA_CMD_RD_SH_MEM_EP_DATA_BUFFER 0x04001003
+
+struct data_cmd_rd_sh_mem_ep_data_buffer {
+ uint32_t buf_addr_lsw;
+ uint32_t buf_addr_msw;
+ uint32_t mem_map_handle;
+ uint32_t buf_size;
+} __packed;
+
+#define DATA_CMD_RSP_RD_SH_MEM_EP_DATA_BUFFER 0x05001002
+
+struct data_cmd_rsp_rd_sh_mem_ep_data_buffer_done {
+ uint32_t status;
+ uint32_t buf_addr_lsw;
+ uint32_t buf_addr_msw;
+ uint32_t mem_map_handle;
+ uint32_t data_size;
+ uint32_t offset;
+ uint32_t timestamp_lsw;
+ uint32_t timestamp_msw;
+ uint32_t flags;
+ uint32_t num_frames;
+} __packed;
+
+#define DATA_CMD_RD_SH_MEM_EP_DATA_BUFFER_V2 0x0400100B
+
+struct data_cmd_rd_sh_mem_ep_data_buffer_v2 {
+ uint32_t buf_addr_lsw;
+ uint32_t buf_addr_msw;
+ uint32_t mem_map_handle;
+ uint32_t buf_size;
+ uint32_t md_buf_addr_lsw;
+ uint32_t md_buf_addr_msw;
+ uint32_t md_mem_map_handle;
+ uint32_t md_buf_size;
+} __packed;
+
+#define DATA_CMD_RSP_RD_SH_MEM_EP_DATA_BUFFER_V2 0x05001005
+
+struct data_cmd_rsp_rd_sh_mem_ep_data_buffer_done_v2 {
+ uint32_t status;
+ uint32_t buf_addr_lsw;
+ uint32_t buf_addr_msw;
+ uint32_t mem_map_handle;
+ uint32_t data_size;
+ uint32_t offset;
+ uint32_t timestamp_lsw;
+ uint32_t timestamp_msw;
+ uint32_t flags;
+ uint32_t num_frames;
+ uint32_t md_status;
+ uint32_t md_buf_addr_lsw;
+ uint32_t md_buf_addr_msw;
+ uint32_t md_mem_map_handle;
+ uint32_t md_size;
+} __packed;
+
+#define PARAM_ID_RD_SH_MEM_CFG 0x08001007
+
+struct param_id_rd_sh_mem_cfg {
+ uint32_t num_frames_per_buffer;
+ uint32_t metadata_control_flags;
+
+} __packed;
+
+#define DATA_CMD_WR_SH_MEM_EP_EOS_RENDERED 0x05001001
+
+struct data_cmd_wr_sh_mem_ep_eos_rendered {
+ uint32_t module_instance_id;
+ uint32_t render_status;
+} __packed;
+
+#define MODULE_ID_WR_SHARED_MEM_EP 0x07001000
+
+struct apm_cmd_header {
+ uint32_t payload_address_lsw;
+ uint32_t payload_address_msw;
+ uint32_t mem_map_handle;
+ uint32_t payload_size;
+} __packed;
+
+#define APM_CMD_HDR_SIZE sizeof(struct apm_cmd_header)
+
+struct apm_module_param_data {
+ uint32_t module_instance_id;
+ uint32_t param_id;
+ uint32_t param_size;
+ uint32_t error_code;
+} __packed;
+
+#define APM_MODULE_PARAM_DATA_SIZE sizeof(struct apm_module_param_data)
+
+struct apm_module_param_shared_data {
+ uint32_t param_id;
+ uint32_t param_size;
+} __packed;
+
+struct apm_prop_data {
+ uint32_t prop_id;
+ uint32_t prop_size;
+} __packed;
+
+/* Sub-Graph Properties */
+#define APM_PARAM_ID_SUB_GRAPH_CONFIG 0x08001001
+
+struct apm_param_id_sub_graph_cfg {
+ uint32_t num_sub_graphs;
+} __packed;
+
+struct apm_sub_graph_cfg {
+ uint32_t sub_graph_id;
+ uint32_t num_sub_graph_prop;
+} __packed;
+
+#define APM_SUB_GRAPH_PROP_ID_PERF_MODE 0x0800100E
+
+struct apm_sg_prop_id_perf_mode {
+ uint32_t perf_mode;
+} __packed;
+
+#define APM_SG_PROP_ID_PERF_MODE_SIZE 4
+
+#define APM_SUB_GRAPH_PROP_ID_DIRECTION 0x0800100F
+
+struct apm_sg_prop_id_direction {
+ uint32_t direction;
+} __packed;
+
+#define APM_SG_PROP_ID_DIR_SIZE 4
+
+#define APM_SUB_GRAPH_PROP_ID_SCENARIO_ID 0x08001010
+#define APM_SUB_GRAPH_SID_AUDIO_PLAYBACK 0x1
+#define APM_SUB_GRAPH_SID_AUDIO_RECORD 0x2
+#define APM_SUB_GRAPH_SID_AUDIO_VOICE_CALL 0x3
+
+struct apm_sg_prop_id_scenario_id {
+ uint32_t scenario_id;
+} __packed;
+
+#define APM_SG_PROP_ID_SID_SIZE 4
+/* container api */
+#define APM_PARAM_ID_CONTAINER_CONFIG 0x08001000
+
+struct apm_param_id_container_cfg {
+ uint32_t num_containers;
+} __packed;
+
+struct apm_container_cfg {
+ uint32_t container_id;
+ uint32_t num_prop;
+} __packed;
+
+struct apm_cont_capability {
+ uint32_t capability_id;
+} __packed;
+
+#define APM_CONTAINER_PROP_ID_CAPABILITY_LIST 0x08001011
+#define APM_CONTAINER_PROP_ID_CAPABILITY_SIZE 8
+
+#define APM_PROP_ID_INVALID 0x0
+#define APM_CONTAINER_CAP_ID_PP 0x1
+#define APM_CONTAINER_CAP_ID_PP 0x1
+
+struct apm_cont_prop_id_cap_list {
+ uint32_t num_capability_id;
+} __packed;
+
+#define APM_CONTAINER_PROP_ID_GRAPH_POS 0x08001012
+
+struct apm_cont_prop_id_graph_pos {
+ uint32_t graph_pos;
+} __packed;
+
+#define APM_CONTAINER_PROP_ID_STACK_SIZE 0x08001013
+
+struct apm_cont_prop_id_stack_size {
+ uint32_t stack_size;
+} __packed;
+
+#define APM_CONTAINER_PROP_ID_PROC_DOMAIN 0x08001014
+
+struct apm_cont_prop_id_domain {
+ uint32_t proc_domain;
+} __packed;
+
+#define CONFIG_I2S_WS_SRC_EXTERNAL 0x0
+#define CONFIG_I2S_WS_SRC_INTERNAL 0x1
+
+#define PARAM_ID_I2S_INTF_CFG 0x08001019
+struct param_id_i2s_intf_cfg {
+ uint32_t lpaif_type;
+ uint32_t intf_idx;
+ uint16_t sd_line_idx;
+ uint16_t ws_src;
+} __packed;
+
+#define I2S_INTF_TYPE_PRIMARY 0
+#define I2S_INTF_TYPE_SECONDARY 1
+#define I2S_INTF_TYPE_TERTIARY 2
+#define I2S_INTF_TYPE_QUATERNARY 3
+#define I2S_INTF_TYPE_QUINARY 4
+#define I2S_SD0 1
+#define I2S_SD1 2
+#define I2S_SD2 3
+#define I2S_SD3 4
+
+#define PORT_ID_I2S_INPUT 2
+#define PORT_ID_I2S_OUPUT 1
+#define I2S_STACK_SIZE 2048
+
+#define PARAM_ID_AUDIO_IF_INTF_CFG 0x08001B11
+
+/*
+ * struct param_id_audio_if_intf_cfg - Audio interface configuration
+ * @qaif_type: Audio interface type (e.g. QAIF, QAIF_VA)
+ * @intf_idx: Interface instance index
+ * @intf_mode: Interface operating mode (TDM/PCM/I2S)
+ * @ctrl_data_out_enable: Enable sharing of data-out signal with other masters
+ * @active_slot_mask: Bitmask indicating active slots
+ * @nslots_per_frame: Number of slots per audio frame
+ * @slot_width: Width of each slot in bits
+ * @active_lane_mask: Bitmask of active data lanes
+ * @frame_sync_rate: Frame sync rate in Hz
+ * @frame_sync_src: Frame sync source selection
+ * @frame_sync_mode: Frame sync mode configuration
+ * @invert_frame_sync_pulse: Invert frame sync polarity when set
+ * @frame_sync_data_delay: Data delay from frame sync in bit clocks
+ * @bit_clk_type: Bit clock type (internal / external)
+ * @inv_int_bit_clk: Invert internal bit clock when set
+ * @inv_ext_bit_clk: Invert external bit clock when set
+ *
+ * This structure defines configuration parameters for the Qualcomm
+ * Audio Interface (QAIF) block. It is used to program interface
+ * characteristics such as slot configuration, clocking and frame
+ * synchronization behaviour.
+ */
+struct param_id_audio_if_intf_cfg {
+ uint16_t qaif_type;
+ uint16_t intf_idx;
+ uint16_t intf_mode;
+ uint16_t ctrl_data_out_enable;
+ uint32_t active_slot_mask;
+ uint16_t nslots_per_frame;
+ uint16_t slot_width;
+ uint32_t active_lane_mask;
+ uint32_t frame_sync_rate;
+ uint16_t frame_sync_src;
+ uint16_t frame_sync_mode;
+ uint16_t invert_frame_sync_pulse;
+ uint16_t frame_sync_data_delay;
+ uint16_t bit_clk_type;
+ uint8_t inv_int_bit_clk;
+ uint8_t inv_ext_bit_clk;
+} __packed;
+
+#define PARAM_ID_HW_EP_FRAME_DURATION 0x08001B2F
+#define AUDIO_IF_FRAME_DURATION_US 1000
+#define AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE 1
+#define AUDIO_IF_FRAME_DURATION_MIN_US 1
+#define AUDIO_IF_FRAME_DURATION_MAX_US 100000
+
+/**
+ * struct param_id_hw_ep_frame_duration - Hardware endpoint frame duration
+ * @frame_duration_in_us: Frame duration in microseconds.
+ * @allow_frame_duration_normalization: Permit SPF to normalize frame duration.
+ * @min_normalized_frame_dur_us: Minimum normalized frame duration in microseconds.
+ * @max_normalized_frame_dur_us: Maximum normalized frame duration in microseconds.
+ *
+ * This structure configures the frame duration for the Audio IF hardware
+ * endpoint and, when enabled, the allowed normalization range.
+ */
+struct param_id_hw_ep_frame_duration {
+ uint32_t frame_duration_in_us;
+ uint32_t allow_frame_duration_normalization;
+ uint32_t min_normalized_frame_dur_us;
+ uint32_t max_normalized_frame_dur_us;
+} __packed;
+
+#define PARAM_ID_DISPLAY_PORT_INTF_CFG 0x08001154
+
+struct param_id_display_port_intf_cfg {
+ uint32_t channel_allocation;
+ /* Multi-Steam Transport index */
+ uint32_t mst_idx;
+ uint32_t dptx_idx;
+} __packed;
+
+#define PARAM_ID_HW_EP_MF_CFG 0x08001017
+struct param_id_hw_ep_mf {
+ uint32_t sample_rate;
+ uint16_t bit_width;
+ uint16_t num_channels;
+ uint32_t data_format;
+} __packed;
+
+#define PARAM_ID_HW_EP_FRAME_SIZE_FACTOR 0x08001018
+
+struct param_id_fram_size_factor {
+ uint32_t frame_size_factor;
+} __packed;
+
+#define APM_CONTAINER_PROP_ID_PARENT_CONTAINER_ID 0x080010CB
+
+struct apm_cont_prop_id_parent_container {
+ uint32_t parent_container_id;
+} __packed;
+
+#define APM_CONTAINER_PROP_ID_HEAP_ID 0x08001174
+#define APM_CONT_HEAP_DEFAULT 0x1
+#define APM_CONT_HEAP_LOW_POWER 0x2
+
+struct apm_cont_prop_id_headp_id {
+ uint32_t heap_id;
+} __packed;
+
+struct apm_modules_list {
+ uint32_t sub_graph_id;
+ uint32_t container_id;
+ uint32_t num_modules;
+} __packed;
+
+struct apm_module_obj {
+ uint32_t module_id;
+ uint32_t instance_id;
+} __packed;
+
+#define APM_MODULE_PROP_ID_PORT_INFO 0x08001015
+#define APM_MODULE_PROP_ID_PORT_INFO_SZ 8
+struct apm_module_prop_id_port_info {
+ uint32_t max_ip_port;
+ uint32_t max_op_port;
+} __packed;
+
+#define DATA_LOGGING_MAX_INPUT_PORTS 0x1
+#define DATA_LOGGING_MAX_OUTPUT_PORTS 0x1
+#define DATA_LOGGING_STACK_SIZE 2048
+#define PARAM_ID_DATA_LOGGING_CONFIG 0x08001031
+
+struct data_logging_config {
+ uint32_t log_code;
+ uint32_t log_tap_point_id;
+ uint32_t mode;
+} __packed;
+
+/* Speaker Protection */
+#define PARAM_ID_SP_OP_MODE 0x080011e9
+#define PARAM_ID_SP_OP_MODE_NORMAL 0
+#define PARAM_ID_SP_OP_MODE_CALIBRATION 1
+#define PARAM_ID_SP_OP_MODE_FACTORY_TEST 2
+#define PARAM_ID_SP_OP_MODE_VALIDATION 3
+
+struct param_id_sp_op_mode {
+ uint32_t operation_mode;
+} __packed;
+
+/* Speaker Protection VI */
+
+#define PARAM_ID_SP_VI_OP_MODE_CFG 0x080011f4
+#define PARAM_ID_SP_VI_OP_MODE_NORMAL 0
+#define PARAM_ID_SP_VI_OP_MODE_CALIBRATION 1
+#define PARAM_ID_SP_VI_OP_MODE_FACTORY_TEST 2
+#define PARAM_ID_SP_VI_OP_MODE_VALIDATION 3
+struct param_id_sp_vi_op_mode_cfg {
+ uint32_t num_channels;
+ uint32_t operation_mode;
+ uint32_t quick_calibration;
+ uint32_t r0_t0_selection[];
+} __packed;
+
+#define PARAM_ID_SP_VI_EX_MODE_CFG 0x080011ff
+struct param_id_sp_vi_ex_mode_cfg {
+ uint32_t factory_mode;
+} __packed;
+
+#define PARAM_ID_SP_VI_CHANNEL_MAP_CFG 0x08001203
+struct param_id_sp_vi_channel_map_cfg {
+ uint32_t num_channels;
+ /* [ Vsense of ch 1, Isense of ch 1, Vsense of ch 2, Isense of ch 2, ... ] */
+ uint32_t channel_mapping[];
+} __packed;
+
+#define PARAM_ID_SAL_OUTPUT_CFG 0x08001016
+struct param_id_sal_output_config {
+ uint32_t bits_per_sample;
+} __packed;
+
+#define PARAM_ID_SAL_LIMITER_ENABLE 0x0800101E
+struct param_id_sal_limiter_enable {
+ uint32_t enable_lim;
+} __packed;
+
+#define PARAM_ID_MFC_OUTPUT_MEDIA_FORMAT 0x08001024
+#define PARAM_ID_EARLY_EOS_DELAY 0x0800114C
+#define EARLY_EOS_DELAY_MS 150
+
+struct param_id_mfc_media_format {
+ uint32_t sample_rate;
+ uint16_t bit_width;
+ uint16_t num_channels;
+ uint16_t channel_mapping[];
+} __packed;
+
+struct param_id_gapless_early_eos_delay_t {
+ uint32_t early_eos_delay_ms;
+} __packed;
+
+struct media_format {
+ uint32_t data_format;
+ uint32_t fmt_id;
+ uint32_t payload_size;
+} __packed;
+
+struct payload_media_fmt_pcm {
+ uint32_t sample_rate;
+ uint16_t bit_width;
+ uint16_t alignment;
+ uint16_t bits_per_sample;
+ uint16_t q_factor;
+ uint16_t endianness;
+ uint16_t num_channels;
+ uint8_t channel_mapping[];
+} __packed;
+
+#define PARAM_ID_MODULE_ENABLE 0x08001026
+struct param_id_module_enable {
+ uint32_t enable;
+} __packed;
+
+#define PARAM_ID_CODEC_DMA_INTF_CFG 0x08001063
+
+struct param_id_codec_dma_intf_cfg {
+ /* 1 - RXTX
+ * 2 - WSA
+ * 3 - VA
+ * 4 - AXI
+ */
+ uint32_t lpaif_type;
+ /*
+ * RX0 | TX0 = 1
+ * RX1 | TX1 = 2
+ * RX2 | TX2 = 3... so on
+ */
+ uint32_t intf_index;
+ uint32_t active_channels_mask;
+} __packed;
+
+struct audio_hw_clk_cfg {
+ uint32_t clock_id;
+ uint32_t clock_freq;
+ uint32_t clock_attri;
+ uint32_t clock_root;
+} __packed;
+
+struct audio_hw_clk_rel_cfg {
+ uint32_t clock_id;
+} __packed;
+
+#define PARAM_ID_HW_EP_POWER_MODE_CFG 0x8001176
+#define AR_HW_EP_POWER_MODE_0 0 /* default */
+#define AR_HW_EP_POWER_MODE_1 1 /* XO Shutdown allowed */
+#define AR_HW_EP_POWER_MODE_2 2 /* XO Shutdown not allowed */
+
+struct param_id_hw_ep_power_mode_cfg {
+ uint32_t power_mode;
+} __packed;
+
+#define PARAM_ID_HW_EP_DMA_DATA_ALIGN 0x08001233
+#define AR_HW_EP_DMA_DATA_ALIGN_MSB 0
+#define AR_HW_EP_DMA_DATA_ALIGN_LSB 1
+#define AR_PCM_MAX_NUM_CHANNEL 8
+
+struct param_id_hw_ep_dma_data_align {
+ uint32_t dma_data_align;
+} __packed;
+
+#define PARAM_ID_VOL_CTRL_MASTER_GAIN 0x08001035
+#define VOL_CTRL_DEFAULT_GAIN 0x2000
+
+struct param_id_vol_ctrl_master_gain {
+ uint16_t master_gain;
+ uint16_t reserved;
+} __packed;
+
+
+#define PARAM_ID_REMOVE_INITIAL_SILENCE 0x0800114B
+#define PARAM_ID_REMOVE_TRAILING_SILENCE 0x0800115D
+
+#define PARAM_ID_REAL_MODULE_ID 0x0800100B
+
+struct param_id_placeholder_real_module_id {
+ uint32_t real_module_id;
+} __packed;
+
+
+#define PARAM_ID_SH_MEM_PULL_PUSH_MODE_CFG 0x0800100A
+
+/**
+ * struct param_id_sh_mem_pull_push_mode_cfg - Shared memory push/pull config
+ * @shared_circ_buf_addr_lsw: Lower 32 bits of the circular buffer address.
+ * @shared_circ_buf_addr_msw: Upper 32 bits of the circular buffer address.
+ * @shared_circ_buf_size: Circular buffer size in bytes.
+ * @circ_buf_mem_map_handle: Circular buffer memory map handle.
+ * @shared_pos_buf_addr_lsw: Lower 32 bits of the position buffer address.
+ * @shared_pos_buf_addr_msw: Upper 32 bits of the position buffer address.
+ * @pos_buf_mem_map_handle: Position buffer memory map handle.
+ */
+struct param_id_sh_mem_pull_push_mode_cfg {
+ uint32_t shared_circ_buf_addr_lsw;
+ uint32_t shared_circ_buf_addr_msw;
+ uint32_t shared_circ_buf_size;
+ uint32_t circ_buf_mem_map_handle;
+ uint32_t shared_pos_buf_addr_lsw;
+ uint32_t shared_pos_buf_addr_msw;
+ uint32_t pos_buf_mem_map_handle;
+} __packed;
+
+/**
+ * struct sh_mem_pull_push_mode_position_buffer - Shared position buffer
+ * @frame_counter: Synchronization counter.
+ * @index: Current read/write index in bytes.
+ * @timestamp_us_lsw: Lower 32 bits of the timestamp in microseconds.
+ * @timestamp_us_msw: Upper 32 bits of the timestamp in microseconds.
+ *
+ * The frame counter should be read before and after the other fields to
+ * ensure the DSP did not update them while they were being read.
+ */
+struct sh_mem_pull_push_mode_position_buffer {
+ uint32_t frame_counter;
+ uint32_t index;
+ uint32_t timestamp_us_lsw;
+ uint32_t timestamp_us_msw;
+} __packed;
+
+/* Graph */
+struct audioreach_connection {
+ /* Connections */
+ uint32_t src_mod_inst_id;
+ uint32_t src_mod_op_port_id;
+ uint32_t dst_mod_inst_id;
+ uint32_t dst_mod_ip_port_id;
+ struct list_head node;
+};
+
+struct audioreach_graph_info {
+ int id;
+ uint32_t mem_map_handle;
+ uint32_t pos_buf_mem_map_handle;
+ uint32_t num_sub_graphs;
+ struct list_head sg_list;
+ bool is_push_pull_mode;
+ /* DPCM connection from FE Graph to BE graph */
+ uint32_t src_mod_inst_id;
+ uint32_t src_mod_op_port_id;
+ uint32_t dst_mod_inst_id;
+ uint32_t dst_mod_ip_port_id;
+};
+
+struct audioreach_sub_graph {
+ uint32_t sub_graph_id;
+ uint32_t perf_mode;
+ uint32_t direction;
+ uint32_t scenario_id;
+ struct list_head node;
+
+ struct audioreach_graph_info *info;
+ uint32_t num_containers;
+ struct list_head container_list;
+};
+
+struct audioreach_container {
+ uint32_t container_id;
+ uint32_t capability_id;
+ uint32_t graph_pos;
+ uint32_t stack_size;
+ uint32_t proc_domain;
+ struct list_head node;
+
+ uint32_t num_modules;
+ struct list_head modules_list;
+ struct audioreach_sub_graph *sub_graph;
+};
+
+#define AR_MAX_MOD_LINKS 8
+
+struct audioreach_module {
+ uint32_t module_id;
+ uint32_t instance_id;
+
+ uint32_t max_ip_port;
+ uint32_t max_op_port;
+
+ uint32_t num_connections;
+ /* Connections */
+ uint32_t src_mod_inst_id;
+ uint32_t src_mod_op_port_id[AR_MAX_MOD_LINKS];
+ uint32_t dst_mod_inst_id[AR_MAX_MOD_LINKS];
+ uint32_t dst_mod_ip_port_id[AR_MAX_MOD_LINKS];
+
+ /* Format specifics */
+ uint32_t ch_fmt;
+ uint32_t rate;
+ uint32_t bit_depth;
+
+ /* I2S module */
+ uint32_t hw_interface_idx;
+ uint32_t sd_line_idx;
+ uint32_t ws_src;
+ uint32_t frame_size_factor;
+ uint32_t data_format;
+ uint32_t hw_interface_type;
+
+ /* Audio IF module (TDM/PCM/I2S) */
+ u32 slot_mask;
+ u32 active_lane_mask;
+ u32 frame_sync_rate;
+ u16 qaif_type;
+ u16 sync_src;
+ u16 ctrl_data_out_enable;
+ u16 nslots_per_frame;
+ u16 slot_width;
+ u16 intf_mode;
+ u16 sync_mode;
+ u16 ctrl_invert_sync_pulse;
+ u16 ctrl_sync_data_delay;
+ u16 bit_clk_type;
+ u8 inv_int_bit_clk;
+ u8 inv_ext_bit_clk;
+
+ /* PCM module specific */
+ uint32_t interleave_type;
+
+ /* GAIN/Vol Control Module */
+ uint16_t gain;
+
+ /* Logging */
+ uint32_t log_code;
+ uint32_t log_tap_point_id;
+ uint32_t log_mode;
+
+ /* bookkeeping */
+ struct list_head node;
+ struct audioreach_container *container;
+ struct snd_soc_dapm_widget *widget;
+ struct audioreach_module_priv_data *data;
+};
+
+struct audioreach_module_config {
+ int direction;
+ u32 sample_rate;
+ u16 bit_width;
+ u16 bits_per_sample;
+
+ u16 data_format;
+ u16 num_channels;
+ u16 dp_idx;
+ u32 channel_allocation;
+ u32 sd_line_mask;
+ int fmt;
+ u32 slot_mask;
+ u16 nslots_per_frame;
+ u16 slot_width;
+ struct snd_codec codec;
+ u8 channel_map[AR_PCM_MAX_NUM_CHANNEL];
+};
+
+/* Packet Allocation routines */
+void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, uint32_t
+ token);
+void audioreach_set_default_channel_mapping(u8 *ch_map, int num_channels);
+void *audioreach_alloc_cmd_pkt(int payload_size, uint32_t opcode,
+ uint32_t token, uint32_t src_port,
+ uint32_t dest_port);
+void *audioreach_alloc_apm_pkt(int pkt_size, uint32_t opcode, uint32_t token,
+ uint32_t src_port);
+void *audioreach_alloc_pkt(int payload_size, uint32_t opcode,
+ uint32_t token, uint32_t src_port,
+ uint32_t dest_port);
+void *audioreach_alloc_graph_pkt(struct q6apm *apm,
+ const struct audioreach_graph_info *info);
+/* Topology specific */
+int audioreach_tplg_init(struct snd_soc_component *component);
+
+/* Module specific */
+void audioreach_graph_free_buf(struct q6apm_graph *graph);
+int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result,
+ struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait,
+ const struct gpr_pkt *pkt, uint32_t rsp_opcode);
+int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt,
+ uint32_t rsp_opcode);
+int audioreach_set_media_format(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const struct audioreach_module_config *cfg);
+int audioreach_shared_memory_send_eos(struct q6apm_graph *graph);
+int audioreach_gain_set_vol_ctrl(struct q6apm *apm,
+ const struct audioreach_module *module, int vol);
+int audioreach_send_u32_param(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ uint32_t param_id, uint32_t param_val);
+int audioreach_compr_set_param(struct q6apm_graph *graph,
+ const struct audioreach_module_config *mcfg);
+int audioreach_setup_push_pull(struct q6apm_graph *graph, phys_addr_t bphys,
+ phys_addr_t pphys, uint32_t mem_map_handle,
+ uint32_t pos_buf_mem_map_handle, uint32_t size);
+int audioreach_map_memory_position_buffer(struct q6apm_graph *graph, unsigned int dir);
+
+int audioreach_shmem_register_event(struct q6apm_graph *graph, int bytes, int num_levels);
+#endif /* __AUDIOREACH_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6adm.c b/sound/soc/qcom/qdsp6/q6adm.c
new file mode 100644
index 000000000..db0ae71f2
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6adm.c
@@ -0,0 +1,609 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include <linux/device.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/apr.h>
+#include <linux/wait.h>
+#include <sound/asound.h>
+#include "q6adm.h"
+#include "q6afe.h"
+#include "q6core.h"
+#include "q6dsp-common.h"
+#include "q6dsp-errno.h"
+
+#define ADM_CMD_DEVICE_OPEN_V5 0x00010326
+#define ADM_CMDRSP_DEVICE_OPEN_V5 0x00010329
+#define ADM_CMD_DEVICE_CLOSE_V5 0x00010327
+#define ADM_CMD_MATRIX_MAP_ROUTINGS_V5 0x00010325
+
+#define TIMEOUT_MS 1000
+#define RESET_COPP_ID 99
+#define INVALID_COPP_ID 0xFF
+/* Definition for a legacy device session. */
+#define ADM_LEGACY_DEVICE_SESSION 0
+#define ADM_MATRIX_ID_AUDIO_RX 0
+#define ADM_MATRIX_ID_AUDIO_TX 1
+
+struct q6copp {
+ int afe_port;
+ int copp_idx;
+ int id;
+ int topology;
+ int mode;
+ int rate;
+ int bit_width;
+ int channels;
+ int app_type;
+ int acdb_id;
+
+ struct aprv2_ibasic_rsp_result_t result;
+ struct kref refcount;
+ wait_queue_head_t wait;
+ struct list_head node;
+ struct q6adm *adm;
+};
+
+struct q6adm {
+ struct apr_device *apr;
+ struct device *dev;
+ struct q6core_svc_api_info ainfo;
+ unsigned long copp_bitmap[AFE_MAX_PORTS];
+ struct list_head copps_list;
+ spinlock_t copps_list_lock;
+ struct aprv2_ibasic_rsp_result_t result;
+ struct mutex lock;
+ wait_queue_head_t matrix_map_wait;
+};
+
+struct q6adm_cmd_device_open_v5 {
+ u16 flags;
+ u16 mode_of_operation;
+ u16 endpoint_id_1;
+ u16 endpoint_id_2;
+ u32 topology_id;
+ u16 dev_num_channel;
+ u16 bit_width;
+ u32 sample_rate;
+ u8 dev_channel_mapping[8];
+} __packed;
+
+struct q6adm_cmd_matrix_map_routings_v5 {
+ u32 matrix_id;
+ u32 num_sessions;
+} __packed;
+
+struct q6adm_session_map_node_v5 {
+ u16 session_id;
+ u16 num_copps;
+} __packed;
+
+static struct q6copp *q6adm_find_copp(struct q6adm *adm, int port_idx,
+ int copp_idx)
+{
+ struct q6copp *c;
+ struct q6copp *ret = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&adm->copps_list_lock, flags);
+ list_for_each_entry(c, &adm->copps_list, node) {
+ if ((port_idx == c->afe_port) && (copp_idx == c->copp_idx)) {
+ ret = c;
+ kref_get(&c->refcount);
+ break;
+ }
+ }
+
+ spin_unlock_irqrestore(&adm->copps_list_lock, flags);
+
+ return ret;
+
+}
+
+static int q6adm_apr_send_copp_pkt(struct q6adm *adm, struct q6copp *copp,
+ struct apr_pkt *pkt, uint32_t rsp_opcode)
+{
+ struct device *dev = adm->dev;
+ uint32_t opcode = pkt->hdr.opcode;
+ int ret;
+
+ mutex_lock(&adm->lock);
+ copp->result.opcode = 0;
+ copp->result.status = 0;
+ ret = apr_send_pkt(adm->apr, pkt);
+ if (ret < 0) {
+ dev_err(dev, "Failed to send APR packet\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* Wait for the callback with copp id */
+ if (rsp_opcode)
+ ret = wait_event_timeout(copp->wait,
+ (copp->result.opcode == opcode) ||
+ (copp->result.opcode == rsp_opcode),
+ msecs_to_jiffies(TIMEOUT_MS));
+ else
+ ret = wait_event_timeout(copp->wait,
+ (copp->result.opcode == opcode),
+ msecs_to_jiffies(TIMEOUT_MS));
+
+ if (!ret) {
+ dev_err(dev, "ADM copp cmd timedout\n");
+ ret = -ETIMEDOUT;
+ } else if (copp->result.status > 0) {
+ dev_err(dev, "DSP returned error[%d]\n",
+ copp->result.status);
+ ret = -EINVAL;
+ }
+
+err:
+ mutex_unlock(&adm->lock);
+ return ret;
+}
+
+static int q6adm_device_close(struct q6adm *adm, struct q6copp *copp,
+ int port_id, int copp_idx)
+{
+ struct apr_pkt close;
+
+ close.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ close.hdr.pkt_size = sizeof(close);
+ close.hdr.src_port = port_id;
+ close.hdr.dest_port = copp->id;
+ close.hdr.token = port_id << 16 | copp_idx;
+ close.hdr.opcode = ADM_CMD_DEVICE_CLOSE_V5;
+
+ return q6adm_apr_send_copp_pkt(adm, copp, &close, 0);
+}
+
+static void q6adm_free_copp(struct kref *ref)
+{
+ struct q6copp *c = container_of(ref, struct q6copp, refcount);
+ struct q6adm *adm = c->adm;
+ unsigned long flags;
+ int ret;
+
+ ret = q6adm_device_close(adm, c, c->afe_port, c->copp_idx);
+ if (ret < 0)
+ dev_err(adm->dev, "Failed to close copp %d\n", ret);
+
+ spin_lock_irqsave(&adm->copps_list_lock, flags);
+ clear_bit(c->copp_idx, &adm->copp_bitmap[c->afe_port]);
+ list_del(&c->node);
+ spin_unlock_irqrestore(&adm->copps_list_lock, flags);
+ kfree(c);
+}
+
+static int q6adm_callback(struct apr_device *adev, const struct apr_resp_pkt *data)
+{
+ const struct aprv2_ibasic_rsp_result_t *result = data->payload;
+ int port_idx, copp_idx;
+ const struct apr_hdr *hdr = &data->hdr;
+ struct q6copp *copp;
+ struct q6adm *adm = dev_get_drvdata(&adev->dev);
+
+ if (!data->payload_size)
+ return 0;
+
+ copp_idx = (hdr->token) & 0XFF;
+ port_idx = ((hdr->token) >> 16) & 0xFF;
+ if (port_idx < 0 || port_idx >= AFE_MAX_PORTS) {
+ dev_err(&adev->dev, "Invalid port idx %d token %d\n",
+ port_idx, hdr->token);
+ return 0;
+ }
+ if (copp_idx < 0 || copp_idx >= MAX_COPPS_PER_PORT) {
+ dev_err(&adev->dev, "Invalid copp idx %d token %d\n",
+ copp_idx, hdr->token);
+ return 0;
+ }
+
+ switch (hdr->opcode) {
+ case APR_BASIC_RSP_RESULT: {
+ if (result->status != 0) {
+ dev_err(&adev->dev, "cmd = 0x%x return error = 0x%x\n",
+ result->opcode, result->status);
+ }
+ switch (result->opcode) {
+ case ADM_CMD_DEVICE_OPEN_V5:
+ case ADM_CMD_DEVICE_CLOSE_V5:
+ list_for_each_entry(copp, &adm->copps_list, node) {
+ if ((port_idx == copp->afe_port) && (copp_idx == copp->copp_idx)) {
+ copp->result = *result;
+ wake_up(&copp->wait);
+ break;
+ }
+ }
+ break;
+ case ADM_CMD_MATRIX_MAP_ROUTINGS_V5:
+ adm->result = *result;
+ wake_up(&adm->matrix_map_wait);
+ break;
+
+ default:
+ dev_err(&adev->dev, "Unknown Cmd: 0x%x\n",
+ result->opcode);
+ break;
+ }
+ return 0;
+ }
+ case ADM_CMDRSP_DEVICE_OPEN_V5: {
+ struct adm_cmd_rsp_device_open_v5 {
+ u32 status;
+ u16 copp_id;
+ u16 reserved;
+ } __packed *open = data->payload;
+
+ copp = q6adm_find_copp(adm, port_idx, copp_idx);
+ if (!copp)
+ return 0;
+
+ if (open->copp_id == INVALID_COPP_ID) {
+ dev_err(&adev->dev, "Invalid coppid rxed %d\n",
+ open->copp_id);
+ copp->result.status = ADSP_EBADPARAM;
+ wake_up(&copp->wait);
+ kref_put(&copp->refcount, q6adm_free_copp);
+ break;
+ }
+ copp->result.opcode = hdr->opcode;
+ copp->id = open->copp_id;
+ wake_up(&copp->wait);
+ kref_put(&copp->refcount, q6adm_free_copp);
+ }
+ break;
+ default:
+ dev_err(&adev->dev, "Unknown cmd:0x%x\n",
+ hdr->opcode);
+ break;
+ }
+
+ return 0;
+}
+
+static struct q6copp *q6adm_alloc_copp(struct q6adm *adm, int port_idx)
+{
+ struct q6copp *c;
+ int idx;
+
+ idx = find_first_zero_bit(&adm->copp_bitmap[port_idx],
+ MAX_COPPS_PER_PORT);
+
+ if (idx >= MAX_COPPS_PER_PORT)
+ return ERR_PTR(-EBUSY);
+
+ c = kzalloc_obj(*c, GFP_ATOMIC);
+ if (!c)
+ return ERR_PTR(-ENOMEM);
+
+ set_bit(idx, &adm->copp_bitmap[port_idx]);
+ c->copp_idx = idx;
+ c->afe_port = port_idx;
+ c->adm = adm;
+
+ init_waitqueue_head(&c->wait);
+
+ return c;
+}
+
+static struct q6copp *q6adm_find_matching_copp(struct q6adm *adm,
+ int port_id, int topology,
+ int mode, int rate,
+ int channel_mode, int bit_width,
+ int app_type)
+{
+ struct q6copp *c;
+ struct q6copp *ret = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&adm->copps_list_lock, flags);
+
+ list_for_each_entry(c, &adm->copps_list, node) {
+ if ((port_id == c->afe_port) && (topology == c->topology) &&
+ (mode == c->mode) && (rate == c->rate) &&
+ (bit_width == c->bit_width) && (app_type == c->app_type)) {
+ ret = c;
+ kref_get(&c->refcount);
+ }
+ }
+ spin_unlock_irqrestore(&adm->copps_list_lock, flags);
+
+ return ret;
+}
+
+static int q6adm_device_open(struct q6adm *adm, struct q6copp *copp,
+ int port_id, int path, int topology,
+ int channel_mode, int bit_width, int rate)
+{
+ struct q6adm_cmd_device_open_v5 *open;
+ int afe_port = q6afe_get_port_id(port_id);
+ struct apr_pkt *pkt;
+ int ret, pkt_size = APR_HDR_SIZE + sizeof(*open);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ open = p + APR_HDR_SIZE;
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.src_port = afe_port;
+ pkt->hdr.dest_port = afe_port;
+ pkt->hdr.token = port_id << 16 | copp->copp_idx;
+ pkt->hdr.opcode = ADM_CMD_DEVICE_OPEN_V5;
+ open->flags = ADM_LEGACY_DEVICE_SESSION;
+ open->mode_of_operation = path;
+ open->endpoint_id_1 = afe_port;
+ open->topology_id = topology;
+ open->dev_num_channel = channel_mode & 0x00FF;
+ open->bit_width = bit_width;
+ open->sample_rate = rate;
+
+ ret = q6dsp_map_channels(&open->dev_channel_mapping[0],
+ channel_mode);
+ if (ret)
+ return ret;
+
+ return q6adm_apr_send_copp_pkt(adm, copp, pkt, ADM_CMDRSP_DEVICE_OPEN_V5);
+}
+
+/**
+ * q6adm_open() - open adm and grab a free copp
+ *
+ * @dev: Pointer to adm child device.
+ * @port_id: port id
+ * @path: playback or capture path.
+ * @rate: rate at which copp is required.
+ * @channel_mode: channel mode
+ * @topology: adm topology id
+ * @perf_mode: performace mode.
+ * @bit_width: audio sample bit width
+ * @app_type: Application type.
+ * @acdb_id: ACDB id
+ *
+ * Return: Will be an negative on error or a valid copp pointer on success.
+ */
+struct q6copp *q6adm_open(struct device *dev, int port_id, int path, int rate,
+ int channel_mode, int topology, int perf_mode,
+ uint16_t bit_width, int app_type, int acdb_id)
+{
+ struct q6adm *adm = dev_get_drvdata(dev->parent);
+ struct q6copp *copp;
+ unsigned long flags;
+ int ret = 0;
+
+ if (port_id < 0) {
+ dev_err(dev, "Invalid port_id %d\n", port_id);
+ return ERR_PTR(-EINVAL);
+ }
+
+ copp = q6adm_find_matching_copp(adm, port_id, topology, perf_mode,
+ rate, channel_mode, bit_width, app_type);
+ if (copp) {
+ dev_err(dev, "Found Matching Copp 0x%x\n", copp->copp_idx);
+ return copp;
+ }
+
+ spin_lock_irqsave(&adm->copps_list_lock, flags);
+ copp = q6adm_alloc_copp(adm, port_id);
+ if (IS_ERR(copp)) {
+ spin_unlock_irqrestore(&adm->copps_list_lock, flags);
+ return ERR_CAST(copp);
+ }
+
+ list_add_tail(&copp->node, &adm->copps_list);
+ spin_unlock_irqrestore(&adm->copps_list_lock, flags);
+
+ kref_init(&copp->refcount);
+ copp->topology = topology;
+ copp->mode = perf_mode;
+ copp->rate = rate;
+ copp->channels = channel_mode;
+ copp->bit_width = bit_width;
+ copp->app_type = app_type;
+
+ ret = q6adm_device_open(adm, copp, port_id, path, topology,
+ channel_mode, bit_width, rate);
+ if (ret < 0) {
+ kref_put(&copp->refcount, q6adm_free_copp);
+ return ERR_PTR(ret);
+ }
+
+ return copp;
+}
+EXPORT_SYMBOL_GPL(q6adm_open);
+
+/**
+ * q6adm_get_copp_id() - get copp index
+ *
+ * @copp: Pointer to valid copp
+ *
+ * Return: Will be an negative on error or a valid copp index on success.
+ **/
+int q6adm_get_copp_id(struct q6copp *copp)
+{
+ if (!copp)
+ return -EINVAL;
+
+ return copp->copp_idx;
+}
+EXPORT_SYMBOL_GPL(q6adm_get_copp_id);
+
+/**
+ * q6adm_matrix_map() - Map asm streams and afe ports using payload
+ *
+ * @dev: Pointer to adm child device.
+ * @path: playback or capture path.
+ * @payload_map: map between session id and afe ports.
+ * @perf_mode: Performace mode.
+ *
+ * Return: Will be an negative on error or a zero on success.
+ */
+int q6adm_matrix_map(struct device *dev, int path,
+ struct route_payload payload_map, int perf_mode)
+{
+ struct q6adm *adm = dev_get_drvdata(dev->parent);
+ struct q6adm_cmd_matrix_map_routings_v5 *route;
+ struct q6adm_session_map_node_v5 *node;
+ struct apr_pkt *pkt;
+ uint16_t *copps_list;
+ int ret, i, copp_idx;
+ /* Assumes port_ids have already been validated during adm_open */
+ struct q6copp *copp;
+ int pkt_size = (APR_HDR_SIZE + sizeof(*route) + sizeof(*node) +
+ (sizeof(uint32_t) * payload_map.num_copps));
+
+ void *matrix_map __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!matrix_map)
+ return -ENOMEM;
+
+ pkt = matrix_map;
+ route = matrix_map + APR_HDR_SIZE;
+ node = matrix_map + APR_HDR_SIZE + sizeof(*route);
+ copps_list = matrix_map + APR_HDR_SIZE + sizeof(*route) + sizeof(*node);
+
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.token = 0;
+ pkt->hdr.opcode = ADM_CMD_MATRIX_MAP_ROUTINGS_V5;
+ route->num_sessions = 1;
+
+ switch (path) {
+ case ADM_PATH_PLAYBACK:
+ route->matrix_id = ADM_MATRIX_ID_AUDIO_RX;
+ break;
+ case ADM_PATH_LIVE_REC:
+ route->matrix_id = ADM_MATRIX_ID_AUDIO_TX;
+ break;
+ default:
+ dev_err(dev, "Wrong path set[%d]\n", path);
+ break;
+ }
+
+ node->session_id = payload_map.session_id;
+ node->num_copps = payload_map.num_copps;
+
+ for (i = 0; i < payload_map.num_copps; i++) {
+ int port_idx = payload_map.port_id[i];
+
+ if (port_idx < 0) {
+ dev_err(dev, "Invalid port_id %d\n",
+ payload_map.port_id[i]);
+ return -EINVAL;
+ }
+ copp_idx = payload_map.copp_idx[i];
+
+ copp = q6adm_find_copp(adm, port_idx, copp_idx);
+ if (!copp)
+ return -EINVAL;
+
+ copps_list[i] = copp->id;
+ kref_put(&copp->refcount, q6adm_free_copp);
+ }
+
+ mutex_lock(&adm->lock);
+ adm->result.status = 0;
+ adm->result.opcode = 0;
+
+ ret = apr_send_pkt(adm->apr, pkt);
+ if (ret < 0) {
+ dev_err(dev, "routing for stream %d failed ret %d\n",
+ payload_map.session_id, ret);
+ goto fail_cmd;
+ }
+ ret = wait_event_timeout(adm->matrix_map_wait,
+ adm->result.opcode == pkt->hdr.opcode,
+ msecs_to_jiffies(TIMEOUT_MS));
+ if (!ret) {
+ dev_err(dev, "routing for stream %d failed\n",
+ payload_map.session_id);
+ ret = -ETIMEDOUT;
+ goto fail_cmd;
+ } else if (adm->result.status > 0) {
+ dev_err(dev, "DSP returned error[%d]\n",
+ adm->result.status);
+ ret = -EINVAL;
+ goto fail_cmd;
+ }
+
+fail_cmd:
+ mutex_unlock(&adm->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(q6adm_matrix_map);
+
+/**
+ * q6adm_close() - Close adm copp
+ *
+ * @dev: Pointer to adm child device.
+ * @copp: pointer to previously opened copp
+ *
+ * Return: Will be an negative on error or a zero on success.
+ */
+int q6adm_close(struct device *dev, struct q6copp *copp)
+{
+ kref_put(&copp->refcount, q6adm_free_copp);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(q6adm_close);
+
+static int q6adm_probe(struct apr_device *adev)
+{
+ struct device *dev = &adev->dev;
+ struct q6adm *adm;
+
+ adm = devm_kzalloc(dev, sizeof(*adm), GFP_KERNEL);
+ if (!adm)
+ return -ENOMEM;
+
+ adm->apr = adev;
+ dev_set_drvdata(dev, adm);
+ adm->dev = dev;
+ q6core_get_svc_api_info(adev->svc_id, &adm->ainfo);
+ mutex_init(&adm->lock);
+ init_waitqueue_head(&adm->matrix_map_wait);
+
+ INIT_LIST_HEAD(&adm->copps_list);
+ spin_lock_init(&adm->copps_list_lock);
+
+ return devm_of_platform_populate(dev);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id q6adm_device_id[] = {
+ { .compatible = "qcom,q6adm" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6adm_device_id);
+#endif
+
+static struct apr_driver qcom_q6adm_driver = {
+ .probe = q6adm_probe,
+ .callback = q6adm_callback,
+ .driver = {
+ .name = "qcom-q6adm",
+ .of_match_table = of_match_ptr(q6adm_device_id),
+ },
+};
+
+module_apr_driver(qcom_q6adm_driver);
+MODULE_DESCRIPTION("Q6 Audio Device Manager");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/qdsp6/q6adm.h b/sound/soc/qcom/qdsp6/q6adm.h
new file mode 100644
index 000000000..4f56999b7
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6adm.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __Q6_ADM_V2_H__
+#define __Q6_ADM_V2_H__
+
+#define ADM_PATH_PLAYBACK 0x1
+#define ADM_PATH_LIVE_REC 0x2
+#define MAX_COPPS_PER_PORT 8
+#define NULL_COPP_TOPOLOGY 0x00010312
+
+/* multiple copp per stream. */
+struct route_payload {
+ int num_copps;
+ int session_id;
+ int copp_idx[MAX_COPPS_PER_PORT];
+ int port_id[MAX_COPPS_PER_PORT];
+};
+
+struct q6copp;
+struct q6copp *q6adm_open(struct device *dev, int port_id, int path, int rate,
+ int channel_mode, int topology, int perf_mode,
+ uint16_t bit_width, int app_type, int acdb_id);
+int q6adm_close(struct device *dev, struct q6copp *copp);
+int q6adm_get_copp_id(struct q6copp *copp);
+int q6adm_matrix_map(struct device *dev, int path,
+ struct route_payload payload_map, int perf_mode);
+
+#endif /* __Q6_ADM_V2_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6afe-clocks.c b/sound/soc/qcom/qdsp6/q6afe-clocks.c
new file mode 100644
index 000000000..84b9018c3
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6afe-clocks.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020, Linaro Limited
+
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include "q6dsp-lpass-clocks.h"
+#include "q6afe.h"
+
+#define Q6AFE_CLK(id) { \
+ .clk_id = id, \
+ .q6dsp_clk_id = Q6AFE_##id, \
+ .name = #id, \
+ .rate = 19200000, \
+ }
+
+
+static const struct q6dsp_clk_init q6afe_clks[] = {
+ Q6AFE_CLK(LPASS_CLK_ID_PRI_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_PRI_MI2S_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_SEC_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_SEC_MI2S_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_TER_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_TER_MI2S_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUAD_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUAD_MI2S_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_SPEAKER_I2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_SPEAKER_I2S_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_SPEAKER_I2S_OSR),
+ Q6AFE_CLK(LPASS_CLK_ID_QUI_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUI_MI2S_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_SEN_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_SEN_MI2S_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_INT0_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_INT1_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_INT2_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_INT3_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_INT4_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_INT5_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_INT6_MI2S_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUI_MI2S_OSR),
+ Q6AFE_CLK(LPASS_CLK_ID_PRI_PCM_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_PRI_PCM_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_SEC_PCM_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_SEC_PCM_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_TER_PCM_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_TER_PCM_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUAD_PCM_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUAD_PCM_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUIN_PCM_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUIN_PCM_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUI_PCM_OSR),
+ Q6AFE_CLK(LPASS_CLK_ID_PRI_TDM_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_PRI_TDM_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_SEC_TDM_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_SEC_TDM_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_TER_TDM_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_TER_TDM_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUAD_TDM_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUAD_TDM_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUIN_TDM_IBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUIN_TDM_EBIT),
+ Q6AFE_CLK(LPASS_CLK_ID_QUIN_TDM_OSR),
+ Q6AFE_CLK(LPASS_CLK_ID_MCLK_1),
+ Q6AFE_CLK(LPASS_CLK_ID_MCLK_2),
+ Q6AFE_CLK(LPASS_CLK_ID_MCLK_3),
+ Q6AFE_CLK(LPASS_CLK_ID_MCLK_4),
+ Q6AFE_CLK(LPASS_CLK_ID_INTERNAL_DIGITAL_CODEC_CORE),
+ Q6AFE_CLK(LPASS_CLK_ID_INT_MCLK_0),
+ Q6AFE_CLK(LPASS_CLK_ID_INT_MCLK_1),
+ Q6AFE_CLK(LPASS_CLK_ID_WSA_CORE_MCLK),
+ Q6AFE_CLK(LPASS_CLK_ID_WSA_CORE_NPL_MCLK),
+ Q6AFE_CLK(LPASS_CLK_ID_VA_CORE_MCLK),
+ Q6AFE_CLK(LPASS_CLK_ID_TX_CORE_MCLK),
+ Q6AFE_CLK(LPASS_CLK_ID_TX_CORE_NPL_MCLK),
+ Q6AFE_CLK(LPASS_CLK_ID_RX_CORE_MCLK),
+ Q6AFE_CLK(LPASS_CLK_ID_RX_CORE_NPL_MCLK),
+ Q6AFE_CLK(LPASS_CLK_ID_VA_CORE_2X_MCLK),
+ Q6DSP_VOTE_CLK(LPASS_HW_AVTIMER_VOTE,
+ Q6AFE_LPASS_CORE_AVTIMER_BLOCK,
+ "LPASS_AVTIMER_MACRO"),
+ Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE,
+ Q6AFE_LPASS_CORE_HW_MACRO_BLOCK,
+ "LPASS_HW_MACRO"),
+ Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE,
+ Q6AFE_LPASS_CORE_HW_DCODEC_BLOCK,
+ "LPASS_HW_DCODEC"),
+};
+
+static const struct q6dsp_clk_desc q6dsp_clk_q6afe __maybe_unused = {
+ .clks = q6afe_clks,
+ .num_clks = ARRAY_SIZE(q6afe_clks),
+ .lpass_set_clk = q6afe_set_lpass_clock,
+ .lpass_vote_clk = q6afe_vote_lpass_core_hw,
+ .lpass_unvote_clk = q6afe_unvote_lpass_core_hw,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id q6afe_clock_device_id[] = {
+ { .compatible = "qcom,q6afe-clocks", .data = &q6dsp_clk_q6afe },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6afe_clock_device_id);
+#endif
+
+static struct platform_driver q6afe_clock_platform_driver = {
+ .driver = {
+ .name = "q6afe-clock",
+ .of_match_table = of_match_ptr(q6afe_clock_device_id),
+ },
+ .probe = q6dsp_clock_dev_probe,
+};
+module_platform_driver(q6afe_clock_platform_driver);
+
+MODULE_DESCRIPTION("Q6 Audio Frontend clock driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c
new file mode 100644
index 000000000..920345609
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6afe-dai.c
@@ -0,0 +1,1201 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include "q6dsp-lpass-ports.h"
+#include "q6dsp-common.h"
+#include "q6afe.h"
+
+
+struct q6afe_dai_priv_data {
+ uint32_t sd_line_mask;
+ uint32_t sync_mode;
+ uint32_t sync_src;
+ uint32_t data_out_enable;
+ uint32_t invert_sync;
+ uint32_t data_delay;
+ uint32_t data_align;
+};
+
+struct q6afe_dai_data {
+ struct q6afe_port *port[AFE_PORT_MAX];
+ struct q6afe_port_config port_config[AFE_PORT_MAX];
+ bool is_port_started[AFE_PORT_MAX];
+ struct q6afe_dai_priv_data priv[AFE_PORT_MAX];
+};
+
+static int q6slim_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_slim_cfg *slim = &dai_data->port_config[dai->id].slim;
+
+ slim->sample_rate = params_rate(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ case SNDRV_PCM_FORMAT_SPECIAL:
+ slim->bit_width = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ slim->bit_width = 24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ slim->bit_width = 32;
+ break;
+ default:
+ pr_err("%s: format %d\n",
+ __func__, params_format(params));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int q6hdmi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ int channels = params_channels(params);
+ struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi;
+ int ret;
+
+ hdmi->sample_rate = params_rate(params);
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ hdmi->bit_width = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ hdmi->bit_width = 24;
+ break;
+ }
+
+ ret = q6dsp_get_channel_allocation(channels);
+ if (ret < 0)
+ return ret;
+
+ hdmi->channel_allocation = (u16) ret;
+
+ return 0;
+}
+
+static int q6afe_usb_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ int channels = params_channels(params);
+ int rate = params_rate(params);
+ struct q6afe_usb_cfg *usb = &dai_data->port_config[dai->id].usb_audio;
+
+ usb->sample_rate = rate;
+ usb->num_channels = channels;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_U16_LE:
+ case SNDRV_PCM_FORMAT_S16_LE:
+ usb->bit_width = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_S24_3LE:
+ usb->bit_width = 24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ usb->bit_width = 32;
+ break;
+ default:
+ dev_err(dai->dev, "%s: invalid format %d\n",
+ __func__, params_format(params));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int q6i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_i2s_cfg *i2s = &dai_data->port_config[dai->id].i2s_cfg;
+
+ i2s->sample_rate = params_rate(params);
+ i2s->bit_width = params_width(params);
+ i2s->num_channels = params_channels(params);
+ i2s->sd_line_mask = dai_data->priv[dai->id].sd_line_mask;
+
+ return 0;
+}
+
+static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_i2s_cfg *i2s = &dai_data->port_config[dai->id].i2s_cfg;
+
+ i2s->fmt = fmt;
+
+ return 0;
+}
+
+static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask,
+ unsigned int rx_mask,
+ int slots, int slot_width)
+{
+
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_tdm_cfg *tdm = &dai_data->port_config[dai->id].tdm;
+ unsigned int cap_mask;
+ int rc = 0;
+
+ /* HW only supports 16 and 32 bit slot width configuration */
+ if ((slot_width != 16) && (slot_width != 32)) {
+ dev_err(dai->dev, "%s: invalid slot_width %d\n",
+ __func__, slot_width);
+ return -EINVAL;
+ }
+
+ /* HW supports 1-32 slots configuration. Typical: 1, 2, 4, 8, 16, 32 */
+ switch (slots) {
+ case 2:
+ cap_mask = 0x03;
+ break;
+ case 4:
+ cap_mask = 0x0F;
+ break;
+ case 8:
+ cap_mask = 0xFF;
+ break;
+ case 16:
+ cap_mask = 0xFFFF;
+ break;
+ default:
+ dev_err(dai->dev, "%s: invalid slots %d\n",
+ __func__, slots);
+ return -EINVAL;
+ }
+
+ switch (dai->id) {
+ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7:
+ tdm->nslots_per_frame = slots;
+ tdm->slot_width = slot_width;
+ /* TDM RX dais ids are even and tx are odd */
+ tdm->slot_mask = ((dai->id & 0x1) ? tx_mask : rx_mask) & cap_mask;
+ break;
+ default:
+ dev_err(dai->dev, "%s: invalid dai id 0x%x\n",
+ __func__, dai->id);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int q6tdm_set_channel_map(struct snd_soc_dai *dai,
+ unsigned int tx_num, const unsigned int *tx_slot,
+ unsigned int rx_num, const unsigned int *rx_slot)
+{
+
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_tdm_cfg *tdm = &dai_data->port_config[dai->id].tdm;
+ int rc = 0;
+ int i = 0;
+
+ switch (dai->id) {
+ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7:
+ if (dai->id & 0x1) {
+ if (!tx_slot) {
+ dev_err(dai->dev, "tx slot not found\n");
+ return -EINVAL;
+ }
+ if (tx_num > AFE_PORT_MAX_AUDIO_CHAN_CNT) {
+ dev_err(dai->dev, "invalid tx num %d\n",
+ tx_num);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < tx_num; i++)
+ tdm->ch_mapping[i] = tx_slot[i];
+
+ for (i = tx_num; i < AFE_PORT_MAX_AUDIO_CHAN_CNT; i++)
+ tdm->ch_mapping[i] = Q6AFE_CMAP_INVALID;
+
+ tdm->num_channels = tx_num;
+ } else {
+ /* rx */
+ if (!rx_slot) {
+ dev_err(dai->dev, "rx slot not found\n");
+ return -EINVAL;
+ }
+ if (rx_num > AFE_PORT_MAX_AUDIO_CHAN_CNT) {
+ dev_err(dai->dev, "invalid rx num %d\n",
+ rx_num);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < rx_num; i++)
+ tdm->ch_mapping[i] = rx_slot[i];
+
+ for (i = rx_num; i < AFE_PORT_MAX_AUDIO_CHAN_CNT; i++)
+ tdm->ch_mapping[i] = Q6AFE_CMAP_INVALID;
+
+ tdm->num_channels = rx_num;
+ }
+
+ break;
+ default:
+ dev_err(dai->dev, "%s: invalid dai id 0x%x\n",
+ __func__, dai->id);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int q6tdm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_tdm_cfg *tdm = &dai_data->port_config[dai->id].tdm;
+
+ tdm->bit_width = params_width(params);
+ tdm->sample_rate = params_rate(params);
+ tdm->num_channels = params_channels(params);
+ tdm->data_align_type = dai_data->priv[dai->id].data_align;
+ tdm->sync_src = dai_data->priv[dai->id].sync_src;
+ tdm->sync_mode = dai_data->priv[dai->id].sync_mode;
+
+ return 0;
+}
+
+static int q6dma_set_channel_map(struct snd_soc_dai *dai,
+ unsigned int tx_num,
+ const unsigned int *tx_ch_mask,
+ unsigned int rx_num,
+ const unsigned int *rx_ch_mask)
+{
+
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_cdc_dma_cfg *cfg = &dai_data->port_config[dai->id].dma_cfg;
+ int ch_mask;
+ int rc = 0;
+
+ switch (dai->id) {
+ case WSA_CODEC_DMA_TX_0:
+ case WSA_CODEC_DMA_TX_1:
+ case WSA_CODEC_DMA_TX_2:
+ case VA_CODEC_DMA_TX_0:
+ case VA_CODEC_DMA_TX_1:
+ case VA_CODEC_DMA_TX_2:
+ case TX_CODEC_DMA_TX_0:
+ case TX_CODEC_DMA_TX_1:
+ case TX_CODEC_DMA_TX_2:
+ case TX_CODEC_DMA_TX_3:
+ case TX_CODEC_DMA_TX_4:
+ case TX_CODEC_DMA_TX_5:
+ if (!tx_ch_mask) {
+ dev_err(dai->dev, "tx slot not found\n");
+ return -EINVAL;
+ }
+
+ if (tx_num > AFE_PORT_MAX_AUDIO_CHAN_CNT) {
+ dev_err(dai->dev, "invalid tx num %d\n",
+ tx_num);
+ return -EINVAL;
+ }
+ ch_mask = *tx_ch_mask;
+
+ break;
+ case WSA_CODEC_DMA_RX_0:
+ case WSA_CODEC_DMA_RX_1:
+ case RX_CODEC_DMA_RX_0:
+ case RX_CODEC_DMA_RX_1:
+ case RX_CODEC_DMA_RX_2:
+ case RX_CODEC_DMA_RX_3:
+ case RX_CODEC_DMA_RX_4:
+ case RX_CODEC_DMA_RX_5:
+ case RX_CODEC_DMA_RX_6:
+ case RX_CODEC_DMA_RX_7:
+ /* rx */
+ if (!rx_ch_mask) {
+ dev_err(dai->dev, "rx slot not found\n");
+ return -EINVAL;
+ }
+ if (rx_num > AFE_PORT_MAX_AUDIO_CHAN_CNT) {
+ dev_err(dai->dev, "invalid rx num %d\n",
+ rx_num);
+ return -EINVAL;
+ }
+ ch_mask = *rx_ch_mask;
+
+ break;
+ default:
+ dev_err(dai->dev, "%s: invalid dai id 0x%x\n",
+ __func__, dai->id);
+ return -EINVAL;
+ }
+
+ cfg->active_channels_mask = ch_mask;
+
+ return rc;
+}
+
+static int q6dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_cdc_dma_cfg *cfg = &dai_data->port_config[dai->id].dma_cfg;
+
+ cfg->bit_width = params_width(params);
+ cfg->sample_rate = params_rate(params);
+ cfg->num_channels = params_channels(params);
+
+ return 0;
+}
+static void q6afe_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ int rc;
+
+ if (!dai_data->is_port_started[dai->id])
+ return;
+
+ rc = q6afe_port_stop(dai_data->port[dai->id]);
+ if (rc < 0)
+ dev_err(dai->dev, "fail to close AFE port (%d)\n", rc);
+
+ dai_data->is_port_started[dai->id] = false;
+
+}
+
+static int q6afe_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ int rc;
+
+ if (dai_data->is_port_started[dai->id]) {
+ /* stop the port and restart with new port config */
+ rc = q6afe_port_stop(dai_data->port[dai->id]);
+ if (rc < 0) {
+ dev_err(dai->dev, "fail to close AFE port (%d)\n", rc);
+ return rc;
+ }
+ }
+
+ switch (dai->id) {
+ case HDMI_RX:
+ case DISPLAY_PORT_RX:
+ q6afe_hdmi_port_prepare(dai_data->port[dai->id],
+ &dai_data->port_config[dai->id].hdmi);
+ break;
+ case SLIMBUS_0_RX ... SLIMBUS_6_TX:
+ q6afe_slim_port_prepare(dai_data->port[dai->id],
+ &dai_data->port_config[dai->id].slim);
+ break;
+ case SENARY_MI2S_RX ... SENARY_MI2S_TX:
+ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX:
+ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX:
+ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4:
+ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6:
+ rc = q6afe_i2s_port_prepare(dai_data->port[dai->id],
+ &dai_data->port_config[dai->id].i2s_cfg);
+ if (rc < 0) {
+ dev_err(dai->dev, "fail to prepare AFE port %x\n",
+ dai->id);
+ return rc;
+ }
+ break;
+ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7:
+ q6afe_tdm_port_prepare(dai_data->port[dai->id],
+ &dai_data->port_config[dai->id].tdm);
+ break;
+ case WSA_CODEC_DMA_RX_0 ... RX_CODEC_DMA_RX_7:
+ q6afe_cdc_dma_port_prepare(dai_data->port[dai->id],
+ &dai_data->port_config[dai->id].dma_cfg);
+ break;
+ case USB_RX:
+ q6afe_usb_port_prepare(dai_data->port[dai->id],
+ &dai_data->port_config[dai->id].usb_audio);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ rc = q6afe_port_start(dai_data->port[dai->id]);
+ if (rc < 0) {
+ dev_err(dai->dev, "fail to start AFE port %x\n", dai->id);
+ return rc;
+ }
+ dai_data->is_port_started[dai->id] = true;
+
+ return 0;
+}
+
+static int q6slim_set_channel_map(struct snd_soc_dai *dai,
+ unsigned int tx_num,
+ const unsigned int *tx_slot,
+ unsigned int rx_num,
+ const unsigned int *rx_slot)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_port_config *pcfg = &dai_data->port_config[dai->id];
+ int i;
+
+ if (dai->id & 0x1) {
+ /* TX */
+ if (!tx_slot) {
+ pr_err("%s: tx slot not found\n", __func__);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < tx_num; i++)
+ pcfg->slim.ch_mapping[i] = tx_slot[i];
+
+ pcfg->slim.num_channels = tx_num;
+
+
+ } else {
+ if (!rx_slot) {
+ pr_err("%s: rx slot not found\n", __func__);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < rx_num; i++)
+ pcfg->slim.ch_mapping[i] = rx_slot[i];
+
+ pcfg->slim.num_channels = rx_num;
+
+ }
+
+ return 0;
+}
+
+static int q6afe_mi2s_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_port *port = dai_data->port[dai->id];
+
+ switch (clk_id) {
+ case LPAIF_DIG_CLK:
+ return q6afe_port_set_sysclk(port, clk_id, 0, 5, freq, dir);
+ case LPAIF_BIT_CLK:
+ case LPAIF_OSR_CLK:
+ return q6afe_port_set_sysclk(port, clk_id,
+ Q6AFE_LPASS_CLK_SRC_INTERNAL,
+ Q6AFE_LPASS_CLK_ROOT_DEFAULT,
+ freq, dir);
+ case Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT ... Q6AFE_LPASS_CLK_ID_QUI_MI2S_OSR:
+ case Q6AFE_LPASS_CLK_ID_MCLK_1 ... Q6AFE_LPASS_CLK_ID_INT_MCLK_1:
+ case Q6AFE_LPASS_CLK_ID_WSA_CORE_MCLK ... Q6AFE_LPASS_CLK_ID_VA_CORE_2X_MCLK:
+ return q6afe_port_set_sysclk(port, clk_id,
+ Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_NO,
+ Q6AFE_LPASS_CLK_ROOT_DEFAULT,
+ freq, dir);
+ case Q6AFE_LPASS_CLK_ID_PRI_TDM_IBIT ... Q6AFE_LPASS_CLK_ID_QUIN_TDM_EBIT:
+ return q6afe_port_set_sysclk(port, clk_id,
+ Q6AFE_LPASS_CLK_ATTRIBUTE_INVERT_COUPLE_NO,
+ Q6AFE_LPASS_CLK_ROOT_DEFAULT,
+ freq, dir);
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_route q6afe_dapm_routes[] = {
+ {"HDMI Playback", NULL, "HDMI_RX"},
+ {"DISPLAY_PORT_RX_0 Playback", NULL, "DISPLAY_PORT_RX"},
+ {"Slimbus Playback", NULL, "SLIMBUS_0_RX"},
+ {"Slimbus1 Playback", NULL, "SLIMBUS_1_RX"},
+ {"Slimbus2 Playback", NULL, "SLIMBUS_2_RX"},
+ {"Slimbus3 Playback", NULL, "SLIMBUS_3_RX"},
+ {"Slimbus4 Playback", NULL, "SLIMBUS_4_RX"},
+ {"Slimbus5 Playback", NULL, "SLIMBUS_5_RX"},
+ {"Slimbus6 Playback", NULL, "SLIMBUS_6_RX"},
+
+ {"SLIMBUS_0_TX", NULL, "Slimbus Capture"},
+ {"SLIMBUS_1_TX", NULL, "Slimbus1 Capture"},
+ {"SLIMBUS_2_TX", NULL, "Slimbus2 Capture"},
+ {"SLIMBUS_3_TX", NULL, "Slimbus3 Capture"},
+ {"SLIMBUS_4_TX", NULL, "Slimbus4 Capture"},
+ {"SLIMBUS_5_TX", NULL, "Slimbus5 Capture"},
+ {"SLIMBUS_6_TX", NULL, "Slimbus6 Capture"},
+
+ {"Primary MI2S Playback", NULL, "PRI_MI2S_RX"},
+ {"Secondary MI2S Playback", NULL, "SEC_MI2S_RX"},
+ {"Tertiary MI2S Playback", NULL, "TERT_MI2S_RX"},
+ {"Quaternary MI2S Playback", NULL, "QUAT_MI2S_RX"},
+ {"Quinary MI2S Playback", NULL, "QUIN_MI2S_RX"},
+ {"Senary MI2S Playback", NULL, "SEN_MI2S_RX"},
+
+ {"Primary TDM0 Playback", NULL, "PRIMARY_TDM_RX_0"},
+ {"Primary TDM1 Playback", NULL, "PRIMARY_TDM_RX_1"},
+ {"Primary TDM2 Playback", NULL, "PRIMARY_TDM_RX_2"},
+ {"Primary TDM3 Playback", NULL, "PRIMARY_TDM_RX_3"},
+ {"Primary TDM4 Playback", NULL, "PRIMARY_TDM_RX_4"},
+ {"Primary TDM5 Playback", NULL, "PRIMARY_TDM_RX_5"},
+ {"Primary TDM6 Playback", NULL, "PRIMARY_TDM_RX_6"},
+ {"Primary TDM7 Playback", NULL, "PRIMARY_TDM_RX_7"},
+
+ {"Secondary TDM0 Playback", NULL, "SEC_TDM_RX_0"},
+ {"Secondary TDM1 Playback", NULL, "SEC_TDM_RX_1"},
+ {"Secondary TDM2 Playback", NULL, "SEC_TDM_RX_2"},
+ {"Secondary TDM3 Playback", NULL, "SEC_TDM_RX_3"},
+ {"Secondary TDM4 Playback", NULL, "SEC_TDM_RX_4"},
+ {"Secondary TDM5 Playback", NULL, "SEC_TDM_RX_5"},
+ {"Secondary TDM6 Playback", NULL, "SEC_TDM_RX_6"},
+ {"Secondary TDM7 Playback", NULL, "SEC_TDM_RX_7"},
+
+ {"Tertiary TDM0 Playback", NULL, "TERT_TDM_RX_0"},
+ {"Tertiary TDM1 Playback", NULL, "TERT_TDM_RX_1"},
+ {"Tertiary TDM2 Playback", NULL, "TERT_TDM_RX_2"},
+ {"Tertiary TDM3 Playback", NULL, "TERT_TDM_RX_3"},
+ {"Tertiary TDM4 Playback", NULL, "TERT_TDM_RX_4"},
+ {"Tertiary TDM5 Playback", NULL, "TERT_TDM_RX_5"},
+ {"Tertiary TDM6 Playback", NULL, "TERT_TDM_RX_6"},
+ {"Tertiary TDM7 Playback", NULL, "TERT_TDM_RX_7"},
+
+ {"Quaternary TDM0 Playback", NULL, "QUAT_TDM_RX_0"},
+ {"Quaternary TDM1 Playback", NULL, "QUAT_TDM_RX_1"},
+ {"Quaternary TDM2 Playback", NULL, "QUAT_TDM_RX_2"},
+ {"Quaternary TDM3 Playback", NULL, "QUAT_TDM_RX_3"},
+ {"Quaternary TDM4 Playback", NULL, "QUAT_TDM_RX_4"},
+ {"Quaternary TDM5 Playback", NULL, "QUAT_TDM_RX_5"},
+ {"Quaternary TDM6 Playback", NULL, "QUAT_TDM_RX_6"},
+ {"Quaternary TDM7 Playback", NULL, "QUAT_TDM_RX_7"},
+
+ {"Quinary TDM0 Playback", NULL, "QUIN_TDM_RX_0"},
+ {"Quinary TDM1 Playback", NULL, "QUIN_TDM_RX_1"},
+ {"Quinary TDM2 Playback", NULL, "QUIN_TDM_RX_2"},
+ {"Quinary TDM3 Playback", NULL, "QUIN_TDM_RX_3"},
+ {"Quinary TDM4 Playback", NULL, "QUIN_TDM_RX_4"},
+ {"Quinary TDM5 Playback", NULL, "QUIN_TDM_RX_5"},
+ {"Quinary TDM6 Playback", NULL, "QUIN_TDM_RX_6"},
+ {"Quinary TDM7 Playback", NULL, "QUIN_TDM_RX_7"},
+
+ {"PRIMARY_TDM_TX_0", NULL, "Primary TDM0 Capture"},
+ {"PRIMARY_TDM_TX_1", NULL, "Primary TDM1 Capture"},
+ {"PRIMARY_TDM_TX_2", NULL, "Primary TDM2 Capture"},
+ {"PRIMARY_TDM_TX_3", NULL, "Primary TDM3 Capture"},
+ {"PRIMARY_TDM_TX_4", NULL, "Primary TDM4 Capture"},
+ {"PRIMARY_TDM_TX_5", NULL, "Primary TDM5 Capture"},
+ {"PRIMARY_TDM_TX_6", NULL, "Primary TDM6 Capture"},
+ {"PRIMARY_TDM_TX_7", NULL, "Primary TDM7 Capture"},
+
+ {"SEC_TDM_TX_0", NULL, "Secondary TDM0 Capture"},
+ {"SEC_TDM_TX_1", NULL, "Secondary TDM1 Capture"},
+ {"SEC_TDM_TX_2", NULL, "Secondary TDM2 Capture"},
+ {"SEC_TDM_TX_3", NULL, "Secondary TDM3 Capture"},
+ {"SEC_TDM_TX_4", NULL, "Secondary TDM4 Capture"},
+ {"SEC_TDM_TX_5", NULL, "Secondary TDM5 Capture"},
+ {"SEC_TDM_TX_6", NULL, "Secondary TDM6 Capture"},
+ {"SEC_TDM_TX_7", NULL, "Secondary TDM7 Capture"},
+
+ {"TERT_TDM_TX_0", NULL, "Tertiary TDM0 Capture"},
+ {"TERT_TDM_TX_1", NULL, "Tertiary TDM1 Capture"},
+ {"TERT_TDM_TX_2", NULL, "Tertiary TDM2 Capture"},
+ {"TERT_TDM_TX_3", NULL, "Tertiary TDM3 Capture"},
+ {"TERT_TDM_TX_4", NULL, "Tertiary TDM4 Capture"},
+ {"TERT_TDM_TX_5", NULL, "Tertiary TDM5 Capture"},
+ {"TERT_TDM_TX_6", NULL, "Tertiary TDM6 Capture"},
+ {"TERT_TDM_TX_7", NULL, "Tertiary TDM7 Capture"},
+
+ {"QUAT_TDM_TX_0", NULL, "Quaternary TDM0 Capture"},
+ {"QUAT_TDM_TX_1", NULL, "Quaternary TDM1 Capture"},
+ {"QUAT_TDM_TX_2", NULL, "Quaternary TDM2 Capture"},
+ {"QUAT_TDM_TX_3", NULL, "Quaternary TDM3 Capture"},
+ {"QUAT_TDM_TX_4", NULL, "Quaternary TDM4 Capture"},
+ {"QUAT_TDM_TX_5", NULL, "Quaternary TDM5 Capture"},
+ {"QUAT_TDM_TX_6", NULL, "Quaternary TDM6 Capture"},
+ {"QUAT_TDM_TX_7", NULL, "Quaternary TDM7 Capture"},
+
+ {"QUIN_TDM_TX_0", NULL, "Quinary TDM0 Capture"},
+ {"QUIN_TDM_TX_1", NULL, "Quinary TDM1 Capture"},
+ {"QUIN_TDM_TX_2", NULL, "Quinary TDM2 Capture"},
+ {"QUIN_TDM_TX_3", NULL, "Quinary TDM3 Capture"},
+ {"QUIN_TDM_TX_4", NULL, "Quinary TDM4 Capture"},
+ {"QUIN_TDM_TX_5", NULL, "Quinary TDM5 Capture"},
+ {"QUIN_TDM_TX_6", NULL, "Quinary TDM6 Capture"},
+ {"QUIN_TDM_TX_7", NULL, "Quinary TDM7 Capture"},
+
+ {"TERT_MI2S_TX", NULL, "Tertiary MI2S Capture"},
+ {"PRI_MI2S_TX", NULL, "Primary MI2S Capture"},
+ {"SEC_MI2S_TX", NULL, "Secondary MI2S Capture"},
+ {"QUAT_MI2S_TX", NULL, "Quaternary MI2S Capture"},
+ {"QUIN_MI2S_TX", NULL, "Quinary MI2S Capture"},
+ {"SEN_MI2S_TX", NULL, "Senary MI2S Capture"},
+
+ {"WSA_CODEC_DMA_RX_0 Playback", NULL, "WSA_CODEC_DMA_RX_0"},
+ {"WSA_CODEC_DMA_TX_0", NULL, "WSA_CODEC_DMA_TX_0 Capture"},
+ {"WSA_CODEC_DMA_RX_1 Playback", NULL, "WSA_CODEC_DMA_RX_1"},
+ {"WSA_CODEC_DMA_TX_1", NULL, "WSA_CODEC_DMA_TX_1 Capture"},
+ {"WSA_CODEC_DMA_TX_2", NULL, "WSA_CODEC_DMA_TX_2 Capture"},
+ {"VA_CODEC_DMA_TX_0", NULL, "VA_CODEC_DMA_TX_0 Capture"},
+ {"VA_CODEC_DMA_TX_1", NULL, "VA_CODEC_DMA_TX_1 Capture"},
+ {"VA_CODEC_DMA_TX_2", NULL, "VA_CODEC_DMA_TX_2 Capture"},
+ {"RX_CODEC_DMA_RX_0 Playback", NULL, "RX_CODEC_DMA_RX_0"},
+ {"TX_CODEC_DMA_TX_0", NULL, "TX_CODEC_DMA_TX_0 Capture"},
+ {"RX_CODEC_DMA_RX_1 Playback", NULL, "RX_CODEC_DMA_RX_1"},
+ {"TX_CODEC_DMA_TX_1", NULL, "TX_CODEC_DMA_TX_1 Capture"},
+ {"RX_CODEC_DMA_RX_2 Playback", NULL, "RX_CODEC_DMA_RX_2"},
+ {"TX_CODEC_DMA_TX_2", NULL, "TX_CODEC_DMA_TX_2 Capture"},
+ {"RX_CODEC_DMA_RX_3 Playback", NULL, "RX_CODEC_DMA_RX_3"},
+ {"TX_CODEC_DMA_TX_3", NULL, "TX_CODEC_DMA_TX_3 Capture"},
+ {"RX_CODEC_DMA_RX_4 Playback", NULL, "RX_CODEC_DMA_RX_4"},
+ {"TX_CODEC_DMA_TX_4", NULL, "TX_CODEC_DMA_TX_4 Capture"},
+ {"RX_CODEC_DMA_RX_5 Playback", NULL, "RX_CODEC_DMA_RX_5"},
+ {"TX_CODEC_DMA_TX_5", NULL, "TX_CODEC_DMA_TX_5 Capture"},
+ {"RX_CODEC_DMA_RX_6 Playback", NULL, "RX_CODEC_DMA_RX_6"},
+ {"RX_CODEC_DMA_RX_7 Playback", NULL, "RX_CODEC_DMA_RX_7"},
+
+ /* USB playback AFE port receives data for playback, hence use the RX port */
+ {"USB Playback", NULL, "USB_RX"},
+
+ {"LPI RX0 MI2S Playback", NULL, "LPI_MI2S_RX_0"},
+ {"LPI_MI2S_TX_0", NULL, "LPI TX0 MI2S Capture"},
+ {"LPI RX1 MI2S Playback", NULL, "LPI_MI2S_RX_1"},
+ {"LPI_MI2S_TX_1", NULL, "LPI TX1 MI2S Capture"},
+ {"LPI RX2 MI2S Playback", NULL, "LPI_MI2S_RX_2"},
+ {"LPI_MI2S_TX_2", NULL, "LPI TX2 MI2S Capture"},
+ {"LPI RX3 MI2S Playback", NULL, "LPI_MI2S_RX_3"},
+ {"LPI_MI2S_TX_3", NULL, "LPI TX3 MI2S Capture"},
+ {"LPI RX4 MI2S Playback", NULL, "LPI_MI2S_RX_4"},
+ {"LPI_MI2S_TX_4", NULL, "LPI TX4 MI2S Capture"},
+ {"LPI RX5 MI2S Playback", NULL, "LPI_MI2S_RX_5"},
+ {"LPI_MI2S_TX_5", NULL, "LPI TX5 MI2S Capture"},
+ {"LPI RX6 MI2S Playback", NULL, "LPI_MI2S_RX_6"},
+ {"LPI_MI2S_TX_6", NULL, "LPI TX6 MI2S Capture"},
+};
+
+static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_port *port;
+
+ port = q6afe_port_get_from_id(dai->dev, dai->id);
+ if (IS_ERR(port)) {
+ dev_err(dai->dev, "Unable to get afe port\n");
+ return -EINVAL;
+ }
+ dai_data->port[dai->id] = port;
+
+ return 0;
+}
+
+static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+
+ q6afe_port_put(dai_data->port[dai->id]);
+ dai_data->port[dai->id] = NULL;
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops q6afe_usb_ops = {
+ .probe = msm_dai_q6_dai_probe,
+ .prepare = q6afe_dai_prepare,
+ .hw_params = q6afe_usb_hw_params,
+ /*
+ * Shutdown callback required to stop the USB AFE port, which is enabled
+ * by the prepare() stage. This stops the audio traffic on the USB AFE
+ * port on the Q6DSP.
+ */
+ .shutdown = q6afe_dai_shutdown,
+ /*
+ * Startup callback not needed, as AFE port start command passes the PCM
+ * parameters within the AFE command, which is provided by the PCM core
+ * during the prepare() stage.
+ */
+};
+
+static const struct snd_soc_dai_ops q6hdmi_ops = {
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ .prepare = q6afe_dai_prepare,
+ .hw_params = q6hdmi_hw_params,
+ .shutdown = q6afe_dai_shutdown,
+};
+
+static const struct snd_soc_dai_ops q6i2s_ops = {
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ .prepare = q6afe_dai_prepare,
+ .hw_params = q6i2s_hw_params,
+ .set_fmt = q6i2s_set_fmt,
+ .shutdown = q6afe_dai_shutdown,
+ .set_sysclk = q6afe_mi2s_set_sysclk,
+};
+
+static const struct snd_soc_dai_ops q6slim_ops = {
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ .prepare = q6afe_dai_prepare,
+ .hw_params = q6slim_hw_params,
+ .shutdown = q6afe_dai_shutdown,
+ .set_channel_map = q6slim_set_channel_map,
+};
+
+static const struct snd_soc_dai_ops q6tdm_ops = {
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ .prepare = q6afe_dai_prepare,
+ .shutdown = q6afe_dai_shutdown,
+ .set_sysclk = q6afe_mi2s_set_sysclk,
+ .set_tdm_slot = q6tdm_set_tdm_slot,
+ .set_channel_map = q6tdm_set_channel_map,
+ .hw_params = q6tdm_hw_params,
+};
+
+static const struct snd_soc_dai_ops q6dma_ops = {
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ .prepare = q6afe_dai_prepare,
+ .shutdown = q6afe_dai_shutdown,
+ .set_sysclk = q6afe_mi2s_set_sysclk,
+ .set_channel_map = q6dma_set_channel_map,
+ .hw_params = q6dma_hw_params,
+};
+
+static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("HDMI_RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SLIMBUS_0_RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SLIMBUS_1_RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SLIMBUS_2_RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SLIMBUS_3_RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SLIMBUS_4_RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SLIMBUS_5_RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SLIMBUS_6_RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_0_TX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_1_TX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_2_TX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_3_TX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_4_TX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_5_TX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_6_TX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SEN_MI2S_RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SEN_MI2S_TX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUIN_MI2S_RX", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUIN_MI2S_TX", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUAT_MI2S_RX", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUAT_MI2S_TX", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("TERT_MI2S_RX", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TERT_MI2S_TX", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SEC_MI2S_RX", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SEC_MI2S_TX", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SEC_MI2S_RX_SD1",
+ "Secondary MI2S Playback SD1",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("PRI_MI2S_RX", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("PRI_MI2S_TX", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_0", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_1", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_2", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_3", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_4", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_5", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_6", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_7", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_0", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_1", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_2", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_3", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_4", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_5", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_6", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_7", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_0", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_1", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_2", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_3", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_4", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_5", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_6", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_7", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_0", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_1", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_2", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_3", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_4", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_5", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_6", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_7", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_0", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_1", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_2", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_3", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_4", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_5", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_6", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_7", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_0", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_1", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_2", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_3", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_4", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_5", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_6", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_7", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_0", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_1", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_2", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_3", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_4", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_5", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_6", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_7", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_0", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_1", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_2", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_3", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_4", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_5", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_6", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_7", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_0", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_1", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_2", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_3", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_4", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_5", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_6", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_7", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_0", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_1", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_2", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_3", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_4", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_5", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_6", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_7", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("DISPLAY_PORT_RX", "NULL", 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_AIF_IN("WSA_CODEC_DMA_RX_0", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("WSA_CODEC_DMA_TX_0", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("WSA_CODEC_DMA_RX_1", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("WSA_CODEC_DMA_TX_1", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("WSA_CODEC_DMA_TX_2", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("VA_CODEC_DMA_TX_0", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("VA_CODEC_DMA_TX_1", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("VA_CODEC_DMA_TX_2", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_0", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX_CODEC_DMA_TX_0", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_1", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX_CODEC_DMA_TX_1", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_2", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX_CODEC_DMA_TX_2", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_3", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX_CODEC_DMA_TX_3", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_4", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX_CODEC_DMA_TX_4", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_5", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX_CODEC_DMA_TX_5", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_6", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_7", "NULL",
+ 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_AIF_IN("USB_RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_0", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_0", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_1", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_1", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_2", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_2", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_3", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_3", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_4", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_4", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_5", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_5", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_6", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_6", NULL,
+ 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_component_driver q6afe_dai_component = {
+ .name = "q6afe-dai-component",
+ .dapm_widgets = q6afe_dai_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(q6afe_dai_widgets),
+ .dapm_routes = q6afe_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(q6afe_dapm_routes),
+ .of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name,
+
+};
+
+static void of_q6afe_parse_dai_data(struct device *dev,
+ struct q6afe_dai_data *data)
+{
+ struct device_node *node;
+ int ret;
+
+ for_each_child_of_node(dev->of_node, node) {
+ unsigned int lines[Q6AFE_MAX_MI2S_LINES];
+ struct q6afe_dai_priv_data *priv;
+ int id, i, num_lines;
+
+ ret = of_property_read_u32(node, "reg", &id);
+ if (ret || id < 0 || id >= AFE_PORT_MAX) {
+ dev_err(dev, "valid dai id not found:%d\n", ret);
+ continue;
+ }
+
+ switch (id) {
+ /* MI2S specific properties */
+ case SENARY_MI2S_RX ... SENARY_MI2S_TX:
+ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX:
+ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX:
+ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4:
+ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6:
+ priv = &data->priv[id];
+ ret = of_property_read_variable_u32_array(node,
+ "qcom,sd-lines",
+ lines, 0,
+ Q6AFE_MAX_MI2S_LINES);
+ if (ret < 0)
+ num_lines = 0;
+ else
+ num_lines = ret;
+
+ priv->sd_line_mask = 0;
+
+ for (i = 0; i < num_lines; i++)
+ priv->sd_line_mask |= BIT(lines[i]);
+
+ break;
+ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7:
+ priv = &data->priv[id];
+ ret = of_property_read_u32(node, "qcom,tdm-sync-mode",
+ &priv->sync_mode);
+ if (ret) {
+ dev_err(dev, "No Sync mode from DT\n");
+ break;
+ }
+ ret = of_property_read_u32(node, "qcom,tdm-sync-src",
+ &priv->sync_src);
+ if (ret) {
+ dev_err(dev, "No Sync Src from DT\n");
+ break;
+ }
+ ret = of_property_read_u32(node, "qcom,tdm-data-out",
+ &priv->data_out_enable);
+ if (ret) {
+ dev_err(dev, "No Data out enable from DT\n");
+ break;
+ }
+ ret = of_property_read_u32(node, "qcom,tdm-invert-sync",
+ &priv->invert_sync);
+ if (ret) {
+ dev_err(dev, "No Invert sync from DT\n");
+ break;
+ }
+ ret = of_property_read_u32(node, "qcom,tdm-data-delay",
+ &priv->data_delay);
+ if (ret) {
+ dev_err(dev, "No Data Delay from DT\n");
+ break;
+ }
+ ret = of_property_read_u32(node, "qcom,tdm-data-align",
+ &priv->data_align);
+ if (ret) {
+ dev_err(dev, "No Data align from DT\n");
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static int q6afe_dai_dev_probe(struct platform_device *pdev)
+{
+ struct q6dsp_audio_port_dai_driver_config cfg;
+ struct snd_soc_dai_driver *dais;
+ struct q6afe_dai_data *dai_data;
+ struct device *dev = &pdev->dev;
+ int num_dais;
+
+ dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL);
+ if (!dai_data)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, dai_data);
+ of_q6afe_parse_dai_data(dev, dai_data);
+
+ cfg.q6hdmi_ops = &q6hdmi_ops;
+ cfg.q6slim_ops = &q6slim_ops;
+ cfg.q6i2s_ops = &q6i2s_ops;
+ cfg.q6tdm_ops = &q6tdm_ops;
+ cfg.q6dma_ops = &q6dma_ops;
+ cfg.q6usb_ops = &q6afe_usb_ops;
+ dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais);
+
+ return devm_snd_soc_register_component(dev, &q6afe_dai_component, dais, num_dais);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id q6afe_dai_device_id[] = {
+ { .compatible = "qcom,q6afe-dais" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6afe_dai_device_id);
+#endif
+
+static struct platform_driver q6afe_dai_platform_driver = {
+ .driver = {
+ .name = "q6afe-dai",
+ .of_match_table = of_match_ptr(q6afe_dai_device_id),
+ },
+ .probe = q6afe_dai_dev_probe,
+};
+module_platform_driver(q6afe_dai_platform_driver);
+
+MODULE_DESCRIPTION("Q6 Audio Frontend dai driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c
new file mode 100644
index 000000000..1d68a80e8
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6afe.c
@@ -0,0 +1,2005 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/soc/qcom/apr.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "q6dsp-errno.h"
+#include "q6core.h"
+#include "q6afe.h"
+
+/* AFE CMDs */
+#define AFE_PORT_CMD_DEVICE_START 0x000100E5
+#define AFE_PORT_CMD_DEVICE_STOP 0x000100E6
+#define AFE_PORT_CMD_SET_PARAM_V2 0x000100EF
+#define AFE_SVC_CMD_SET_PARAM 0x000100f3
+#define AFE_PORT_CMDRSP_GET_PARAM_V2 0x00010106
+#define AFE_PARAM_ID_HDMI_CONFIG 0x00010210
+#define AFE_MODULE_AUDIO_DEV_INTERFACE 0x0001020C
+#define AFE_MODULE_TDM 0x0001028A
+
+#define AFE_PARAM_ID_CDC_SLIMBUS_SLAVE_CFG 0x00010235
+#define AFE_PARAM_ID_USB_AUDIO_DEV_PARAMS 0x000102A5
+#define AFE_PARAM_ID_USB_AUDIO_DEV_LPCM_FMT 0x000102AA
+
+#define AFE_PARAM_ID_LPAIF_CLK_CONFIG 0x00010238
+#define AFE_PARAM_ID_INT_DIGITAL_CDC_CLK_CONFIG 0x00010239
+
+#define AFE_PARAM_ID_SLIMBUS_CONFIG 0x00010212
+#define AFE_PARAM_ID_I2S_CONFIG 0x0001020D
+#define AFE_PARAM_ID_TDM_CONFIG 0x0001029D
+#define AFE_PARAM_ID_PORT_SLOT_MAPPING_CONFIG 0x00010297
+#define AFE_PARAM_ID_CODEC_DMA_CONFIG 0x000102B8
+#define AFE_PARAM_ID_USB_AUDIO_CONFIG 0x000102A4
+#define AFE_CMD_REMOTE_LPASS_CORE_HW_VOTE_REQUEST 0x000100f4
+#define AFE_CMD_RSP_REMOTE_LPASS_CORE_HW_VOTE_REQUEST 0x000100f5
+#define AFE_CMD_REMOTE_LPASS_CORE_HW_DEVOTE_REQUEST 0x000100f6
+
+/* I2S config specific */
+#define AFE_API_VERSION_I2S_CONFIG 0x1
+#define AFE_PORT_I2S_SD0 0x1
+#define AFE_PORT_I2S_SD1 0x2
+#define AFE_PORT_I2S_SD2 0x3
+#define AFE_PORT_I2S_SD3 0x4
+#define AFE_PORT_I2S_SD0_MASK BIT(0x0)
+#define AFE_PORT_I2S_SD1_MASK BIT(0x1)
+#define AFE_PORT_I2S_SD2_MASK BIT(0x2)
+#define AFE_PORT_I2S_SD3_MASK BIT(0x3)
+#define AFE_PORT_I2S_SD0_1_MASK GENMASK(1, 0)
+#define AFE_PORT_I2S_SD2_3_MASK GENMASK(3, 2)
+#define AFE_PORT_I2S_SD0_1_2_MASK GENMASK(2, 0)
+#define AFE_PORT_I2S_SD0_1_2_3_MASK GENMASK(3, 0)
+#define AFE_PORT_I2S_QUAD01 0x5
+#define AFE_PORT_I2S_QUAD23 0x6
+#define AFE_PORT_I2S_6CHS 0x7
+#define AFE_PORT_I2S_8CHS 0x8
+#define AFE_PORT_I2S_MONO 0x0
+#define AFE_PORT_I2S_STEREO 0x1
+#define AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL 0x0
+#define AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL 0x1
+#define AFE_LINEAR_PCM_DATA 0x0
+
+#define AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG 0x1
+
+/* Port IDs */
+#define AFE_API_VERSION_HDMI_CONFIG 0x1
+#define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E
+#define AFE_PORT_ID_HDMI_OVER_DP_RX 0x6020
+
+/* USB AFE port */
+#define AFE_PORT_ID_USB_RX 0x7000
+
+#define AFE_API_VERSION_SLIMBUS_CONFIG 0x1
+/* Clock set API version */
+#define AFE_API_VERSION_CLOCK_SET 1
+#define Q6AFE_LPASS_CLK_CONFIG_API_VERSION 0x1
+#define AFE_MODULE_CLOCK_SET 0x0001028F
+#define AFE_PARAM_ID_CLOCK_SET 0x00010290
+
+/* SLIMbus Rx port on channel 0. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX 0x4000
+/* SLIMbus Tx port on channel 0. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX 0x4001
+/* SLIMbus Rx port on channel 1. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX 0x4002
+/* SLIMbus Tx port on channel 1. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX 0x4003
+/* SLIMbus Rx port on channel 2. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX 0x4004
+/* SLIMbus Tx port on channel 2. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX 0x4005
+/* SLIMbus Rx port on channel 3. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX 0x4006
+/* SLIMbus Tx port on channel 3. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX 0x4007
+/* SLIMbus Rx port on channel 4. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX 0x4008
+/* SLIMbus Tx port on channel 4. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX 0x4009
+/* SLIMbus Rx port on channel 5. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX 0x400a
+/* SLIMbus Tx port on channel 5. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_TX 0x400b
+/* SLIMbus Rx port on channel 6. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX 0x400c
+/* SLIMbus Tx port on channel 6. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX 0x400d
+#define AFE_PORT_ID_PRIMARY_MI2S_RX 0x1000
+#define AFE_PORT_ID_PRIMARY_MI2S_TX 0x1001
+#define AFE_PORT_ID_SECONDARY_MI2S_RX 0x1002
+#define AFE_PORT_ID_SECONDARY_MI2S_TX 0x1003
+#define AFE_PORT_ID_TERTIARY_MI2S_RX 0x1004
+#define AFE_PORT_ID_TERTIARY_MI2S_TX 0x1005
+#define AFE_PORT_ID_QUATERNARY_MI2S_RX 0x1006
+#define AFE_PORT_ID_QUATERNARY_MI2S_TX 0x1007
+#define AFE_PORT_ID_QUINARY_MI2S_RX 0x1016
+#define AFE_PORT_ID_QUINARY_MI2S_TX 0x1017
+#define AFE_PORT_ID_SENARY_MI2S_RX 0x1018
+#define AFE_PORT_ID_SENARY_MI2S_TX 0x1019
+#define AFE_PORT_ID_INT0_MI2S_RX 0x102e
+#define AFE_PORT_ID_INT0_MI2S_TX 0x102f
+#define AFE_PORT_ID_INT1_MI2S_RX 0x1030
+#define AFE_PORT_ID_INT1_MI2S_TX 0x1031
+#define AFE_PORT_ID_INT2_MI2S_RX 0x1032
+#define AFE_PORT_ID_INT2_MI2S_TX 0x1033
+#define AFE_PORT_ID_INT3_MI2S_RX 0x1034
+#define AFE_PORT_ID_INT3_MI2S_TX 0x1035
+#define AFE_PORT_ID_INT4_MI2S_RX 0x1036
+#define AFE_PORT_ID_INT4_MI2S_TX 0x1037
+#define AFE_PORT_ID_INT5_MI2S_RX 0x1038
+#define AFE_PORT_ID_INT5_MI2S_TX 0x1039
+#define AFE_PORT_ID_INT6_MI2S_RX 0x103a
+#define AFE_PORT_ID_INT6_MI2S_TX 0x103b
+
+/* Start of the range of port IDs for TDM devices. */
+#define AFE_PORT_ID_TDM_PORT_RANGE_START 0x9000
+
+/* End of the range of port IDs for TDM devices. */
+#define AFE_PORT_ID_TDM_PORT_RANGE_END \
+ (AFE_PORT_ID_TDM_PORT_RANGE_START+0x50-1)
+
+/* Size of the range of port IDs for TDM ports. */
+#define AFE_PORT_ID_TDM_PORT_RANGE_SIZE \
+ (AFE_PORT_ID_TDM_PORT_RANGE_END - \
+ AFE_PORT_ID_TDM_PORT_RANGE_START+1)
+
+#define AFE_PORT_ID_PRIMARY_TDM_RX \
+ (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x00)
+#define AFE_PORT_ID_PRIMARY_TDM_RX_1 \
+ (AFE_PORT_ID_PRIMARY_TDM_RX + 0x02)
+#define AFE_PORT_ID_PRIMARY_TDM_RX_2 \
+ (AFE_PORT_ID_PRIMARY_TDM_RX + 0x04)
+#define AFE_PORT_ID_PRIMARY_TDM_RX_3 \
+ (AFE_PORT_ID_PRIMARY_TDM_RX + 0x06)
+#define AFE_PORT_ID_PRIMARY_TDM_RX_4 \
+ (AFE_PORT_ID_PRIMARY_TDM_RX + 0x08)
+#define AFE_PORT_ID_PRIMARY_TDM_RX_5 \
+ (AFE_PORT_ID_PRIMARY_TDM_RX + 0x0A)
+#define AFE_PORT_ID_PRIMARY_TDM_RX_6 \
+ (AFE_PORT_ID_PRIMARY_TDM_RX + 0x0C)
+#define AFE_PORT_ID_PRIMARY_TDM_RX_7 \
+ (AFE_PORT_ID_PRIMARY_TDM_RX + 0x0E)
+
+#define AFE_PORT_ID_PRIMARY_TDM_TX \
+ (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x01)
+#define AFE_PORT_ID_PRIMARY_TDM_TX_1 \
+ (AFE_PORT_ID_PRIMARY_TDM_TX + 0x02)
+#define AFE_PORT_ID_PRIMARY_TDM_TX_2 \
+ (AFE_PORT_ID_PRIMARY_TDM_TX + 0x04)
+#define AFE_PORT_ID_PRIMARY_TDM_TX_3 \
+ (AFE_PORT_ID_PRIMARY_TDM_TX + 0x06)
+#define AFE_PORT_ID_PRIMARY_TDM_TX_4 \
+ (AFE_PORT_ID_PRIMARY_TDM_TX + 0x08)
+#define AFE_PORT_ID_PRIMARY_TDM_TX_5 \
+ (AFE_PORT_ID_PRIMARY_TDM_TX + 0x0A)
+#define AFE_PORT_ID_PRIMARY_TDM_TX_6 \
+ (AFE_PORT_ID_PRIMARY_TDM_TX + 0x0C)
+#define AFE_PORT_ID_PRIMARY_TDM_TX_7 \
+ (AFE_PORT_ID_PRIMARY_TDM_TX + 0x0E)
+
+#define AFE_PORT_ID_SECONDARY_TDM_RX \
+ (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x10)
+#define AFE_PORT_ID_SECONDARY_TDM_RX_1 \
+ (AFE_PORT_ID_SECONDARY_TDM_RX + 0x02)
+#define AFE_PORT_ID_SECONDARY_TDM_RX_2 \
+ (AFE_PORT_ID_SECONDARY_TDM_RX + 0x04)
+#define AFE_PORT_ID_SECONDARY_TDM_RX_3 \
+ (AFE_PORT_ID_SECONDARY_TDM_RX + 0x06)
+#define AFE_PORT_ID_SECONDARY_TDM_RX_4 \
+ (AFE_PORT_ID_SECONDARY_TDM_RX + 0x08)
+#define AFE_PORT_ID_SECONDARY_TDM_RX_5 \
+ (AFE_PORT_ID_SECONDARY_TDM_RX + 0x0A)
+#define AFE_PORT_ID_SECONDARY_TDM_RX_6 \
+ (AFE_PORT_ID_SECONDARY_TDM_RX + 0x0C)
+#define AFE_PORT_ID_SECONDARY_TDM_RX_7 \
+ (AFE_PORT_ID_SECONDARY_TDM_RX + 0x0E)
+
+#define AFE_PORT_ID_SECONDARY_TDM_TX \
+ (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x11)
+#define AFE_PORT_ID_SECONDARY_TDM_TX_1 \
+ (AFE_PORT_ID_SECONDARY_TDM_TX + 0x02)
+#define AFE_PORT_ID_SECONDARY_TDM_TX_2 \
+ (AFE_PORT_ID_SECONDARY_TDM_TX + 0x04)
+#define AFE_PORT_ID_SECONDARY_TDM_TX_3 \
+ (AFE_PORT_ID_SECONDARY_TDM_TX + 0x06)
+#define AFE_PORT_ID_SECONDARY_TDM_TX_4 \
+ (AFE_PORT_ID_SECONDARY_TDM_TX + 0x08)
+#define AFE_PORT_ID_SECONDARY_TDM_TX_5 \
+ (AFE_PORT_ID_SECONDARY_TDM_TX + 0x0A)
+#define AFE_PORT_ID_SECONDARY_TDM_TX_6 \
+ (AFE_PORT_ID_SECONDARY_TDM_TX + 0x0C)
+#define AFE_PORT_ID_SECONDARY_TDM_TX_7 \
+ (AFE_PORT_ID_SECONDARY_TDM_TX + 0x0E)
+
+#define AFE_PORT_ID_TERTIARY_TDM_RX \
+ (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x20)
+#define AFE_PORT_ID_TERTIARY_TDM_RX_1 \
+ (AFE_PORT_ID_TERTIARY_TDM_RX + 0x02)
+#define AFE_PORT_ID_TERTIARY_TDM_RX_2 \
+ (AFE_PORT_ID_TERTIARY_TDM_RX + 0x04)
+#define AFE_PORT_ID_TERTIARY_TDM_RX_3 \
+ (AFE_PORT_ID_TERTIARY_TDM_RX + 0x06)
+#define AFE_PORT_ID_TERTIARY_TDM_RX_4 \
+ (AFE_PORT_ID_TERTIARY_TDM_RX + 0x08)
+#define AFE_PORT_ID_TERTIARY_TDM_RX_5 \
+ (AFE_PORT_ID_TERTIARY_TDM_RX + 0x0A)
+#define AFE_PORT_ID_TERTIARY_TDM_RX_6 \
+ (AFE_PORT_ID_TERTIARY_TDM_RX + 0x0C)
+#define AFE_PORT_ID_TERTIARY_TDM_RX_7 \
+ (AFE_PORT_ID_TERTIARY_TDM_RX + 0x0E)
+
+#define AFE_PORT_ID_TERTIARY_TDM_TX \
+ (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x21)
+#define AFE_PORT_ID_TERTIARY_TDM_TX_1 \
+ (AFE_PORT_ID_TERTIARY_TDM_TX + 0x02)
+#define AFE_PORT_ID_TERTIARY_TDM_TX_2 \
+ (AFE_PORT_ID_TERTIARY_TDM_TX + 0x04)
+#define AFE_PORT_ID_TERTIARY_TDM_TX_3 \
+ (AFE_PORT_ID_TERTIARY_TDM_TX + 0x06)
+#define AFE_PORT_ID_TERTIARY_TDM_TX_4 \
+ (AFE_PORT_ID_TERTIARY_TDM_TX + 0x08)
+#define AFE_PORT_ID_TERTIARY_TDM_TX_5 \
+ (AFE_PORT_ID_TERTIARY_TDM_TX + 0x0A)
+#define AFE_PORT_ID_TERTIARY_TDM_TX_6 \
+ (AFE_PORT_ID_TERTIARY_TDM_TX + 0x0C)
+#define AFE_PORT_ID_TERTIARY_TDM_TX_7 \
+ (AFE_PORT_ID_TERTIARY_TDM_TX + 0x0E)
+
+#define AFE_PORT_ID_QUATERNARY_TDM_RX \
+ (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x30)
+#define AFE_PORT_ID_QUATERNARY_TDM_RX_1 \
+ (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x02)
+#define AFE_PORT_ID_QUATERNARY_TDM_RX_2 \
+ (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x04)
+#define AFE_PORT_ID_QUATERNARY_TDM_RX_3 \
+ (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x06)
+#define AFE_PORT_ID_QUATERNARY_TDM_RX_4 \
+ (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x08)
+#define AFE_PORT_ID_QUATERNARY_TDM_RX_5 \
+ (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x0A)
+#define AFE_PORT_ID_QUATERNARY_TDM_RX_6 \
+ (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x0C)
+#define AFE_PORT_ID_QUATERNARY_TDM_RX_7 \
+ (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x0E)
+
+#define AFE_PORT_ID_QUATERNARY_TDM_TX \
+ (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x31)
+#define AFE_PORT_ID_QUATERNARY_TDM_TX_1 \
+ (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x02)
+#define AFE_PORT_ID_QUATERNARY_TDM_TX_2 \
+ (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x04)
+#define AFE_PORT_ID_QUATERNARY_TDM_TX_3 \
+ (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x06)
+#define AFE_PORT_ID_QUATERNARY_TDM_TX_4 \
+ (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x08)
+#define AFE_PORT_ID_QUATERNARY_TDM_TX_5 \
+ (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x0A)
+#define AFE_PORT_ID_QUATERNARY_TDM_TX_6 \
+ (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x0C)
+#define AFE_PORT_ID_QUATERNARY_TDM_TX_7 \
+ (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x0E)
+
+#define AFE_PORT_ID_QUINARY_TDM_RX \
+ (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x40)
+#define AFE_PORT_ID_QUINARY_TDM_RX_1 \
+ (AFE_PORT_ID_QUINARY_TDM_RX + 0x02)
+#define AFE_PORT_ID_QUINARY_TDM_RX_2 \
+ (AFE_PORT_ID_QUINARY_TDM_RX + 0x04)
+#define AFE_PORT_ID_QUINARY_TDM_RX_3 \
+ (AFE_PORT_ID_QUINARY_TDM_RX + 0x06)
+#define AFE_PORT_ID_QUINARY_TDM_RX_4 \
+ (AFE_PORT_ID_QUINARY_TDM_RX + 0x08)
+#define AFE_PORT_ID_QUINARY_TDM_RX_5 \
+ (AFE_PORT_ID_QUINARY_TDM_RX + 0x0A)
+#define AFE_PORT_ID_QUINARY_TDM_RX_6 \
+ (AFE_PORT_ID_QUINARY_TDM_RX + 0x0C)
+#define AFE_PORT_ID_QUINARY_TDM_RX_7 \
+ (AFE_PORT_ID_QUINARY_TDM_RX + 0x0E)
+
+#define AFE_PORT_ID_QUINARY_TDM_TX \
+ (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x41)
+#define AFE_PORT_ID_QUINARY_TDM_TX_1 \
+ (AFE_PORT_ID_QUINARY_TDM_TX + 0x02)
+#define AFE_PORT_ID_QUINARY_TDM_TX_2 \
+ (AFE_PORT_ID_QUINARY_TDM_TX + 0x04)
+#define AFE_PORT_ID_QUINARY_TDM_TX_3 \
+ (AFE_PORT_ID_QUINARY_TDM_TX + 0x06)
+#define AFE_PORT_ID_QUINARY_TDM_TX_4 \
+ (AFE_PORT_ID_QUINARY_TDM_TX + 0x08)
+#define AFE_PORT_ID_QUINARY_TDM_TX_5 \
+ (AFE_PORT_ID_QUINARY_TDM_TX + 0x0A)
+#define AFE_PORT_ID_QUINARY_TDM_TX_6 \
+ (AFE_PORT_ID_QUINARY_TDM_TX + 0x0C)
+#define AFE_PORT_ID_QUINARY_TDM_TX_7 \
+ (AFE_PORT_ID_QUINARY_TDM_TX + 0x0E)
+
+/* AFE WSA Codec DMA Rx port 0 */
+#define AFE_PORT_ID_WSA_CODEC_DMA_RX_0 0xB000
+/* AFE WSA Codec DMA Tx port 0 */
+#define AFE_PORT_ID_WSA_CODEC_DMA_TX_0 0xB001
+/* AFE WSA Codec DMA Rx port 1 */
+#define AFE_PORT_ID_WSA_CODEC_DMA_RX_1 0xB002
+/* AFE WSA Codec DMA Tx port 1 */
+#define AFE_PORT_ID_WSA_CODEC_DMA_TX_1 0xB003
+/* AFE WSA Codec DMA Tx port 2 */
+#define AFE_PORT_ID_WSA_CODEC_DMA_TX_2 0xB005
+/* AFE VA Codec DMA Tx port 0 */
+#define AFE_PORT_ID_VA_CODEC_DMA_TX_0 0xB021
+/* AFE VA Codec DMA Tx port 1 */
+#define AFE_PORT_ID_VA_CODEC_DMA_TX_1 0xB023
+/* AFE VA Codec DMA Tx port 2 */
+#define AFE_PORT_ID_VA_CODEC_DMA_TX_2 0xB025
+/* AFE Rx Codec DMA Rx port 0 */
+#define AFE_PORT_ID_RX_CODEC_DMA_RX_0 0xB030
+/* AFE Tx Codec DMA Tx port 0 */
+#define AFE_PORT_ID_TX_CODEC_DMA_TX_0 0xB031
+/* AFE Rx Codec DMA Rx port 1 */
+#define AFE_PORT_ID_RX_CODEC_DMA_RX_1 0xB032
+/* AFE Tx Codec DMA Tx port 1 */
+#define AFE_PORT_ID_TX_CODEC_DMA_TX_1 0xB033
+/* AFE Rx Codec DMA Rx port 2 */
+#define AFE_PORT_ID_RX_CODEC_DMA_RX_2 0xB034
+/* AFE Tx Codec DMA Tx port 2 */
+#define AFE_PORT_ID_TX_CODEC_DMA_TX_2 0xB035
+/* AFE Rx Codec DMA Rx port 3 */
+#define AFE_PORT_ID_RX_CODEC_DMA_RX_3 0xB036
+/* AFE Tx Codec DMA Tx port 3 */
+#define AFE_PORT_ID_TX_CODEC_DMA_TX_3 0xB037
+/* AFE Rx Codec DMA Rx port 4 */
+#define AFE_PORT_ID_RX_CODEC_DMA_RX_4 0xB038
+/* AFE Tx Codec DMA Tx port 4 */
+#define AFE_PORT_ID_TX_CODEC_DMA_TX_4 0xB039
+/* AFE Rx Codec DMA Rx port 5 */
+#define AFE_PORT_ID_RX_CODEC_DMA_RX_5 0xB03A
+/* AFE Tx Codec DMA Tx port 5 */
+#define AFE_PORT_ID_TX_CODEC_DMA_TX_5 0xB03B
+/* AFE Rx Codec DMA Rx port 6 */
+#define AFE_PORT_ID_RX_CODEC_DMA_RX_6 0xB03C
+/* AFE Rx Codec DMA Rx port 7 */
+#define AFE_PORT_ID_RX_CODEC_DMA_RX_7 0xB03E
+
+#define Q6AFE_LPASS_MODE_CLK1_VALID 1
+#define Q6AFE_LPASS_MODE_CLK2_VALID 2
+#define Q6AFE_LPASS_CLK_SRC_INTERNAL 1
+#define Q6AFE_LPASS_CLK_ROOT_DEFAULT 0
+#define AFE_API_VERSION_TDM_CONFIG 1
+#define AFE_API_VERSION_SLOT_MAPPING_CONFIG 1
+#define AFE_API_VERSION_CODEC_DMA_CONFIG 1
+
+#define TIMEOUT_MS 3000
+#define AFE_CMD_RESP_AVAIL 0
+#define AFE_CMD_RESP_NONE 1
+#define AFE_CLK_TOKEN 1024
+
+struct q6afe {
+ struct apr_device *apr;
+ struct device *dev;
+ struct q6core_svc_api_info ainfo;
+ struct mutex lock;
+ struct aprv2_ibasic_rsp_result_t result;
+ wait_queue_head_t wait;
+ struct list_head port_list;
+ spinlock_t port_list_lock;
+};
+
+struct afe_port_cmd_device_start {
+ u16 port_id;
+ u16 reserved;
+} __packed;
+
+struct afe_port_cmd_device_stop {
+ u16 port_id;
+ u16 reserved;
+/* Reserved for 32-bit alignment. This field must be set to 0.*/
+} __packed;
+
+struct afe_port_param_data_v2 {
+ u32 module_id;
+ u32 param_id;
+ u16 param_size;
+ u16 reserved;
+} __packed;
+
+struct afe_svc_cmd_set_param {
+ uint32_t payload_size;
+ uint32_t payload_address_lsw;
+ uint32_t payload_address_msw;
+ uint32_t mem_map_handle;
+} __packed;
+
+struct afe_port_cmd_set_param_v2 {
+ u16 port_id;
+ u16 payload_size;
+ u32 payload_address_lsw;
+ u32 payload_address_msw;
+ u32 mem_map_handle;
+} __packed;
+
+struct afe_param_id_hdmi_multi_chan_audio_cfg {
+ u32 hdmi_cfg_minor_version;
+ u16 datatype;
+ u16 channel_allocation;
+ u32 sample_rate;
+ u16 bit_width;
+ u16 reserved;
+} __packed;
+
+struct afe_param_id_slimbus_cfg {
+ u32 sb_cfg_minor_version;
+/* Minor version used for tracking the version of the SLIMBUS
+ * configuration interface.
+ * Supported values: #AFE_API_VERSION_SLIMBUS_CONFIG
+ */
+
+ u16 slimbus_dev_id;
+/* SLIMbus hardware device ID, which is required to handle
+ * multiple SLIMbus hardware blocks.
+ * Supported values: - #AFE_SLIMBUS_DEVICE_1 - #AFE_SLIMBUS_DEVICE_2
+ */
+ u16 bit_width;
+/* Bit width of the sample.
+ * Supported values: 16, 24
+ */
+ u16 data_format;
+/* Data format supported by the SLIMbus hardware. The default is
+ * 0 (#AFE_SB_DATA_FORMAT_NOT_INDICATED), which indicates the
+ * hardware does not perform any format conversions before the data
+ * transfer.
+ */
+ u16 num_channels;
+/* Number of channels.
+ * Supported values: 1 to #AFE_PORT_MAX_AUDIO_CHAN_CNT
+ */
+ u8 shared_ch_mapping[AFE_PORT_MAX_AUDIO_CHAN_CNT];
+/* Mapping of shared channel IDs (128 to 255) to which the
+ * master port is to be connected.
+ * Shared_channel_mapping[i] represents the shared channel assigned
+ * for audio channel i in multichannel audio data.
+ */
+ u32 sample_rate;
+/* Sampling rate of the port.
+ * Supported values:
+ * - #AFE_PORT_SAMPLE_RATE_8K
+ * - #AFE_PORT_SAMPLE_RATE_16K
+ * - #AFE_PORT_SAMPLE_RATE_48K
+ * - #AFE_PORT_SAMPLE_RATE_96K
+ * - #AFE_PORT_SAMPLE_RATE_192K
+ */
+} __packed;
+
+struct afe_clk_cfg {
+ u32 i2s_cfg_minor_version;
+ u32 clk_val1;
+ u32 clk_val2;
+ u16 clk_src;
+ u16 clk_root;
+ u16 clk_set_mode;
+ u16 reserved;
+} __packed;
+
+struct afe_digital_clk_cfg {
+ u32 i2s_cfg_minor_version;
+ u32 clk_val;
+ u16 clk_root;
+ u16 reserved;
+} __packed;
+
+struct afe_param_id_i2s_cfg {
+ u32 i2s_cfg_minor_version;
+ u16 bit_width;
+ u16 channel_mode;
+ u16 mono_stereo;
+ u16 ws_src;
+ u32 sample_rate;
+ u16 data_format;
+ u16 reserved;
+} __packed;
+
+struct afe_param_id_tdm_cfg {
+ u32 tdm_cfg_minor_version;
+ u32 num_channels;
+ u32 sample_rate;
+ u32 bit_width;
+ u16 data_format;
+ u16 sync_mode;
+ u16 sync_src;
+ u16 nslots_per_frame;
+ u16 ctrl_data_out_enable;
+ u16 ctrl_invert_sync_pulse;
+ u16 ctrl_sync_data_delay;
+ u16 slot_width;
+ u32 slot_mask;
+} __packed;
+
+struct afe_param_id_cdc_dma_cfg {
+ u32 cdc_dma_cfg_minor_version;
+ u32 sample_rate;
+ u16 bit_width;
+ u16 data_format;
+ u16 num_channels;
+ u16 active_channels_mask;
+} __packed;
+
+struct afe_param_id_usb_cfg {
+/* Minor version used for tracking USB audio device configuration.
+ * Supported values: AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG
+ */
+ u32 cfg_minor_version;
+/* Sampling rate of the port.
+ * Supported values:
+ * - AFE_PORT_SAMPLE_RATE_8K
+ * - AFE_PORT_SAMPLE_RATE_11025
+ * - AFE_PORT_SAMPLE_RATE_12K
+ * - AFE_PORT_SAMPLE_RATE_16K
+ * - AFE_PORT_SAMPLE_RATE_22050
+ * - AFE_PORT_SAMPLE_RATE_24K
+ * - AFE_PORT_SAMPLE_RATE_32K
+ * - AFE_PORT_SAMPLE_RATE_44P1K
+ * - AFE_PORT_SAMPLE_RATE_48K
+ * - AFE_PORT_SAMPLE_RATE_96K
+ * - AFE_PORT_SAMPLE_RATE_192K
+ */
+ u32 sample_rate;
+/* Bit width of the sample.
+ * Supported values: 16, 24
+ */
+ u16 bit_width;
+/* Number of channels.
+ * Supported values: 1 and 2
+ */
+ u16 num_channels;
+/* Data format supported by the USB. The supported value is
+ * 0 (#AFE_USB_AUDIO_DATA_FORMAT_LINEAR_PCM).
+ */
+ u16 data_format;
+/* this field must be 0 */
+ u16 reserved;
+/* device token of actual end USB audio device */
+ u32 dev_token;
+/* endianness of this interface */
+ u32 endian;
+/* service interval */
+ u32 service_interval;
+} __packed;
+
+/**
+ * struct afe_param_id_usb_audio_dev_params
+ * @cfg_minor_version: Minor version used for tracking USB audio device
+ * configuration.
+ * Supported values:
+ * AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG
+ * @dev_token: device token of actual end USB audio device
+ **/
+struct afe_param_id_usb_audio_dev_params {
+ u32 cfg_minor_version;
+ u32 dev_token;
+} __packed;
+
+/**
+ * struct afe_param_id_usb_audio_dev_lpcm_fmt
+ * @cfg_minor_version: Minor version used for tracking USB audio device
+ * configuration.
+ * Supported values:
+ * AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG
+ * @endian: endianness of this interface
+ **/
+struct afe_param_id_usb_audio_dev_lpcm_fmt {
+ u32 cfg_minor_version;
+ u32 endian;
+} __packed;
+
+#define AFE_PARAM_ID_USB_AUDIO_SVC_INTERVAL 0x000102B7
+
+/**
+ * struct afe_param_id_usb_audio_svc_interval
+ * @cfg_minor_version: Minor version used for tracking USB audio device
+ * configuration.
+ * Supported values:
+ * AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG
+ * @svc_interval: service interval
+ **/
+struct afe_param_id_usb_audio_svc_interval {
+ u32 cfg_minor_version;
+ u32 svc_interval;
+} __packed;
+
+union afe_port_config {
+ struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch;
+ struct afe_param_id_slimbus_cfg slim_cfg;
+ struct afe_param_id_i2s_cfg i2s_cfg;
+ struct afe_param_id_tdm_cfg tdm_cfg;
+ struct afe_param_id_cdc_dma_cfg dma_cfg;
+ struct afe_param_id_usb_cfg usb_cfg;
+} __packed;
+
+
+struct afe_clk_set {
+ uint32_t clk_set_minor_version;
+ uint32_t clk_id;
+ uint32_t clk_freq_in_hz;
+ uint16_t clk_attri;
+ uint16_t clk_root;
+ uint32_t enable;
+};
+
+struct afe_param_id_slot_mapping_cfg {
+ u32 minor_version;
+ u16 num_channels;
+ u16 bitwidth;
+ u32 data_align_type;
+ u16 ch_mapping[AFE_PORT_MAX_AUDIO_CHAN_CNT];
+} __packed;
+
+struct q6afe_port {
+ wait_queue_head_t wait;
+ union afe_port_config port_cfg;
+ struct afe_param_id_slot_mapping_cfg *scfg;
+ struct aprv2_ibasic_rsp_result_t result;
+ int token;
+ int id;
+ int cfg_type;
+ struct q6afe *afe;
+ struct kref refcount;
+ struct list_head node;
+};
+
+struct afe_cmd_remote_lpass_core_hw_vote_request {
+ uint32_t hw_block_id;
+ char client_name[8];
+} __packed;
+
+struct afe_cmd_remote_lpass_core_hw_devote_request {
+ uint32_t hw_block_id;
+ uint32_t client_handle;
+} __packed;
+
+
+
+struct afe_port_map {
+ int port_id;
+ int token;
+ int is_rx;
+ int is_dig_pcm;
+};
+
+/*
+ * Mapping between Virtual Port IDs to DSP AFE Port ID
+ * On B Family SoCs DSP Port IDs are consistent across multiple SoCs
+ * on A Family SoCs DSP port IDs are same as virtual Port IDs.
+ */
+
+static struct afe_port_map port_maps[AFE_PORT_MAX] = {
+ [HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX, HDMI_RX, 1, 1},
+ [SLIMBUS_0_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX,
+ SLIMBUS_0_RX, 1, 1},
+ [SLIMBUS_1_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX,
+ SLIMBUS_1_RX, 1, 1},
+ [SLIMBUS_2_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX,
+ SLIMBUS_2_RX, 1, 1},
+ [SLIMBUS_3_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX,
+ SLIMBUS_3_RX, 1, 1},
+ [SLIMBUS_4_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX,
+ SLIMBUS_4_RX, 1, 1},
+ [SLIMBUS_5_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX,
+ SLIMBUS_5_RX, 1, 1},
+ [SLIMBUS_6_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX,
+ SLIMBUS_6_RX, 1, 1},
+ [SLIMBUS_0_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX,
+ SLIMBUS_0_TX, 0, 1},
+ [SLIMBUS_1_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX,
+ SLIMBUS_1_TX, 0, 1},
+ [SLIMBUS_2_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX,
+ SLIMBUS_2_TX, 0, 1},
+ [SLIMBUS_3_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX,
+ SLIMBUS_3_TX, 0, 1},
+ [SLIMBUS_4_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX,
+ SLIMBUS_4_TX, 0, 1},
+ [SLIMBUS_5_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_TX,
+ SLIMBUS_5_TX, 0, 1},
+ [SLIMBUS_6_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX,
+ SLIMBUS_6_TX, 0, 1},
+ [PRIMARY_MI2S_RX] = { AFE_PORT_ID_PRIMARY_MI2S_RX,
+ PRIMARY_MI2S_RX, 1, 1},
+ [PRIMARY_MI2S_TX] = { AFE_PORT_ID_PRIMARY_MI2S_TX,
+ PRIMARY_MI2S_RX, 0, 1},
+ [SECONDARY_MI2S_RX] = { AFE_PORT_ID_SECONDARY_MI2S_RX,
+ SECONDARY_MI2S_RX, 1, 1},
+ [SECONDARY_MI2S_TX] = { AFE_PORT_ID_SECONDARY_MI2S_TX,
+ SECONDARY_MI2S_TX, 0, 1},
+ [TERTIARY_MI2S_RX] = { AFE_PORT_ID_TERTIARY_MI2S_RX,
+ TERTIARY_MI2S_RX, 1, 1},
+ [TERTIARY_MI2S_TX] = { AFE_PORT_ID_TERTIARY_MI2S_TX,
+ TERTIARY_MI2S_TX, 0, 1},
+ [QUATERNARY_MI2S_RX] = { AFE_PORT_ID_QUATERNARY_MI2S_RX,
+ QUATERNARY_MI2S_RX, 1, 1},
+ [QUATERNARY_MI2S_TX] = { AFE_PORT_ID_QUATERNARY_MI2S_TX,
+ QUATERNARY_MI2S_TX, 0, 1},
+ [QUINARY_MI2S_RX] = { AFE_PORT_ID_QUINARY_MI2S_RX,
+ QUINARY_MI2S_RX, 1, 1},
+ [QUINARY_MI2S_TX] = { AFE_PORT_ID_QUINARY_MI2S_TX,
+ QUINARY_MI2S_TX, 0, 1},
+ [SENARY_MI2S_RX] = { AFE_PORT_ID_SENARY_MI2S_RX,
+ SENARY_MI2S_RX, 1, 1},
+ [SENARY_MI2S_TX] = { AFE_PORT_ID_SENARY_MI2S_TX,
+ SENARY_MI2S_TX, 0, 1},
+ [PRIMARY_TDM_RX_0] = { AFE_PORT_ID_PRIMARY_TDM_RX,
+ PRIMARY_TDM_RX_0, 1, 1},
+ [PRIMARY_TDM_TX_0] = { AFE_PORT_ID_PRIMARY_TDM_TX,
+ PRIMARY_TDM_TX_0, 0, 1},
+ [PRIMARY_TDM_RX_1] = { AFE_PORT_ID_PRIMARY_TDM_RX_1,
+ PRIMARY_TDM_RX_1, 1, 1},
+ [PRIMARY_TDM_TX_1] = { AFE_PORT_ID_PRIMARY_TDM_TX_1,
+ PRIMARY_TDM_TX_1, 0, 1},
+ [PRIMARY_TDM_RX_2] = { AFE_PORT_ID_PRIMARY_TDM_RX_2,
+ PRIMARY_TDM_RX_2, 1, 1},
+ [PRIMARY_TDM_TX_2] = { AFE_PORT_ID_PRIMARY_TDM_TX_2,
+ PRIMARY_TDM_TX_2, 0, 1},
+ [PRIMARY_TDM_RX_3] = { AFE_PORT_ID_PRIMARY_TDM_RX_3,
+ PRIMARY_TDM_RX_3, 1, 1},
+ [PRIMARY_TDM_TX_3] = { AFE_PORT_ID_PRIMARY_TDM_TX_3,
+ PRIMARY_TDM_TX_3, 0, 1},
+ [PRIMARY_TDM_RX_4] = { AFE_PORT_ID_PRIMARY_TDM_RX_4,
+ PRIMARY_TDM_RX_4, 1, 1},
+ [PRIMARY_TDM_TX_4] = { AFE_PORT_ID_PRIMARY_TDM_TX_4,
+ PRIMARY_TDM_TX_4, 0, 1},
+ [PRIMARY_TDM_RX_5] = { AFE_PORT_ID_PRIMARY_TDM_RX_5,
+ PRIMARY_TDM_RX_5, 1, 1},
+ [PRIMARY_TDM_TX_5] = { AFE_PORT_ID_PRIMARY_TDM_TX_5,
+ PRIMARY_TDM_TX_5, 0, 1},
+ [PRIMARY_TDM_RX_6] = { AFE_PORT_ID_PRIMARY_TDM_RX_6,
+ PRIMARY_TDM_RX_6, 1, 1},
+ [PRIMARY_TDM_TX_6] = { AFE_PORT_ID_PRIMARY_TDM_TX_6,
+ PRIMARY_TDM_TX_6, 0, 1},
+ [PRIMARY_TDM_RX_7] = { AFE_PORT_ID_PRIMARY_TDM_RX_7,
+ PRIMARY_TDM_RX_7, 1, 1},
+ [PRIMARY_TDM_TX_7] = { AFE_PORT_ID_PRIMARY_TDM_TX_7,
+ PRIMARY_TDM_TX_7, 0, 1},
+ [SECONDARY_TDM_RX_0] = { AFE_PORT_ID_SECONDARY_TDM_RX,
+ SECONDARY_TDM_RX_0, 1, 1},
+ [SECONDARY_TDM_TX_0] = { AFE_PORT_ID_SECONDARY_TDM_TX,
+ SECONDARY_TDM_TX_0, 0, 1},
+ [SECONDARY_TDM_RX_1] = { AFE_PORT_ID_SECONDARY_TDM_RX_1,
+ SECONDARY_TDM_RX_1, 1, 1},
+ [SECONDARY_TDM_TX_1] = { AFE_PORT_ID_SECONDARY_TDM_TX_1,
+ SECONDARY_TDM_TX_1, 0, 1},
+ [SECONDARY_TDM_RX_2] = { AFE_PORT_ID_SECONDARY_TDM_RX_2,
+ SECONDARY_TDM_RX_2, 1, 1},
+ [SECONDARY_TDM_TX_2] = { AFE_PORT_ID_SECONDARY_TDM_TX_2,
+ SECONDARY_TDM_TX_2, 0, 1},
+ [SECONDARY_TDM_RX_3] = { AFE_PORT_ID_SECONDARY_TDM_RX_3,
+ SECONDARY_TDM_RX_3, 1, 1},
+ [SECONDARY_TDM_TX_3] = { AFE_PORT_ID_SECONDARY_TDM_TX_3,
+ SECONDARY_TDM_TX_3, 0, 1},
+ [SECONDARY_TDM_RX_4] = { AFE_PORT_ID_SECONDARY_TDM_RX_4,
+ SECONDARY_TDM_RX_4, 1, 1},
+ [SECONDARY_TDM_TX_4] = { AFE_PORT_ID_SECONDARY_TDM_TX_4,
+ SECONDARY_TDM_TX_4, 0, 1},
+ [SECONDARY_TDM_RX_5] = { AFE_PORT_ID_SECONDARY_TDM_RX_5,
+ SECONDARY_TDM_RX_5, 1, 1},
+ [SECONDARY_TDM_TX_5] = { AFE_PORT_ID_SECONDARY_TDM_TX_5,
+ SECONDARY_TDM_TX_5, 0, 1},
+ [SECONDARY_TDM_RX_6] = { AFE_PORT_ID_SECONDARY_TDM_RX_6,
+ SECONDARY_TDM_RX_6, 1, 1},
+ [SECONDARY_TDM_TX_6] = { AFE_PORT_ID_SECONDARY_TDM_TX_6,
+ SECONDARY_TDM_TX_6, 0, 1},
+ [SECONDARY_TDM_RX_7] = { AFE_PORT_ID_SECONDARY_TDM_RX_7,
+ SECONDARY_TDM_RX_7, 1, 1},
+ [SECONDARY_TDM_TX_7] = { AFE_PORT_ID_SECONDARY_TDM_TX_7,
+ SECONDARY_TDM_TX_7, 0, 1},
+ [TERTIARY_TDM_RX_0] = { AFE_PORT_ID_TERTIARY_TDM_RX,
+ TERTIARY_TDM_RX_0, 1, 1},
+ [TERTIARY_TDM_TX_0] = { AFE_PORT_ID_TERTIARY_TDM_TX,
+ TERTIARY_TDM_TX_0, 0, 1},
+ [TERTIARY_TDM_RX_1] = { AFE_PORT_ID_TERTIARY_TDM_RX_1,
+ TERTIARY_TDM_RX_1, 1, 1},
+ [TERTIARY_TDM_TX_1] = { AFE_PORT_ID_TERTIARY_TDM_TX_1,
+ TERTIARY_TDM_TX_1, 0, 1},
+ [TERTIARY_TDM_RX_2] = { AFE_PORT_ID_TERTIARY_TDM_RX_2,
+ TERTIARY_TDM_RX_2, 1, 1},
+ [TERTIARY_TDM_TX_2] = { AFE_PORT_ID_TERTIARY_TDM_TX_2,
+ TERTIARY_TDM_TX_2, 0, 1},
+ [TERTIARY_TDM_RX_3] = { AFE_PORT_ID_TERTIARY_TDM_RX_3,
+ TERTIARY_TDM_RX_3, 1, 1},
+ [TERTIARY_TDM_TX_3] = { AFE_PORT_ID_TERTIARY_TDM_TX_3,
+ TERTIARY_TDM_TX_3, 0, 1},
+ [TERTIARY_TDM_RX_4] = { AFE_PORT_ID_TERTIARY_TDM_RX_4,
+ TERTIARY_TDM_RX_4, 1, 1},
+ [TERTIARY_TDM_TX_4] = { AFE_PORT_ID_TERTIARY_TDM_TX_4,
+ TERTIARY_TDM_TX_4, 0, 1},
+ [TERTIARY_TDM_RX_5] = { AFE_PORT_ID_TERTIARY_TDM_RX_5,
+ TERTIARY_TDM_RX_5, 1, 1},
+ [TERTIARY_TDM_TX_5] = { AFE_PORT_ID_TERTIARY_TDM_TX_5,
+ TERTIARY_TDM_TX_5, 0, 1},
+ [TERTIARY_TDM_RX_6] = { AFE_PORT_ID_TERTIARY_TDM_RX_6,
+ TERTIARY_TDM_RX_6, 1, 1},
+ [TERTIARY_TDM_TX_6] = { AFE_PORT_ID_TERTIARY_TDM_TX_6,
+ TERTIARY_TDM_TX_6, 0, 1},
+ [TERTIARY_TDM_RX_7] = { AFE_PORT_ID_TERTIARY_TDM_RX_7,
+ TERTIARY_TDM_RX_7, 1, 1},
+ [TERTIARY_TDM_TX_7] = { AFE_PORT_ID_TERTIARY_TDM_TX_7,
+ TERTIARY_TDM_TX_7, 0, 1},
+ [QUATERNARY_TDM_RX_0] = { AFE_PORT_ID_QUATERNARY_TDM_RX,
+ QUATERNARY_TDM_RX_0, 1, 1},
+ [QUATERNARY_TDM_TX_0] = { AFE_PORT_ID_QUATERNARY_TDM_TX,
+ QUATERNARY_TDM_TX_0, 0, 1},
+ [QUATERNARY_TDM_RX_1] = { AFE_PORT_ID_QUATERNARY_TDM_RX_1,
+ QUATERNARY_TDM_RX_1, 1, 1},
+ [QUATERNARY_TDM_TX_1] = { AFE_PORT_ID_QUATERNARY_TDM_TX_1,
+ QUATERNARY_TDM_TX_1, 0, 1},
+ [QUATERNARY_TDM_RX_2] = { AFE_PORT_ID_QUATERNARY_TDM_RX_2,
+ QUATERNARY_TDM_RX_2, 1, 1},
+ [QUATERNARY_TDM_TX_2] = { AFE_PORT_ID_QUATERNARY_TDM_TX_2,
+ QUATERNARY_TDM_TX_2, 0, 1},
+ [QUATERNARY_TDM_RX_3] = { AFE_PORT_ID_QUATERNARY_TDM_RX_3,
+ QUATERNARY_TDM_RX_3, 1, 1},
+ [QUATERNARY_TDM_TX_3] = { AFE_PORT_ID_QUATERNARY_TDM_TX_3,
+ QUATERNARY_TDM_TX_3, 0, 1},
+ [QUATERNARY_TDM_RX_4] = { AFE_PORT_ID_QUATERNARY_TDM_RX_4,
+ QUATERNARY_TDM_RX_4, 1, 1},
+ [QUATERNARY_TDM_TX_4] = { AFE_PORT_ID_QUATERNARY_TDM_TX_4,
+ QUATERNARY_TDM_TX_4, 0, 1},
+ [QUATERNARY_TDM_RX_5] = { AFE_PORT_ID_QUATERNARY_TDM_RX_5,
+ QUATERNARY_TDM_RX_5, 1, 1},
+ [QUATERNARY_TDM_TX_5] = { AFE_PORT_ID_QUATERNARY_TDM_TX_5,
+ QUATERNARY_TDM_TX_5, 0, 1},
+ [QUATERNARY_TDM_RX_6] = { AFE_PORT_ID_QUATERNARY_TDM_RX_6,
+ QUATERNARY_TDM_RX_6, 1, 1},
+ [QUATERNARY_TDM_TX_6] = { AFE_PORT_ID_QUATERNARY_TDM_TX_6,
+ QUATERNARY_TDM_TX_6, 0, 1},
+ [QUATERNARY_TDM_RX_7] = { AFE_PORT_ID_QUATERNARY_TDM_RX_7,
+ QUATERNARY_TDM_RX_7, 1, 1},
+ [QUATERNARY_TDM_TX_7] = { AFE_PORT_ID_QUATERNARY_TDM_TX_7,
+ QUATERNARY_TDM_TX_7, 0, 1},
+ [QUINARY_TDM_RX_0] = { AFE_PORT_ID_QUINARY_TDM_RX,
+ QUINARY_TDM_RX_0, 1, 1},
+ [QUINARY_TDM_TX_0] = { AFE_PORT_ID_QUINARY_TDM_TX,
+ QUINARY_TDM_TX_0, 0, 1},
+ [QUINARY_TDM_RX_1] = { AFE_PORT_ID_QUINARY_TDM_RX_1,
+ QUINARY_TDM_RX_1, 1, 1},
+ [QUINARY_TDM_TX_1] = { AFE_PORT_ID_QUINARY_TDM_TX_1,
+ QUINARY_TDM_TX_1, 0, 1},
+ [QUINARY_TDM_RX_2] = { AFE_PORT_ID_QUINARY_TDM_RX_2,
+ QUINARY_TDM_RX_2, 1, 1},
+ [QUINARY_TDM_TX_2] = { AFE_PORT_ID_QUINARY_TDM_TX_2,
+ QUINARY_TDM_TX_2, 0, 1},
+ [QUINARY_TDM_RX_3] = { AFE_PORT_ID_QUINARY_TDM_RX_3,
+ QUINARY_TDM_RX_3, 1, 1},
+ [QUINARY_TDM_TX_3] = { AFE_PORT_ID_QUINARY_TDM_TX_3,
+ QUINARY_TDM_TX_3, 0, 1},
+ [QUINARY_TDM_RX_4] = { AFE_PORT_ID_QUINARY_TDM_RX_4,
+ QUINARY_TDM_RX_4, 1, 1},
+ [QUINARY_TDM_TX_4] = { AFE_PORT_ID_QUINARY_TDM_TX_4,
+ QUINARY_TDM_TX_4, 0, 1},
+ [QUINARY_TDM_RX_5] = { AFE_PORT_ID_QUINARY_TDM_RX_5,
+ QUINARY_TDM_RX_5, 1, 1},
+ [QUINARY_TDM_TX_5] = { AFE_PORT_ID_QUINARY_TDM_TX_5,
+ QUINARY_TDM_TX_5, 0, 1},
+ [QUINARY_TDM_RX_6] = { AFE_PORT_ID_QUINARY_TDM_RX_6,
+ QUINARY_TDM_RX_6, 1, 1},
+ [QUINARY_TDM_TX_6] = { AFE_PORT_ID_QUINARY_TDM_TX_6,
+ QUINARY_TDM_TX_6, 0, 1},
+ [QUINARY_TDM_RX_7] = { AFE_PORT_ID_QUINARY_TDM_RX_7,
+ QUINARY_TDM_RX_7, 1, 1},
+ [QUINARY_TDM_TX_7] = { AFE_PORT_ID_QUINARY_TDM_TX_7,
+ QUINARY_TDM_TX_7, 0, 1},
+ [DISPLAY_PORT_RX] = { AFE_PORT_ID_HDMI_OVER_DP_RX,
+ DISPLAY_PORT_RX, 1, 1},
+ [WSA_CODEC_DMA_RX_0] = { AFE_PORT_ID_WSA_CODEC_DMA_RX_0,
+ WSA_CODEC_DMA_RX_0, 1, 1},
+ [WSA_CODEC_DMA_TX_0] = { AFE_PORT_ID_WSA_CODEC_DMA_TX_0,
+ WSA_CODEC_DMA_TX_0, 0, 1},
+ [WSA_CODEC_DMA_RX_1] = { AFE_PORT_ID_WSA_CODEC_DMA_RX_1,
+ WSA_CODEC_DMA_RX_1, 1, 1},
+ [WSA_CODEC_DMA_TX_1] = { AFE_PORT_ID_WSA_CODEC_DMA_TX_1,
+ WSA_CODEC_DMA_TX_1, 0, 1},
+ [WSA_CODEC_DMA_TX_2] = { AFE_PORT_ID_WSA_CODEC_DMA_TX_2,
+ WSA_CODEC_DMA_TX_2, 0, 1},
+ [VA_CODEC_DMA_TX_0] = { AFE_PORT_ID_VA_CODEC_DMA_TX_0,
+ VA_CODEC_DMA_TX_0, 0, 1},
+ [VA_CODEC_DMA_TX_1] = { AFE_PORT_ID_VA_CODEC_DMA_TX_1,
+ VA_CODEC_DMA_TX_1, 0, 1},
+ [VA_CODEC_DMA_TX_2] = { AFE_PORT_ID_VA_CODEC_DMA_TX_2,
+ VA_CODEC_DMA_TX_2, 0, 1},
+ [RX_CODEC_DMA_RX_0] = { AFE_PORT_ID_RX_CODEC_DMA_RX_0,
+ RX_CODEC_DMA_RX_0, 1, 1},
+ [TX_CODEC_DMA_TX_0] = { AFE_PORT_ID_TX_CODEC_DMA_TX_0,
+ TX_CODEC_DMA_TX_0, 0, 1},
+ [RX_CODEC_DMA_RX_1] = { AFE_PORT_ID_RX_CODEC_DMA_RX_1,
+ RX_CODEC_DMA_RX_1, 1, 1},
+ [TX_CODEC_DMA_TX_1] = { AFE_PORT_ID_TX_CODEC_DMA_TX_1,
+ TX_CODEC_DMA_TX_1, 0, 1},
+ [RX_CODEC_DMA_RX_2] = { AFE_PORT_ID_RX_CODEC_DMA_RX_2,
+ RX_CODEC_DMA_RX_2, 1, 1},
+ [TX_CODEC_DMA_TX_2] = { AFE_PORT_ID_TX_CODEC_DMA_TX_2,
+ TX_CODEC_DMA_TX_2, 0, 1},
+ [RX_CODEC_DMA_RX_3] = { AFE_PORT_ID_RX_CODEC_DMA_RX_3,
+ RX_CODEC_DMA_RX_3, 1, 1},
+ [TX_CODEC_DMA_TX_3] = { AFE_PORT_ID_TX_CODEC_DMA_TX_3,
+ TX_CODEC_DMA_TX_3, 0, 1},
+ [RX_CODEC_DMA_RX_4] = { AFE_PORT_ID_RX_CODEC_DMA_RX_4,
+ RX_CODEC_DMA_RX_4, 1, 1},
+ [TX_CODEC_DMA_TX_4] = { AFE_PORT_ID_TX_CODEC_DMA_TX_4,
+ TX_CODEC_DMA_TX_4, 0, 1},
+ [RX_CODEC_DMA_RX_5] = { AFE_PORT_ID_RX_CODEC_DMA_RX_5,
+ RX_CODEC_DMA_RX_5, 1, 1},
+ [TX_CODEC_DMA_TX_5] = { AFE_PORT_ID_TX_CODEC_DMA_TX_5,
+ TX_CODEC_DMA_TX_5, 0, 1},
+ [RX_CODEC_DMA_RX_6] = { AFE_PORT_ID_RX_CODEC_DMA_RX_6,
+ RX_CODEC_DMA_RX_6, 1, 1},
+ [RX_CODEC_DMA_RX_7] = { AFE_PORT_ID_RX_CODEC_DMA_RX_7,
+ RX_CODEC_DMA_RX_7, 1, 1},
+ [USB_RX] = { AFE_PORT_ID_USB_RX, USB_RX, 1, 1},
+ [LPI_MI2S_RX_0] = { AFE_PORT_ID_INT0_MI2S_RX,
+ LPI_MI2S_RX_0, 1, 1},
+ [LPI_MI2S_TX_0] = { AFE_PORT_ID_INT0_MI2S_TX,
+ LPI_MI2S_TX_0, 0, 1},
+ [LPI_MI2S_RX_1] = { AFE_PORT_ID_INT1_MI2S_RX,
+ LPI_MI2S_RX_1, 1, 1},
+ [LPI_MI2S_TX_1] = { AFE_PORT_ID_INT1_MI2S_TX,
+ LPI_MI2S_TX_1, 0, 1},
+ [LPI_MI2S_RX_2] = { AFE_PORT_ID_INT2_MI2S_RX,
+ LPI_MI2S_RX_2, 1, 1},
+ [LPI_MI2S_TX_2] = { AFE_PORT_ID_INT2_MI2S_TX,
+ LPI_MI2S_TX_2, 0, 1},
+ [LPI_MI2S_RX_3] = { AFE_PORT_ID_INT3_MI2S_RX,
+ LPI_MI2S_RX_3, 1, 1},
+ [LPI_MI2S_TX_3] = { AFE_PORT_ID_INT3_MI2S_TX,
+ LPI_MI2S_TX_3, 0, 1},
+ [LPI_MI2S_RX_4] = { AFE_PORT_ID_INT4_MI2S_RX,
+ LPI_MI2S_RX_4, 1, 1},
+ [LPI_MI2S_TX_4] = { AFE_PORT_ID_INT4_MI2S_TX,
+ LPI_MI2S_TX_4, 0, 1},
+ [LPI_MI2S_RX_5] = { AFE_PORT_ID_INT5_MI2S_RX,
+ LPI_MI2S_RX_5, 1, 1},
+ [LPI_MI2S_TX_5] = { AFE_PORT_ID_INT5_MI2S_TX,
+ LPI_MI2S_TX_5, 0, 1},
+ [LPI_MI2S_RX_6] = { AFE_PORT_ID_INT6_MI2S_RX,
+ LPI_MI2S_RX_6, 1, 1},
+ [LPI_MI2S_TX_6] = { AFE_PORT_ID_INT6_MI2S_TX,
+ LPI_MI2S_TX_6, 0, 1},
+};
+
+static void q6afe_port_free(struct kref *ref)
+{
+ struct q6afe_port *port;
+ struct q6afe *afe;
+
+ port = container_of(ref, struct q6afe_port, refcount);
+ afe = port->afe;
+ scoped_guard(spinlock_irqsave, &afe->port_list_lock)
+ list_del(&port->node);
+ kfree(port->scfg);
+ kfree(port);
+}
+
+static struct q6afe_port *q6afe_find_port(struct q6afe *afe, int token)
+{
+ struct q6afe_port *p;
+ struct q6afe_port *ret = NULL;
+
+ guard(spinlock_irqsave)(&afe->port_list_lock);
+ list_for_each_entry(p, &afe->port_list, node)
+ if (p->token == token) {
+ ret = p;
+ kref_get(&p->refcount);
+ break;
+ }
+
+ return ret;
+}
+
+static int q6afe_callback(struct apr_device *adev, const struct apr_resp_pkt *data)
+{
+ struct q6afe *afe = dev_get_drvdata(&adev->dev);
+ const struct aprv2_ibasic_rsp_result_t *res;
+ const struct apr_hdr *hdr = &data->hdr;
+ struct q6afe_port *port;
+
+ if (!data->payload_size)
+ return 0;
+
+ res = data->payload;
+ switch (hdr->opcode) {
+ case APR_BASIC_RSP_RESULT: {
+ if (res->status) {
+ dev_err(afe->dev, "cmd = 0x%x returned error = 0x%x\n",
+ res->opcode, res->status);
+ }
+ switch (res->opcode) {
+ case AFE_PORT_CMD_SET_PARAM_V2:
+ case AFE_PORT_CMD_DEVICE_STOP:
+ case AFE_PORT_CMD_DEVICE_START:
+ case AFE_SVC_CMD_SET_PARAM:
+ port = q6afe_find_port(afe, hdr->token);
+ if (port) {
+ port->result = *res;
+ wake_up(&port->wait);
+ kref_put(&port->refcount, q6afe_port_free);
+ } else if (hdr->token == AFE_CLK_TOKEN) {
+ afe->result = *res;
+ wake_up(&afe->wait);
+ }
+ break;
+ default:
+ dev_err(afe->dev, "Unknown cmd 0x%x\n", res->opcode);
+ break;
+ }
+ }
+ break;
+ case AFE_CMD_RSP_REMOTE_LPASS_CORE_HW_VOTE_REQUEST:
+ afe->result.opcode = hdr->opcode;
+ afe->result.status = res->status;
+ wake_up(&afe->wait);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * q6afe_get_port_id() - Get port id from a given port index
+ *
+ * @index: port index
+ *
+ * Return: Will be an negative on error or valid port_id on success
+ */
+int q6afe_get_port_id(int index)
+{
+ if (index < 0 || index >= AFE_PORT_MAX)
+ return -EINVAL;
+
+ return port_maps[index].port_id;
+}
+EXPORT_SYMBOL_GPL(q6afe_get_port_id);
+
+static int afe_apr_send_pkt(struct q6afe *afe, struct apr_pkt *pkt,
+ struct q6afe_port *port, uint32_t rsp_opcode)
+{
+ wait_queue_head_t *wait;
+ struct aprv2_ibasic_rsp_result_t *result;
+ int ret;
+
+ mutex_lock(&afe->lock);
+ if (port) {
+ wait = &port->wait;
+ result = &port->result;
+ } else {
+ result = &afe->result;
+ wait = &afe->wait;
+ }
+
+ result->opcode = 0;
+ result->status = 0;
+
+ ret = apr_send_pkt(afe->apr, pkt);
+ if (ret < 0) {
+ dev_err(afe->dev, "packet not transmitted (%d)\n", ret);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = wait_event_timeout(*wait, (result->opcode == rsp_opcode),
+ msecs_to_jiffies(TIMEOUT_MS));
+ if (!ret) {
+ ret = -ETIMEDOUT;
+ } else if (result->status > 0) {
+ dev_err(afe->dev, "DSP returned error[%x]\n",
+ result->status);
+ ret = -EINVAL;
+ } else {
+ ret = 0;
+ }
+
+err:
+ mutex_unlock(&afe->lock);
+
+ return ret;
+}
+
+static int q6afe_set_param(struct q6afe *afe, struct q6afe_port *port,
+ void *data, int param_id, int module_id, int psize,
+ int token)
+{
+ struct afe_svc_cmd_set_param *param;
+ struct afe_port_param_data_v2 *pdata;
+ struct apr_pkt *pkt;
+ int ret, pkt_size = APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata) + psize;
+ void *pl;
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ param = p + APR_HDR_SIZE;
+ pdata = p + APR_HDR_SIZE + sizeof(*param);
+ pl = p + APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata);
+ memcpy(pl, data, psize);
+
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.token = token;
+ pkt->hdr.opcode = AFE_SVC_CMD_SET_PARAM;
+
+ param->payload_size = sizeof(*pdata) + psize;
+ param->payload_address_lsw = 0x00;
+ param->payload_address_msw = 0x00;
+ param->mem_map_handle = 0x00;
+ pdata->module_id = module_id;
+ pdata->param_id = param_id;
+ pdata->param_size = psize;
+
+ ret = afe_apr_send_pkt(afe, pkt, port, AFE_SVC_CMD_SET_PARAM);
+ if (ret)
+ dev_err(afe->dev, "AFE set params failed %d\n", ret);
+
+ return ret;
+}
+
+static int q6afe_port_set_param(struct q6afe_port *port, void *data,
+ int param_id, int module_id, int psize)
+{
+ return q6afe_set_param(port->afe, port, data, param_id, module_id,
+ psize, port->token);
+}
+
+static int q6afe_port_set_param_v2(struct q6afe_port *port, void *data,
+ int param_id, int module_id, int psize)
+{
+ struct afe_port_cmd_set_param_v2 *param;
+ struct afe_port_param_data_v2 *pdata;
+ struct q6afe *afe = port->afe;
+ struct apr_pkt *pkt;
+ u16 port_id = port->id;
+ int ret, pkt_size = APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata) + psize;
+ void *pl;
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ param = p + APR_HDR_SIZE;
+ pdata = p + APR_HDR_SIZE + sizeof(*param);
+ pl = p + APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata);
+ memcpy(pl, data, psize);
+
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.token = port->token;
+ pkt->hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2;
+
+ param->port_id = port_id;
+ param->payload_size = sizeof(*pdata) + psize;
+ param->payload_address_lsw = 0x00;
+ param->payload_address_msw = 0x00;
+ param->mem_map_handle = 0x00;
+ pdata->module_id = module_id;
+ pdata->param_id = param_id;
+ pdata->param_size = psize;
+
+ ret = afe_apr_send_pkt(afe, pkt, port, AFE_PORT_CMD_SET_PARAM_V2);
+ if (ret)
+ dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
+ port_id, ret);
+
+ return ret;
+}
+
+static int q6afe_port_set_lpass_clock(struct q6afe_port *port,
+ struct afe_clk_cfg *cfg)
+{
+ return q6afe_port_set_param_v2(port, cfg,
+ AFE_PARAM_ID_LPAIF_CLK_CONFIG,
+ AFE_MODULE_AUDIO_DEV_INTERFACE,
+ sizeof(*cfg));
+}
+
+static int q6afe_set_lpass_clock_v2(struct q6afe_port *port,
+ struct afe_clk_set *cfg)
+{
+ return q6afe_port_set_param(port, cfg, AFE_PARAM_ID_CLOCK_SET,
+ AFE_MODULE_CLOCK_SET, sizeof(*cfg));
+}
+
+static int q6afe_set_digital_codec_core_clock(struct q6afe_port *port,
+ struct afe_digital_clk_cfg *cfg)
+{
+ return q6afe_port_set_param_v2(port, cfg,
+ AFE_PARAM_ID_INT_DIGITAL_CDC_CLK_CONFIG,
+ AFE_MODULE_AUDIO_DEV_INTERFACE,
+ sizeof(*cfg));
+}
+
+int q6afe_set_lpass_clock(struct device *dev, int clk_id, int attri,
+ int clk_root, unsigned int freq)
+{
+ struct q6afe *afe = dev_get_drvdata(dev->parent);
+ struct afe_clk_set cset = {0,};
+
+ cset.clk_set_minor_version = AFE_API_VERSION_CLOCK_SET;
+ cset.clk_id = clk_id;
+ cset.clk_freq_in_hz = freq;
+ cset.clk_attri = attri;
+ cset.clk_root = clk_root;
+ cset.enable = !!freq;
+
+ return q6afe_set_param(afe, NULL, &cset, AFE_PARAM_ID_CLOCK_SET,
+ AFE_MODULE_CLOCK_SET, sizeof(cset),
+ AFE_CLK_TOKEN);
+}
+EXPORT_SYMBOL_GPL(q6afe_set_lpass_clock);
+
+int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id,
+ int clk_src, int clk_root,
+ unsigned int freq, int dir)
+{
+ struct afe_clk_cfg ccfg = {0,};
+ struct afe_clk_set cset = {0,};
+ struct afe_digital_clk_cfg dcfg = {0,};
+ int ret;
+
+ switch (clk_id) {
+ case LPAIF_DIG_CLK:
+ dcfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG;
+ dcfg.clk_val = freq;
+ dcfg.clk_root = clk_root;
+ ret = q6afe_set_digital_codec_core_clock(port, &dcfg);
+ break;
+ case LPAIF_BIT_CLK:
+ ccfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG;
+ ccfg.clk_val1 = freq;
+ ccfg.clk_src = clk_src;
+ ccfg.clk_root = clk_root;
+ ccfg.clk_set_mode = Q6AFE_LPASS_MODE_CLK1_VALID;
+ ret = q6afe_port_set_lpass_clock(port, &ccfg);
+ break;
+
+ case LPAIF_OSR_CLK:
+ ccfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG;
+ ccfg.clk_val2 = freq;
+ ccfg.clk_src = clk_src;
+ ccfg.clk_root = clk_root;
+ ccfg.clk_set_mode = Q6AFE_LPASS_MODE_CLK2_VALID;
+ ret = q6afe_port_set_lpass_clock(port, &ccfg);
+ break;
+ case Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT ... Q6AFE_LPASS_CLK_ID_QUI_MI2S_OSR:
+ case Q6AFE_LPASS_CLK_ID_MCLK_1 ... Q6AFE_LPASS_CLK_ID_INT_MCLK_1:
+ case Q6AFE_LPASS_CLK_ID_PRI_TDM_IBIT ... Q6AFE_LPASS_CLK_ID_QUIN_TDM_EBIT:
+ case Q6AFE_LPASS_CLK_ID_WSA_CORE_MCLK ... Q6AFE_LPASS_CLK_ID_VA_CORE_2X_MCLK:
+ cset.clk_set_minor_version = AFE_API_VERSION_CLOCK_SET;
+ cset.clk_id = clk_id;
+ cset.clk_freq_in_hz = freq;
+ cset.clk_attri = clk_src;
+ cset.clk_root = clk_root;
+ cset.enable = !!freq;
+ ret = q6afe_set_lpass_clock_v2(port, &cset);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(q6afe_port_set_sysclk);
+
+/**
+ * q6afe_port_stop() - Stop a afe port
+ *
+ * @port: Instance of port to stop
+ *
+ * Return: Will be an negative on packet size on success.
+ */
+int q6afe_port_stop(struct q6afe_port *port)
+{
+ struct afe_port_cmd_device_stop *stop;
+ struct q6afe *afe = port->afe;
+ struct apr_pkt *pkt;
+ int port_id = port->id;
+ int ret = 0;
+ int index, pkt_size;
+
+ index = port->token;
+ if (index < 0 || index >= AFE_PORT_MAX) {
+ dev_err(afe->dev, "AFE port index[%d] invalid!\n", index);
+ return -EINVAL;
+ }
+
+ pkt_size = APR_HDR_SIZE + sizeof(*stop);
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ stop = p + APR_HDR_SIZE;
+
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.token = index;
+ pkt->hdr.opcode = AFE_PORT_CMD_DEVICE_STOP;
+ stop->port_id = port_id;
+ stop->reserved = 0;
+
+ ret = afe_apr_send_pkt(afe, pkt, port, AFE_PORT_CMD_DEVICE_STOP);
+ if (ret)
+ dev_err(afe->dev, "AFE close failed %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(q6afe_port_stop);
+
+/**
+ * q6afe_slim_port_prepare() - Prepare slim afe port.
+ *
+ * @port: Instance of afe port
+ * @cfg: SLIM configuration for the afe port
+ *
+ */
+void q6afe_slim_port_prepare(struct q6afe_port *port,
+ struct q6afe_slim_cfg *cfg)
+{
+ union afe_port_config *pcfg = &port->port_cfg;
+
+ pcfg->slim_cfg.sb_cfg_minor_version = AFE_API_VERSION_SLIMBUS_CONFIG;
+ pcfg->slim_cfg.sample_rate = cfg->sample_rate;
+ pcfg->slim_cfg.bit_width = cfg->bit_width;
+ pcfg->slim_cfg.num_channels = cfg->num_channels;
+ pcfg->slim_cfg.data_format = cfg->data_format;
+ pcfg->slim_cfg.shared_ch_mapping[0] = cfg->ch_mapping[0];
+ pcfg->slim_cfg.shared_ch_mapping[1] = cfg->ch_mapping[1];
+ pcfg->slim_cfg.shared_ch_mapping[2] = cfg->ch_mapping[2];
+ pcfg->slim_cfg.shared_ch_mapping[3] = cfg->ch_mapping[3];
+
+}
+EXPORT_SYMBOL_GPL(q6afe_slim_port_prepare);
+
+/**
+ * q6afe_tdm_port_prepare() - Prepare tdm afe port.
+ *
+ * @port: Instance of afe port
+ * @cfg: TDM configuration for the afe port
+ *
+ */
+void q6afe_tdm_port_prepare(struct q6afe_port *port,
+ struct q6afe_tdm_cfg *cfg)
+{
+ union afe_port_config *pcfg = &port->port_cfg;
+
+ pcfg->tdm_cfg.tdm_cfg_minor_version = AFE_API_VERSION_TDM_CONFIG;
+ pcfg->tdm_cfg.num_channels = cfg->num_channels;
+ pcfg->tdm_cfg.sample_rate = cfg->sample_rate;
+ pcfg->tdm_cfg.bit_width = cfg->bit_width;
+ pcfg->tdm_cfg.data_format = cfg->data_format;
+ pcfg->tdm_cfg.sync_mode = cfg->sync_mode;
+ pcfg->tdm_cfg.sync_src = cfg->sync_src;
+ pcfg->tdm_cfg.nslots_per_frame = cfg->nslots_per_frame;
+
+ pcfg->tdm_cfg.slot_width = cfg->slot_width;
+ pcfg->tdm_cfg.slot_mask = cfg->slot_mask;
+ port->scfg = kzalloc_obj(*port->scfg);
+ if (!port->scfg)
+ return;
+
+ port->scfg->minor_version = AFE_API_VERSION_SLOT_MAPPING_CONFIG;
+ port->scfg->num_channels = cfg->num_channels;
+ port->scfg->bitwidth = cfg->bit_width;
+ port->scfg->data_align_type = cfg->data_align_type;
+ memcpy(port->scfg->ch_mapping, cfg->ch_mapping,
+ sizeof(u16) * AFE_PORT_MAX_AUDIO_CHAN_CNT);
+}
+EXPORT_SYMBOL_GPL(q6afe_tdm_port_prepare);
+
+/**
+ * afe_port_send_usb_dev_param() - Send USB dev token
+ *
+ * @port: Instance of afe port
+ * @cardidx: USB SND card index to reference
+ * @pcmidx: USB SND PCM device index to reference
+ *
+ * The USB dev token carries information about which USB SND card instance and
+ * PCM device to execute the offload on. This information is carried through
+ * to the stream enable QMI request, which is handled by the offload class
+ * driver. The information is parsed to determine which USB device to query
+ * the required resources for.
+ */
+int afe_port_send_usb_dev_param(struct q6afe_port *port, int cardidx, int pcmidx)
+{
+ struct afe_param_id_usb_audio_dev_params usb_dev;
+ int ret;
+
+ memset(&usb_dev, 0, sizeof(usb_dev));
+
+ usb_dev.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG;
+ usb_dev.dev_token = (cardidx << 16) | (pcmidx << 8);
+ ret = q6afe_port_set_param_v2(port, &usb_dev,
+ AFE_PARAM_ID_USB_AUDIO_DEV_PARAMS,
+ AFE_MODULE_AUDIO_DEV_INTERFACE,
+ sizeof(usb_dev));
+ if (ret)
+ dev_err(port->afe->dev, "%s: AFE device param cmd failed %d\n",
+ __func__, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(afe_port_send_usb_dev_param);
+
+static int afe_port_send_usb_params(struct q6afe_port *port, struct q6afe_usb_cfg *cfg)
+{
+ union afe_port_config *pcfg = &port->port_cfg;
+ struct afe_param_id_usb_audio_dev_lpcm_fmt lpcm_fmt;
+ struct afe_param_id_usb_audio_svc_interval svc_int;
+ int ret;
+
+ if (!pcfg) {
+ dev_err(port->afe->dev, "%s: Error, no configuration data\n", __func__);
+ return -EINVAL;
+ }
+
+ memset(&lpcm_fmt, 0, sizeof(lpcm_fmt));
+ memset(&svc_int, 0, sizeof(svc_int));
+
+ lpcm_fmt.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG;
+ lpcm_fmt.endian = pcfg->usb_cfg.endian;
+ ret = q6afe_port_set_param_v2(port, &lpcm_fmt,
+ AFE_PARAM_ID_USB_AUDIO_DEV_LPCM_FMT,
+ AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(lpcm_fmt));
+ if (ret) {
+ dev_err(port->afe->dev, "%s: AFE device param cmd LPCM_FMT failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ svc_int.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG;
+ svc_int.svc_interval = pcfg->usb_cfg.service_interval;
+ ret = q6afe_port_set_param_v2(port, &svc_int,
+ AFE_PARAM_ID_USB_AUDIO_SVC_INTERVAL,
+ AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(svc_int));
+ if (ret)
+ dev_err(port->afe->dev, "%s: AFE device param cmd svc_interval failed %d\n",
+ __func__, ret);
+
+ return ret;
+}
+
+/**
+ * q6afe_usb_port_prepare() - Prepare usb afe port.
+ *
+ * @port: Instance of afe port
+ * @cfg: USB configuration for the afe port
+ *
+ */
+void q6afe_usb_port_prepare(struct q6afe_port *port,
+ struct q6afe_usb_cfg *cfg)
+{
+ union afe_port_config *pcfg = &port->port_cfg;
+
+ pcfg->usb_cfg.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG;
+ pcfg->usb_cfg.sample_rate = cfg->sample_rate;
+ pcfg->usb_cfg.num_channels = cfg->num_channels;
+ pcfg->usb_cfg.bit_width = cfg->bit_width;
+
+ afe_port_send_usb_params(port, cfg);
+}
+EXPORT_SYMBOL_GPL(q6afe_usb_port_prepare);
+
+/**
+ * q6afe_hdmi_port_prepare() - Prepare hdmi afe port.
+ *
+ * @port: Instance of afe port
+ * @cfg: HDMI configuration for the afe port
+ *
+ */
+void q6afe_hdmi_port_prepare(struct q6afe_port *port,
+ struct q6afe_hdmi_cfg *cfg)
+{
+ union afe_port_config *pcfg = &port->port_cfg;
+
+ pcfg->hdmi_multi_ch.hdmi_cfg_minor_version =
+ AFE_API_VERSION_HDMI_CONFIG;
+ pcfg->hdmi_multi_ch.datatype = cfg->datatype;
+ pcfg->hdmi_multi_ch.channel_allocation = cfg->channel_allocation;
+ pcfg->hdmi_multi_ch.sample_rate = cfg->sample_rate;
+ pcfg->hdmi_multi_ch.bit_width = cfg->bit_width;
+}
+EXPORT_SYMBOL_GPL(q6afe_hdmi_port_prepare);
+
+/**
+ * q6afe_i2s_port_prepare() - Prepare i2s afe port.
+ *
+ * @port: Instance of afe port
+ * @cfg: I2S configuration for the afe port
+ * Return: Will be an negative on error and zero on success.
+ */
+int q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg)
+{
+ union afe_port_config *pcfg = &port->port_cfg;
+ struct device *dev = port->afe->dev;
+ int num_sd_lines;
+
+ pcfg->i2s_cfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG;
+ pcfg->i2s_cfg.sample_rate = cfg->sample_rate;
+ pcfg->i2s_cfg.bit_width = cfg->bit_width;
+ pcfg->i2s_cfg.data_format = AFE_LINEAR_PCM_DATA;
+
+ switch (cfg->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
+ pcfg->i2s_cfg.ws_src = AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL;
+ break;
+ case SND_SOC_DAIFMT_BC_FC:
+ /* CPU is slave */
+ pcfg->i2s_cfg.ws_src = AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL;
+ break;
+ default:
+ break;
+ }
+
+ num_sd_lines = hweight_long(cfg->sd_line_mask);
+
+ switch (num_sd_lines) {
+ case 0:
+ dev_err(dev, "no line is assigned\n");
+ return -EINVAL;
+ case 1:
+ switch (cfg->sd_line_mask) {
+ case AFE_PORT_I2S_SD0_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD0;
+ break;
+ case AFE_PORT_I2S_SD1_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD1;
+ break;
+ case AFE_PORT_I2S_SD2_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD2;
+ break;
+ case AFE_PORT_I2S_SD3_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD3;
+ break;
+ default:
+ dev_err(dev, "Invalid SD lines\n");
+ return -EINVAL;
+ }
+ break;
+ case 2:
+ switch (cfg->sd_line_mask) {
+ case AFE_PORT_I2S_SD0_1_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_QUAD01;
+ break;
+ case AFE_PORT_I2S_SD2_3_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_QUAD23;
+ break;
+ default:
+ dev_err(dev, "Invalid SD lines\n");
+ return -EINVAL;
+ }
+ break;
+ case 3:
+ switch (cfg->sd_line_mask) {
+ case AFE_PORT_I2S_SD0_1_2_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_6CHS;
+ break;
+ default:
+ dev_err(dev, "Invalid SD lines\n");
+ return -EINVAL;
+ }
+ break;
+ case 4:
+ switch (cfg->sd_line_mask) {
+ case AFE_PORT_I2S_SD0_1_2_3_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_8CHS;
+
+ break;
+ default:
+ dev_err(dev, "Invalid SD lines\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ dev_err(dev, "Invalid SD lines\n");
+ return -EINVAL;
+ }
+
+ switch (cfg->num_channels) {
+ case 1:
+ case 2:
+ switch (pcfg->i2s_cfg.channel_mode) {
+ case AFE_PORT_I2S_QUAD01:
+ case AFE_PORT_I2S_6CHS:
+ case AFE_PORT_I2S_8CHS:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD0;
+ break;
+ case AFE_PORT_I2S_QUAD23:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD2;
+ break;
+ }
+
+ if (cfg->num_channels == 2)
+ pcfg->i2s_cfg.mono_stereo = AFE_PORT_I2S_STEREO;
+ else
+ pcfg->i2s_cfg.mono_stereo = AFE_PORT_I2S_MONO;
+
+ break;
+ case 3:
+ case 4:
+ if (pcfg->i2s_cfg.channel_mode < AFE_PORT_I2S_QUAD01) {
+ dev_err(dev, "Invalid Channel mode\n");
+ return -EINVAL;
+ }
+ break;
+ case 5:
+ case 6:
+ if (pcfg->i2s_cfg.channel_mode < AFE_PORT_I2S_6CHS) {
+ dev_err(dev, "Invalid Channel mode\n");
+ return -EINVAL;
+ }
+ break;
+ case 7:
+ case 8:
+ if (pcfg->i2s_cfg.channel_mode < AFE_PORT_I2S_8CHS) {
+ dev_err(dev, "Invalid Channel mode\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(q6afe_i2s_port_prepare);
+
+/**
+ * q6afe_cdc_dma_port_prepare() - Prepare dma afe port.
+ *
+ * @port: Instance of afe port
+ * @cfg: DMA configuration for the afe port
+ *
+ */
+void q6afe_cdc_dma_port_prepare(struct q6afe_port *port,
+ struct q6afe_cdc_dma_cfg *cfg)
+{
+ union afe_port_config *pcfg = &port->port_cfg;
+ struct afe_param_id_cdc_dma_cfg *dma_cfg = &pcfg->dma_cfg;
+
+ dma_cfg->cdc_dma_cfg_minor_version = AFE_API_VERSION_CODEC_DMA_CONFIG;
+ dma_cfg->sample_rate = cfg->sample_rate;
+ dma_cfg->bit_width = cfg->bit_width;
+ dma_cfg->data_format = cfg->data_format;
+ dma_cfg->num_channels = cfg->num_channels;
+ if (!cfg->active_channels_mask)
+ dma_cfg->active_channels_mask = (1 << cfg->num_channels) - 1;
+}
+EXPORT_SYMBOL_GPL(q6afe_cdc_dma_port_prepare);
+/**
+ * q6afe_port_start() - Start a afe port
+ *
+ * @port: Instance of port to start
+ *
+ * Return: Will be an negative on packet size on success.
+ */
+int q6afe_port_start(struct q6afe_port *port)
+{
+ struct afe_port_cmd_device_start *start;
+ struct q6afe *afe = port->afe;
+ int port_id = port->id;
+ int ret, param_id = port->cfg_type;
+ struct apr_pkt *pkt;
+ int pkt_size;
+
+ ret = q6afe_port_set_param_v2(port, &port->port_cfg, param_id,
+ AFE_MODULE_AUDIO_DEV_INTERFACE,
+ sizeof(port->port_cfg));
+ if (ret) {
+ dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
+ port_id, ret);
+ return ret;
+ }
+
+ if (port->scfg) {
+ ret = q6afe_port_set_param_v2(port, port->scfg,
+ AFE_PARAM_ID_PORT_SLOT_MAPPING_CONFIG,
+ AFE_MODULE_TDM, sizeof(*port->scfg));
+ if (ret) {
+ dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
+ port_id, ret);
+ return ret;
+ }
+ }
+
+ pkt_size = APR_HDR_SIZE + sizeof(*start);
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ start = p + APR_HDR_SIZE;
+
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.token = port->token;
+ pkt->hdr.opcode = AFE_PORT_CMD_DEVICE_START;
+
+ start->port_id = port_id;
+
+ ret = afe_apr_send_pkt(afe, pkt, port, AFE_PORT_CMD_DEVICE_START);
+ if (ret)
+ dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
+ port_id, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(q6afe_port_start);
+
+/**
+ * q6afe_port_get_from_id() - Get port instance from a port id
+ *
+ * @dev: Pointer to afe child device.
+ * @id: port id
+ *
+ * Return: Will be an error pointer on error or a valid afe port
+ * on success.
+ */
+struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id)
+{
+ int port_id;
+ struct q6afe *afe = dev_get_drvdata(dev->parent);
+ struct q6afe_port *port;
+ int cfg_type;
+
+ if (id < 0 || id >= AFE_PORT_MAX) {
+ dev_err(dev, "AFE port token[%d] invalid!\n", id);
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* if port is multiple times bind/unbind before callback finishes */
+ port = q6afe_find_port(afe, id);
+ if (port) {
+ dev_err(dev, "AFE Port already open\n");
+ return port;
+ }
+
+ port_id = port_maps[id].port_id;
+
+ switch (port_id) {
+ case AFE_PORT_ID_MULTICHAN_HDMI_RX:
+ case AFE_PORT_ID_HDMI_OVER_DP_RX:
+ cfg_type = AFE_PARAM_ID_HDMI_CONFIG;
+ break;
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_TX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX:
+ cfg_type = AFE_PARAM_ID_SLIMBUS_CONFIG;
+ break;
+
+ case AFE_PORT_ID_PRIMARY_MI2S_RX:
+ case AFE_PORT_ID_PRIMARY_MI2S_TX:
+ case AFE_PORT_ID_SECONDARY_MI2S_RX:
+ case AFE_PORT_ID_SECONDARY_MI2S_TX:
+ case AFE_PORT_ID_TERTIARY_MI2S_RX:
+ case AFE_PORT_ID_TERTIARY_MI2S_TX:
+ case AFE_PORT_ID_QUATERNARY_MI2S_RX:
+ case AFE_PORT_ID_QUATERNARY_MI2S_TX:
+ case AFE_PORT_ID_QUINARY_MI2S_RX:
+ case AFE_PORT_ID_QUINARY_MI2S_TX:
+ case AFE_PORT_ID_SENARY_MI2S_RX:
+ case AFE_PORT_ID_SENARY_MI2S_TX:
+ case AFE_PORT_ID_INT0_MI2S_RX:
+ case AFE_PORT_ID_INT0_MI2S_TX:
+ case AFE_PORT_ID_INT1_MI2S_RX:
+ case AFE_PORT_ID_INT1_MI2S_TX:
+ case AFE_PORT_ID_INT2_MI2S_RX:
+ case AFE_PORT_ID_INT2_MI2S_TX:
+ case AFE_PORT_ID_INT3_MI2S_RX:
+ case AFE_PORT_ID_INT3_MI2S_TX:
+ case AFE_PORT_ID_INT4_MI2S_RX:
+ case AFE_PORT_ID_INT4_MI2S_TX:
+ case AFE_PORT_ID_INT5_MI2S_RX:
+ case AFE_PORT_ID_INT5_MI2S_TX:
+ case AFE_PORT_ID_INT6_MI2S_RX:
+ case AFE_PORT_ID_INT6_MI2S_TX:
+ cfg_type = AFE_PARAM_ID_I2S_CONFIG;
+ break;
+ case AFE_PORT_ID_PRIMARY_TDM_RX ... AFE_PORT_ID_QUINARY_TDM_TX_7:
+ cfg_type = AFE_PARAM_ID_TDM_CONFIG;
+ break;
+ case AFE_PORT_ID_WSA_CODEC_DMA_RX_0 ... AFE_PORT_ID_RX_CODEC_DMA_RX_7:
+ cfg_type = AFE_PARAM_ID_CODEC_DMA_CONFIG;
+ break;
+ case AFE_PORT_ID_USB_RX:
+ cfg_type = AFE_PARAM_ID_USB_AUDIO_CONFIG;
+ break;
+ default:
+ dev_err(dev, "Invalid port id 0x%x\n", port_id);
+ return ERR_PTR(-EINVAL);
+ }
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return ERR_PTR(-ENOMEM);
+
+ init_waitqueue_head(&port->wait);
+
+ port->token = id;
+ port->id = port_id;
+ port->afe = afe;
+ port->cfg_type = cfg_type;
+ kref_init(&port->refcount);
+
+ scoped_guard(spinlock_irqsave, &afe->port_list_lock)
+ list_add_tail(&port->node, &afe->port_list);
+
+ return port;
+
+}
+EXPORT_SYMBOL_GPL(q6afe_port_get_from_id);
+
+/**
+ * q6afe_port_put() - Release port reference
+ *
+ * @port: Instance of port to put
+ */
+void q6afe_port_put(struct q6afe_port *port)
+{
+ kref_put(&port->refcount, q6afe_port_free);
+}
+EXPORT_SYMBOL_GPL(q6afe_port_put);
+
+int q6afe_unvote_lpass_core_hw(struct device *dev, uint32_t hw_block_id,
+ uint32_t client_handle)
+{
+ struct q6afe *afe = dev_get_drvdata(dev->parent);
+ struct afe_cmd_remote_lpass_core_hw_devote_request *vote_cfg;
+ struct apr_pkt *pkt;
+ int ret = 0;
+ int pkt_size = APR_HDR_SIZE + sizeof(*vote_cfg);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ vote_cfg = p + APR_HDR_SIZE;
+
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.token = hw_block_id;
+ pkt->hdr.opcode = AFE_CMD_REMOTE_LPASS_CORE_HW_DEVOTE_REQUEST;
+ vote_cfg->hw_block_id = hw_block_id;
+ vote_cfg->client_handle = client_handle;
+
+ ret = apr_send_pkt(afe->apr, pkt);
+ if (ret < 0)
+ dev_err(afe->dev, "AFE failed to unvote (%d)\n", hw_block_id);
+
+ return ret;
+}
+EXPORT_SYMBOL(q6afe_unvote_lpass_core_hw);
+
+int q6afe_vote_lpass_core_hw(struct device *dev, uint32_t hw_block_id,
+ const char *client_name, uint32_t *client_handle)
+{
+ struct q6afe *afe = dev_get_drvdata(dev->parent);
+ struct afe_cmd_remote_lpass_core_hw_vote_request *vote_cfg;
+ struct apr_pkt *pkt;
+ int ret = 0;
+ int pkt_size = APR_HDR_SIZE + sizeof(*vote_cfg);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ vote_cfg = p + APR_HDR_SIZE;
+
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.token = hw_block_id;
+ pkt->hdr.opcode = AFE_CMD_REMOTE_LPASS_CORE_HW_VOTE_REQUEST;
+ vote_cfg->hw_block_id = hw_block_id;
+ strscpy(vote_cfg->client_name, client_name,
+ sizeof(vote_cfg->client_name));
+
+ ret = afe_apr_send_pkt(afe, pkt, NULL,
+ AFE_CMD_RSP_REMOTE_LPASS_CORE_HW_VOTE_REQUEST);
+ if (ret)
+ dev_err(afe->dev, "AFE failed to vote (%d)\n", hw_block_id);
+
+ return ret;
+}
+EXPORT_SYMBOL(q6afe_vote_lpass_core_hw);
+
+static int q6afe_probe(struct apr_device *adev)
+{
+ struct q6afe *afe;
+ struct device *dev = &adev->dev;
+
+ afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL);
+ if (!afe)
+ return -ENOMEM;
+
+ q6core_get_svc_api_info(adev->svc_id, &afe->ainfo);
+ afe->apr = adev;
+ mutex_init(&afe->lock);
+ init_waitqueue_head(&afe->wait);
+ afe->dev = dev;
+ INIT_LIST_HEAD(&afe->port_list);
+ spin_lock_init(&afe->port_list_lock);
+
+ dev_set_drvdata(dev, afe);
+
+ return devm_of_platform_populate(dev);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id q6afe_device_id[] = {
+ { .compatible = "qcom,q6afe" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6afe_device_id);
+#endif
+
+static struct apr_driver qcom_q6afe_driver = {
+ .probe = q6afe_probe,
+ .callback = q6afe_callback,
+ .driver = {
+ .name = "qcom-q6afe",
+ .of_match_table = of_match_ptr(q6afe_device_id),
+
+ },
+};
+
+module_apr_driver(qcom_q6afe_driver);
+MODULE_DESCRIPTION("Q6 Audio Front End");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h
new file mode 100644
index 000000000..0b8c3ec13
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6afe.h
@@ -0,0 +1,275 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __Q6AFE_H__
+#define __Q6AFE_H__
+#include "../common.h"
+
+#define AFE_PORT_MAX (LPASS_MAX_PORT)
+
+#define MSM_AFE_PORT_TYPE_RX 0
+#define MSM_AFE_PORT_TYPE_TX 1
+#define AFE_MAX_PORTS AFE_PORT_MAX
+
+#define Q6AFE_MAX_MI2S_LINES 4
+
+#define AFE_MAX_CHAN_COUNT 8
+#define AFE_PORT_MAX_AUDIO_CHAN_CNT 0x8
+
+#define Q6AFE_LPASS_CLK_SRC_INTERNAL 1
+#define Q6AFE_LPASS_CLK_ROOT_DEFAULT 0
+
+#define LPAIF_DIG_CLK 1
+#define LPAIF_BIT_CLK 2
+#define LPAIF_OSR_CLK 3
+
+/* Clock ID for Primary I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100
+/* Clock ID for Primary I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_PRI_MI2S_EBIT 0x101
+/* Clock ID for Secondary I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT 0x102
+/* Clock ID for Secondary I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_SEC_MI2S_EBIT 0x103
+/* Clock ID for Tertiary I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_TER_MI2S_IBIT 0x104
+/* Clock ID for Tertiary I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_TER_MI2S_EBIT 0x105
+/* Clock ID for Quartnery I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_QUAD_MI2S_IBIT 0x106
+/* Clock ID for Quartnery I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_QUAD_MI2S_EBIT 0x107
+/* Clock ID for Speaker I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_SPEAKER_I2S_IBIT 0x108
+/* Clock ID for Speaker I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_SPEAKER_I2S_EBIT 0x109
+/* Clock ID for Speaker I2S OSR */
+#define Q6AFE_LPASS_CLK_ID_SPEAKER_I2S_OSR 0x10A
+
+/* Clock ID for QUINARY I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_QUI_MI2S_IBIT 0x10B
+/* Clock ID for QUINARY I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_QUI_MI2S_EBIT 0x10C
+/* Clock ID for SENARY I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_SEN_MI2S_IBIT 0x10D
+/* Clock ID for SENARY I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_SEN_MI2S_EBIT 0x10E
+/* Clock ID for INT0 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT0_MI2S_IBIT 0x10F
+/* Clock ID for INT1 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT1_MI2S_IBIT 0x110
+/* Clock ID for INT2 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT2_MI2S_IBIT 0x111
+/* Clock ID for INT3 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT3_MI2S_IBIT 0x112
+/* Clock ID for INT4 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT4_MI2S_IBIT 0x113
+/* Clock ID for INT5 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT5_MI2S_IBIT 0x114
+/* Clock ID for INT6 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT6_MI2S_IBIT 0x115
+
+/* Clock ID for QUINARY MI2S OSR CLK */
+#define Q6AFE_LPASS_CLK_ID_QUI_MI2S_OSR 0x116
+
+/* Clock ID for Primary PCM IBIT */
+#define Q6AFE_LPASS_CLK_ID_PRI_PCM_IBIT 0x200
+/* Clock ID for Primary PCM EBIT */
+#define Q6AFE_LPASS_CLK_ID_PRI_PCM_EBIT 0x201
+/* Clock ID for Secondary PCM IBIT */
+#define Q6AFE_LPASS_CLK_ID_SEC_PCM_IBIT 0x202
+/* Clock ID for Secondary PCM EBIT */
+#define Q6AFE_LPASS_CLK_ID_SEC_PCM_EBIT 0x203
+/* Clock ID for Tertiary PCM IBIT */
+#define Q6AFE_LPASS_CLK_ID_TER_PCM_IBIT 0x204
+/* Clock ID for Tertiary PCM EBIT */
+#define Q6AFE_LPASS_CLK_ID_TER_PCM_EBIT 0x205
+/* Clock ID for Quartery PCM IBIT */
+#define Q6AFE_LPASS_CLK_ID_QUAD_PCM_IBIT 0x206
+/* Clock ID for Quartery PCM EBIT */
+#define Q6AFE_LPASS_CLK_ID_QUAD_PCM_EBIT 0x207
+/* Clock ID for Quinary PCM IBIT */
+#define Q6AFE_LPASS_CLK_ID_QUIN_PCM_IBIT 0x208
+/* Clock ID for Quinary PCM EBIT */
+#define Q6AFE_LPASS_CLK_ID_QUIN_PCM_EBIT 0x209
+/* Clock ID for QUINARY PCM OSR */
+#define Q6AFE_LPASS_CLK_ID_QUI_PCM_OSR 0x20A
+
+/** Clock ID for Primary TDM IBIT */
+#define Q6AFE_LPASS_CLK_ID_PRI_TDM_IBIT 0x200
+/** Clock ID for Primary TDM EBIT */
+#define Q6AFE_LPASS_CLK_ID_PRI_TDM_EBIT 0x201
+/** Clock ID for Secondary TDM IBIT */
+#define Q6AFE_LPASS_CLK_ID_SEC_TDM_IBIT 0x202
+/** Clock ID for Secondary TDM EBIT */
+#define Q6AFE_LPASS_CLK_ID_SEC_TDM_EBIT 0x203
+/** Clock ID for Tertiary TDM IBIT */
+#define Q6AFE_LPASS_CLK_ID_TER_TDM_IBIT 0x204
+/** Clock ID for Tertiary TDM EBIT */
+#define Q6AFE_LPASS_CLK_ID_TER_TDM_EBIT 0x205
+/** Clock ID for Quartery TDM IBIT */
+#define Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT 0x206
+/** Clock ID for Quartery TDM EBIT */
+#define Q6AFE_LPASS_CLK_ID_QUAD_TDM_EBIT 0x207
+/** Clock ID for Quinary TDM IBIT */
+#define Q6AFE_LPASS_CLK_ID_QUIN_TDM_IBIT 0x208
+/** Clock ID for Quinary TDM EBIT */
+#define Q6AFE_LPASS_CLK_ID_QUIN_TDM_EBIT 0x209
+/** Clock ID for Quinary TDM OSR */
+#define Q6AFE_LPASS_CLK_ID_QUIN_TDM_OSR 0x20A
+
+/* Clock ID for MCLK1 */
+#define Q6AFE_LPASS_CLK_ID_MCLK_1 0x300
+/* Clock ID for MCLK2 */
+#define Q6AFE_LPASS_CLK_ID_MCLK_2 0x301
+/* Clock ID for MCLK3 */
+#define Q6AFE_LPASS_CLK_ID_MCLK_3 0x302
+/* Clock ID for MCLK4 */
+#define Q6AFE_LPASS_CLK_ID_MCLK_4 0x304
+/* Clock ID for Internal Digital Codec Core */
+#define Q6AFE_LPASS_CLK_ID_INTERNAL_DIGITAL_CODEC_CORE 0x303
+/* Clock ID for INT MCLK0 */
+#define Q6AFE_LPASS_CLK_ID_INT_MCLK_0 0x305
+/* Clock ID for INT MCLK1 */
+#define Q6AFE_LPASS_CLK_ID_INT_MCLK_1 0x306
+
+#define Q6AFE_LPASS_CLK_ID_WSA_CORE_MCLK 0x309
+#define Q6AFE_LPASS_CLK_ID_WSA_CORE_NPL_MCLK 0x30a
+#define Q6AFE_LPASS_CLK_ID_TX_CORE_MCLK 0x30c
+#define Q6AFE_LPASS_CLK_ID_TX_CORE_NPL_MCLK 0x30d
+#define Q6AFE_LPASS_CLK_ID_RX_CORE_MCLK 0x30e
+#define Q6AFE_LPASS_CLK_ID_RX_CORE_NPL_MCLK 0x30f
+#define Q6AFE_LPASS_CLK_ID_VA_CORE_MCLK 0x30b
+#define Q6AFE_LPASS_CLK_ID_VA_CORE_2X_MCLK 0x310
+
+#define Q6AFE_LPASS_CORE_AVTIMER_BLOCK 0x2
+#define Q6AFE_LPASS_CORE_HW_MACRO_BLOCK 0x3
+#define Q6AFE_LPASS_CORE_HW_DCODEC_BLOCK 0x4
+
+/* Clock attribute for invalid use (reserved for internal usage) */
+#define Q6AFE_LPASS_CLK_ATTRIBUTE_INVALID 0x0
+/* Clock attribute for no couple case */
+#define Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_NO 0x1
+/* Clock attribute for dividend couple case */
+#define Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_DIVIDEND 0x2
+/* Clock attribute for divisor couple case */
+#define Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_DIVISOR 0x3
+/* Clock attribute for invert and no couple case */
+#define Q6AFE_LPASS_CLK_ATTRIBUTE_INVERT_COUPLE_NO 0x4
+
+#define Q6AFE_CMAP_INVALID 0xFFFF
+
+struct q6afe_hdmi_cfg {
+ u16 datatype;
+ u16 channel_allocation;
+ u32 sample_rate;
+ u16 bit_width;
+};
+
+struct q6afe_slim_cfg {
+ u32 sample_rate;
+ u16 bit_width;
+ u16 data_format;
+ u16 num_channels;
+ u8 ch_mapping[AFE_MAX_CHAN_COUNT];
+};
+
+struct q6afe_i2s_cfg {
+ u32 sample_rate;
+ u16 bit_width;
+ u16 data_format;
+ u16 num_channels;
+ u32 sd_line_mask;
+ int fmt;
+};
+
+struct q6afe_tdm_cfg {
+ u16 num_channels;
+ u32 sample_rate;
+ u16 bit_width;
+ u16 data_format;
+ u16 sync_mode;
+ u16 sync_src;
+ u16 nslots_per_frame;
+ u16 slot_width;
+ u16 slot_mask;
+ u32 data_align_type;
+ u16 ch_mapping[AFE_MAX_CHAN_COUNT];
+};
+
+struct q6afe_cdc_dma_cfg {
+ u16 sample_rate;
+ u16 bit_width;
+ u16 data_format;
+ u16 num_channels;
+ u16 active_channels_mask;
+};
+
+/**
+ * struct q6afe_usb_cfg
+ * @cfg_minor_version: Minor version used for tracking USB audio device
+ * configuration.
+ * Supported values:
+ * AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG
+ * @sample_rate: Sampling rate of the port
+ * Supported values:
+ * AFE_PORT_SAMPLE_RATE_8K
+ * AFE_PORT_SAMPLE_RATE_11025
+ * AFE_PORT_SAMPLE_RATE_12K
+ * AFE_PORT_SAMPLE_RATE_16K
+ * AFE_PORT_SAMPLE_RATE_22050
+ * AFE_PORT_SAMPLE_RATE_24K
+ * AFE_PORT_SAMPLE_RATE_32K
+ * AFE_PORT_SAMPLE_RATE_44P1K
+ * AFE_PORT_SAMPLE_RATE_48K
+ * AFE_PORT_SAMPLE_RATE_96K
+ * AFE_PORT_SAMPLE_RATE_192K
+ * @bit_width: Bit width of the sample.
+ * Supported values: 16, 24
+ * @num_channels: Number of channels
+ * Supported values: 1, 2
+ **/
+struct q6afe_usb_cfg {
+ u32 cfg_minor_version;
+ u32 sample_rate;
+ u16 bit_width;
+ u16 num_channels;
+};
+
+struct q6afe_port_config {
+ struct q6afe_hdmi_cfg hdmi;
+ struct q6afe_slim_cfg slim;
+ struct q6afe_i2s_cfg i2s_cfg;
+ struct q6afe_tdm_cfg tdm;
+ struct q6afe_cdc_dma_cfg dma_cfg;
+ struct q6afe_usb_cfg usb_audio;
+};
+
+struct q6afe_port;
+
+struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id);
+int q6afe_port_start(struct q6afe_port *port);
+int q6afe_port_stop(struct q6afe_port *port);
+void q6afe_port_put(struct q6afe_port *port);
+int q6afe_get_port_id(int index);
+void q6afe_usb_port_prepare(struct q6afe_port *port,
+ struct q6afe_usb_cfg *cfg);
+void q6afe_hdmi_port_prepare(struct q6afe_port *port,
+ struct q6afe_hdmi_cfg *cfg);
+void q6afe_slim_port_prepare(struct q6afe_port *port,
+ struct q6afe_slim_cfg *cfg);
+int q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg);
+void q6afe_tdm_port_prepare(struct q6afe_port *port, struct q6afe_tdm_cfg *cfg);
+void q6afe_cdc_dma_port_prepare(struct q6afe_port *port,
+ struct q6afe_cdc_dma_cfg *cfg);
+
+int afe_port_send_usb_dev_param(struct q6afe_port *port, int cardidx, int pcmidx);
+int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id,
+ int clk_src, int clk_root,
+ unsigned int freq, int dir);
+int q6afe_set_lpass_clock(struct device *dev, int clk_id, int attri,
+ int clk_root, unsigned int freq);
+int q6afe_vote_lpass_core_hw(struct device *dev, uint32_t hw_block_id,
+ const char *client_name, uint32_t *client_handle);
+int q6afe_unvote_lpass_core_hw(struct device *dev, uint32_t hw_block_id,
+ uint32_t client_handle);
+#endif /* __Q6AFE_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c
new file mode 100644
index 000000000..033197bdd
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6apm-dai.c
@@ -0,0 +1,1061 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2021, Linaro Limited
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/spinlock.h>
+#include <sound/pcm.h>
+#include <asm/div64.h>
+#include <asm/dma.h>
+#include <linux/dma-mapping.h>
+#include <sound/pcm_params.h>
+#include "q6apm.h"
+
+#define DRV_NAME "q6apm-dai"
+#define POS_BUFFER_BYTES 4096
+
+#define PLAYBACK_MIN_NUM_PERIODS 2
+#define PLAYBACK_MAX_NUM_PERIODS 8
+#define PLAYBACK_MAX_PERIOD_SIZE 65536
+#define PLAYBACK_MIN_PERIOD_SIZE 128
+#define CAPTURE_MIN_NUM_PERIODS 2
+#define CAPTURE_MAX_NUM_PERIODS 8
+#define CAPTURE_MAX_PERIOD_SIZE 65536
+#define CAPTURE_MIN_PERIOD_SIZE 6144
+#define BUFFER_BYTES_MAX (PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE)
+#define BUFFER_BYTES_MIN (PLAYBACK_MIN_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE)
+#define COMPR_PLAYBACK_MAX_FRAGMENT_SIZE (128 * 1024)
+#define COMPR_PLAYBACK_MAX_NUM_FRAGMENTS (16 * 4)
+#define COMPR_PLAYBACK_MIN_FRAGMENT_SIZE (8 * 1024)
+#define COMPR_PLAYBACK_MIN_NUM_FRAGMENTS (4)
+#define SID_MASK_DEFAULT 0xF
+
+static const struct snd_compr_codec_caps q6apm_compr_caps = {
+ .num_descriptors = 1,
+ .descriptor[0].max_ch = 2,
+ .descriptor[0].sample_rates = { 8000, 11025, 12000, 16000, 22050,
+ 24000, 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000 },
+ .descriptor[0].num_sample_rates = 13,
+ .descriptor[0].bit_rate[0] = 320,
+ .descriptor[0].bit_rate[1] = 128,
+ .descriptor[0].num_bitrates = 2,
+ .descriptor[0].profiles = 0,
+ .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO,
+ .descriptor[0].formats = 0,
+};
+
+enum stream_state {
+ Q6APM_STREAM_IDLE = 0,
+ Q6APM_STREAM_STOPPED,
+ Q6APM_STREAM_RUNNING,
+};
+
+struct q6apm_dai_rtd {
+ struct snd_pcm_substream *substream;
+ struct snd_compr_stream *cstream;
+ struct snd_codec codec;
+ struct snd_compr_params codec_param;
+ struct snd_dma_buffer dma_buffer;
+ struct sh_mem_pull_push_mode_position_buffer *pos_buffer;
+ uint32_t last_pos_index;
+ phys_addr_t phys;
+ phys_addr_t pos_phys;
+ unsigned int pcm_size;
+ unsigned int push_pull_size;
+ unsigned int pcm_count;
+ unsigned int periods;
+ uint64_t bytes_sent;
+ uint64_t bytes_received;
+ uint64_t copied_total;
+ uint16_t bits_per_sample;
+ snd_pcm_uframes_t queue_ptr;
+ bool next_track;
+ enum stream_state state;
+ struct q6apm_graph *graph;
+ spinlock_t lock;
+ bool notify_on_drain;
+};
+
+struct q6apm_dai_data {
+ long long sid;
+};
+
+static const struct snd_pcm_hardware q6apm_dai_hardware_capture = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_NO_REWINDS | SNDRV_PCM_INFO_SYNC_APPLPTR |
+ SNDRV_PCM_INFO_BATCH),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE),
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 4,
+ .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,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware q6apm_dai_hardware_playback = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_NO_REWINDS | SNDRV_PCM_INFO_SYNC_APPLPTR |
+ SNDRV_PCM_INFO_BATCH),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE),
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 2,
+ .channels_max = 8,
+ .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,
+ .fifo_size = 0,
+};
+
+static void event_handler(uint32_t opcode, uint32_t token, void *payload, void *priv)
+{
+ struct q6apm_dai_rtd *prtd = priv;
+ struct snd_pcm_substream *substream = prtd->substream;
+
+ switch (opcode) {
+ case APM_CLIENT_EVENT_WATERMARK_EVENT:
+ snd_pcm_period_elapsed(substream);
+ break;
+ case APM_CLIENT_EVENT_CMD_EOS_DONE:
+ prtd->state = Q6APM_STREAM_STOPPED;
+ break;
+ case APM_CLIENT_EVENT_DATA_WRITE_DONE:
+ snd_pcm_period_elapsed(substream);
+
+ break;
+ case APM_CLIENT_EVENT_DATA_READ_DONE:
+ snd_pcm_period_elapsed(substream);
+ if (prtd->state == Q6APM_STREAM_RUNNING)
+ q6apm_read(prtd->graph);
+
+ break;
+ default:
+ break;
+ }
+}
+
+static void event_handler_compr(uint32_t opcode, uint32_t token,
+ void *payload, void *priv)
+{
+ struct q6apm_dai_rtd *prtd = priv;
+ struct snd_compr_stream *substream = prtd->cstream;
+ uint32_t wflags = 0;
+ uint64_t avail;
+ uint32_t bytes_written, bytes_to_write;
+ bool is_last_buffer = false;
+
+ guard(spinlock_irqsave)(&prtd->lock);
+ switch (opcode) {
+ case APM_CLIENT_EVENT_CMD_EOS_DONE:
+ if (prtd->notify_on_drain) {
+ snd_compr_drain_notify(prtd->cstream);
+ prtd->notify_on_drain = false;
+ } else {
+ prtd->state = Q6APM_STREAM_STOPPED;
+ }
+ break;
+ case APM_CLIENT_EVENT_DATA_WRITE_DONE:
+ bytes_written = token >> APM_WRITE_TOKEN_LEN_SHIFT;
+ prtd->copied_total += bytes_written;
+ snd_compr_fragment_elapsed(substream);
+
+ if (prtd->state != Q6APM_STREAM_RUNNING)
+ break;
+
+ avail = prtd->bytes_received - prtd->bytes_sent;
+
+ if (avail > prtd->pcm_count) {
+ bytes_to_write = prtd->pcm_count;
+ } else {
+ if (substream->partial_drain || prtd->notify_on_drain)
+ is_last_buffer = true;
+ bytes_to_write = avail;
+ }
+
+ if (bytes_to_write) {
+ if (substream->partial_drain && is_last_buffer)
+ wflags |= APM_LAST_BUFFER_FLAG;
+
+ q6apm_write_async(prtd->graph,
+ bytes_to_write, 0, 0, wflags);
+
+ prtd->bytes_sent += bytes_to_write;
+
+ if (prtd->notify_on_drain && is_last_buffer)
+ audioreach_shared_memory_send_eos(prtd->graph);
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+static int q6apm_dai_prepare(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+ struct audioreach_module_config cfg = {};
+ struct device *dev = component->dev;
+ struct q6apm_dai_data *pdata;
+ int ret;
+
+ pdata = snd_soc_component_get_drvdata(component);
+ if (!pdata)
+ return -EINVAL;
+
+ if (!prtd || !prtd->graph) {
+ dev_err(dev, "%s: private data null or audio client freed\n", __func__);
+ return -EINVAL;
+ }
+
+ cfg.direction = substream->stream;
+ cfg.sample_rate = runtime->rate;
+ cfg.num_channels = runtime->channels;
+ cfg.bit_width = prtd->bits_per_sample;
+ cfg.fmt = SND_AUDIOCODEC_PCM;
+ audioreach_set_default_channel_mapping(cfg.channel_map, runtime->channels);
+ if (prtd->state) {
+ /* clear the previous setup if any */
+ q6apm_graph_stop(prtd->graph);
+ q6apm_free_fragments(prtd->graph, substream->stream);
+ }
+
+ prtd->last_pos_index = 0;
+ prtd->pcm_count = snd_pcm_lib_period_bytes(substream);
+ if (q6apm_is_graph_in_push_pull_mode(prtd->graph)) {
+ if (prtd->pcm_size != prtd->push_pull_size) {
+ ret = q6apm_push_pull_config(prtd->graph, prtd->phys, prtd->pos_phys,
+ prtd->pcm_size);
+ if (ret < 0) {
+ dev_err(dev, "Push/Pull config failed rc = %d\n", ret);
+ return ret;
+ }
+
+ ret = q6apm_register_watermark_event(prtd->graph,
+ prtd->pcm_size / prtd->periods,
+ prtd->periods);
+ if (ret < 0) {
+ dev_err(dev, "WaterMark event config failed rc = %d\n", ret);
+ return ret;
+ }
+ prtd->push_pull_size = prtd->pcm_size;
+ }
+ } else {
+ ret = q6apm_alloc_fragments(prtd->graph, substream->stream, prtd->phys,
+ (prtd->pcm_size / prtd->periods), prtd->periods);
+ if (ret < 0) {
+ dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret);
+ return ret;
+ }
+
+ }
+
+ ret = q6apm_graph_media_format_pcm(prtd->graph, &cfg);
+ if (ret < 0) {
+ dev_err(dev, "%s: CMD Format block failed\n", __func__);
+ return ret;
+ }
+
+ /* rate and channels are sent to audio driver */
+ ret = q6apm_graph_media_format_shmem(prtd->graph, &cfg);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set media format %d\n", ret);
+ return ret;
+ }
+
+ ret = q6apm_graph_prepare(prtd->graph);
+ if (ret) {
+ dev_err(dev, "Failed to prepare Graph %d\n", ret);
+ return ret;
+ }
+
+ ret = q6apm_graph_start(prtd->graph);
+ if (ret) {
+ dev_err(dev, "Failed to Start Graph %d\n", ret);
+ return ret;
+ }
+ if (!q6apm_is_graph_in_push_pull_mode(prtd->graph)) {
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ int i;
+ /* Queue the buffers for Capture ONLY after graph is started */
+ for (i = 0; i < runtime->periods; i++)
+ q6apm_read(prtd->graph);
+ }
+ }
+
+ /* Now that graph as been prepared and started update the internal state accordingly */
+ prtd->state = Q6APM_STREAM_RUNNING;
+
+ return 0;
+}
+
+static int q6apm_dai_ack(struct snd_soc_component *component, struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+ int i, ret = 0, avail_periods;
+
+ if (q6apm_is_graph_in_push_pull_mode(prtd->graph))
+ return 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ avail_periods = (runtime->control->appl_ptr - prtd->queue_ptr)/runtime->period_size;
+ for (i = 0; i < avail_periods; i++) {
+ ret = q6apm_write_async(prtd->graph, prtd->pcm_count, 0, 0, NO_TIMESTAMP);
+ if (ret < 0) {
+ dev_err(component->dev, "Error queuing playback buffer %d\n", ret);
+ return ret;
+ }
+ prtd->queue_ptr += runtime->period_size;
+ }
+ }
+
+ return ret;
+}
+
+static int q6apm_dai_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ /* TODO support be handled via SoftPause Module */
+ prtd->state = Q6APM_STREAM_STOPPED;
+ prtd->queue_ptr = 0;
+ prtd->last_pos_index = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int q6apm_dai_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_prtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_prtd, 0);
+ struct device *dev = component->dev;
+ struct q6apm_dai_data *pdata;
+ struct q6apm_dai_rtd *prtd;
+ int graph_id, ret;
+
+ graph_id = cpu_dai->driver->id;
+
+ pdata = snd_soc_component_get_drvdata(component);
+ if (!pdata) {
+ dev_err(dev, "Drv data not found ..\n");
+ return -EINVAL;
+ }
+
+ prtd = kzalloc_obj(*prtd);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&prtd->lock);
+ prtd->substream = substream;
+ prtd->graph = q6apm_graph_open(dev, event_handler, prtd, graph_id, substream->stream);
+ if (IS_ERR(prtd->graph)) {
+ dev_err(dev, "%s: Could not allocate memory\n", __func__);
+ ret = PTR_ERR(prtd->graph);
+ goto err;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ runtime->hw = q6apm_dai_hardware_playback;
+ else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ runtime->hw = q6apm_dai_hardware_capture;
+
+ /* Ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0) {
+ dev_err(dev, "snd_pcm_hw_constraint_integer failed\n");
+ goto err;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ BUFFER_BYTES_MIN, BUFFER_BYTES_MAX);
+ if (ret < 0) {
+ dev_err(dev, "constraint for buffer bytes min max ret = %d\n", ret);
+ goto err;
+ }
+ }
+
+ /* setup 10ms latency to accommodate DSP restrictions */
+ ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 480);
+ if (ret < 0) {
+ dev_err(dev, "constraint for period bytes step ret = %d\n", ret);
+ goto err;
+ }
+
+ ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 480);
+ if (ret < 0) {
+ dev_err(dev, "constraint for buffer bytes step ret = %d\n", ret);
+ goto err;
+ }
+
+ runtime->private_data = prtd;
+ runtime->dma_bytes = BUFFER_BYTES_MAX;
+ if (pdata->sid < 0)
+ prtd->phys = substream->dma_buffer.addr;
+ else
+ prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32);
+
+ if (q6apm_is_graph_in_push_pull_mode(prtd->graph)) {
+ void *pos_buffer;
+
+ prtd->pos_phys = prtd->phys + BUFFER_BYTES_MAX;
+ pos_buffer = (void *)(substream->dma_buffer.area + BUFFER_BYTES_MAX);
+ prtd->pos_buffer = (struct sh_mem_pull_push_mode_position_buffer *)(pos_buffer);
+ }
+
+ return 0;
+err:
+ kfree(prtd);
+
+ return ret;
+}
+
+static int q6apm_dai_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+
+ if (prtd->state) {
+ /* only stop graph that is started */
+ q6apm_graph_stop(prtd->graph);
+ q6apm_free_fragments(prtd->graph, substream->stream);
+ }
+
+ q6apm_graph_close(prtd->graph);
+ prtd->graph = NULL;
+ kfree(prtd);
+ runtime->private_data = NULL;
+
+ return 0;
+}
+
+static snd_pcm_uframes_t q6apm_dai_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+ snd_pcm_uframes_t ptr;
+
+ if (q6apm_is_graph_in_push_pull_mode(prtd->graph)) {
+ int retries = 10;
+ uint32_t index, fc1, fc2;
+
+ /* index is valid if frame_counter does not change while reading. */
+ do {
+ fc1 = READ_ONCE(prtd->pos_buffer->frame_counter);
+ index = READ_ONCE(prtd->pos_buffer->index);
+ fc2 = READ_ONCE(prtd->pos_buffer->frame_counter);
+ } while (fc1 != fc2 && --retries);
+
+ if (fc1 != fc2)
+ index = prtd->last_pos_index;
+ else
+ prtd->last_pos_index = index;
+
+ ptr = bytes_to_frames(runtime, index);
+ return ptr;
+ }
+ ptr = q6apm_get_hw_pointer(prtd->graph, substream->stream) * runtime->period_size;
+ if (ptr)
+ return ptr - 1;
+
+ return 0;
+}
+
+static int q6apm_dai_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+
+ prtd->pcm_size = params_buffer_bytes(params);
+ prtd->periods = params_periods(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ prtd->bits_per_sample = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ prtd->bits_per_sample = 24;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int q6apm_dai_memory_map(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ int graph_id, bool is_push_pull)
+{
+ struct q6apm_dai_data *pdata;
+ struct device *dev = component->dev;
+ phys_addr_t phys;
+ int ret;
+
+ pdata = snd_soc_component_get_drvdata(component);
+ if (!pdata) {
+ dev_err(component->dev, "Drv data not found ..\n");
+ return -EINVAL;
+ }
+
+ if (pdata->sid < 0)
+ phys = substream->dma_buffer.addr;
+ else
+ phys = substream->dma_buffer.addr | (pdata->sid << 32);
+
+ ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, BUFFER_BYTES_MAX);
+ if (ret < 0)
+ dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret);
+
+ if (is_push_pull) {
+ if (pdata->sid < 0)
+ phys = substream->dma_buffer.addr + BUFFER_BYTES_MAX;
+ else
+ phys = (substream->dma_buffer.addr + BUFFER_BYTES_MAX) | (pdata->sid << 32);
+
+ ret = q6apm_map_pos_buffer(dev, graph_id, phys, POS_BUFFER_BYTES);
+ if (ret < 0)
+ dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret);
+ } else {
+
+ }
+
+ return ret;
+}
+
+static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_pcm *pcm = rtd->pcm;
+ /*
+ * Allocate one extra page as a workaround for a DSP bug where 32-bit
+ * address arithmetic can overflow when the buffer is placed near the
+ * end of the addressable range.
+ */
+ int size = BUFFER_BYTES_MAX + PAGE_SIZE;
+ int graph_id, ret;
+ bool is_push_pull;
+ struct snd_pcm_substream *substream = NULL;
+
+ graph_id = cpu_dai->driver->id;
+
+ /* Note: DSP backend dais are uni-directional ONLY(either playback or capture) */
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
+ substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ else if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
+ substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+
+
+ if (substream) {
+ is_push_pull = q6apm_is_graph_in_push_pull_mode_from_id(component->dev,
+ graph_id,
+ substream->stream);
+ if (is_push_pull)
+ size += POS_BUFFER_BYTES;
+
+ ret = snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, component->dev, size);
+ if (ret)
+ return ret;
+
+ ret = q6apm_dai_memory_map(component, substream, graph_id, is_push_pull);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void q6apm_dai_memory_unmap(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *soc_prtd;
+ struct snd_soc_dai *cpu_dai;
+ int graph_id;
+
+ soc_prtd = snd_soc_substream_to_rtd(substream);
+ if (!soc_prtd)
+ return;
+
+ cpu_dai = snd_soc_rtd_to_cpu(soc_prtd, 0);
+ if (!cpu_dai)
+ return;
+
+ graph_id = cpu_dai->driver->id;
+ q6apm_unmap_memory_fixed_region(component->dev, graph_id);
+
+ if (q6apm_is_graph_in_push_pull_mode_from_id(component->dev, graph_id, substream->stream))
+ q6apm_unmap_pos_buffer(component->dev, graph_id);
+}
+
+static void q6apm_dai_pcm_free(struct snd_soc_component *component, struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+
+ substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+ if (substream)
+ q6apm_dai_memory_unmap(component, substream);
+
+ substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ if (substream)
+ q6apm_dai_memory_unmap(component, substream);
+}
+
+static int q6apm_dai_compr_open(struct snd_soc_component *component,
+ struct snd_compr_stream *stream)
+{
+ struct snd_soc_pcm_runtime *rtd = stream->private_data;
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6apm_dai_rtd *prtd;
+ struct q6apm_dai_data *pdata;
+ struct device *dev = component->dev;
+ int ret, size;
+ int graph_id;
+
+ graph_id = cpu_dai->driver->id;
+ pdata = snd_soc_component_get_drvdata(component);
+ if (!pdata)
+ return -EINVAL;
+
+ prtd = kzalloc_obj(*prtd);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ prtd->cstream = stream;
+ prtd->graph = q6apm_graph_open(dev, event_handler_compr, prtd, graph_id,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (IS_ERR(prtd->graph)) {
+ ret = PTR_ERR(prtd->graph);
+ kfree(prtd);
+ return ret;
+ }
+
+ runtime->private_data = prtd;
+ runtime->dma_bytes = BUFFER_BYTES_MAX;
+ size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE * COMPR_PLAYBACK_MAX_NUM_FRAGMENTS;
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, &prtd->dma_buffer);
+ if (ret)
+ return ret;
+
+ if (pdata->sid < 0)
+ prtd->phys = prtd->dma_buffer.addr;
+ else
+ prtd->phys = prtd->dma_buffer.addr | (pdata->sid << 32);
+
+ snd_compr_set_runtime_buffer(stream, &prtd->dma_buffer);
+ spin_lock_init(&prtd->lock);
+
+ q6apm_enable_compress_module(dev, prtd->graph, true);
+ return 0;
+}
+
+static int q6apm_dai_compr_free(struct snd_soc_component *component,
+ struct snd_compr_stream *stream)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+
+ q6apm_graph_stop(prtd->graph);
+ q6apm_free_fragments(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK);
+ q6apm_unmap_memory_fixed_region(component->dev, prtd->graph->id);
+ q6apm_graph_close(prtd->graph);
+ snd_dma_free_pages(&prtd->dma_buffer);
+ prtd->graph = NULL;
+ kfree(prtd);
+ runtime->private_data = NULL;
+
+ return 0;
+}
+
+static int q6apm_dai_compr_get_caps(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct snd_compr_caps *caps)
+{
+ caps->direction = SND_COMPRESS_PLAYBACK;
+ caps->min_fragment_size = COMPR_PLAYBACK_MIN_FRAGMENT_SIZE;
+ caps->max_fragment_size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE;
+ caps->min_fragments = COMPR_PLAYBACK_MIN_NUM_FRAGMENTS;
+ caps->max_fragments = COMPR_PLAYBACK_MAX_NUM_FRAGMENTS;
+ caps->num_codecs = 4;
+ caps->codecs[0] = SND_AUDIOCODEC_MP3;
+ caps->codecs[1] = SND_AUDIOCODEC_AAC;
+ caps->codecs[2] = SND_AUDIOCODEC_FLAC;
+ caps->codecs[3] = SND_AUDIOCODEC_OPUS_RAW;
+
+ return 0;
+}
+
+static int q6apm_dai_compr_get_codec_caps(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct snd_compr_codec_caps *codec)
+{
+ switch (codec->codec) {
+ case SND_AUDIOCODEC_MP3:
+ *codec = q6apm_compr_caps;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int q6apm_dai_compr_pointer(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct snd_compr_tstamp64 *tstamp)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+ uint64_t temp_copied_total;
+
+ guard(spinlock_irqsave)(&prtd->lock);
+ tstamp->copied_total = prtd->copied_total;
+ temp_copied_total = tstamp->copied_total;
+ tstamp->byte_offset = do_div(temp_copied_total, prtd->pcm_size);
+
+ return 0;
+}
+
+static int q6apm_dai_compr_trigger(struct snd_soc_component *component,
+ struct snd_compr_stream *stream, int cmd)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = q6apm_write_async(prtd->graph, prtd->pcm_count, 0, 0, NO_TIMESTAMP);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ case SND_COMPR_TRIGGER_NEXT_TRACK:
+ prtd->next_track = true;
+ break;
+ case SND_COMPR_TRIGGER_DRAIN:
+ case SND_COMPR_TRIGGER_PARTIAL_DRAIN:
+ prtd->notify_on_drain = true;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int q6apm_dai_compr_ack(struct snd_soc_component *component, struct snd_compr_stream *stream,
+ size_t count)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+
+ guard(spinlock_irqsave)(&prtd->lock);
+ prtd->bytes_received += count;
+
+ return count;
+}
+
+static int q6apm_dai_compr_set_params(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct snd_compr_params *params)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+ struct q6apm_dai_data *pdata;
+ struct audioreach_module_config cfg = {};
+ struct snd_codec *codec = &params->codec;
+ int dir = stream->direction;
+ int ret;
+
+ pdata = snd_soc_component_get_drvdata(component);
+ if (!pdata)
+ return -EINVAL;
+
+ prtd->periods = runtime->fragments;
+ prtd->pcm_count = runtime->fragment_size;
+ prtd->pcm_size = runtime->fragments * runtime->fragment_size;
+ prtd->bits_per_sample = 16;
+
+ if (prtd->next_track != true) {
+ memcpy(&prtd->codec, codec, sizeof(*codec));
+
+ ret = q6apm_set_real_module_id(component->dev, prtd->graph, codec->id);
+ if (ret)
+ return ret;
+
+ cfg.direction = dir;
+ cfg.sample_rate = codec->sample_rate;
+ cfg.num_channels = 2;
+ cfg.bit_width = prtd->bits_per_sample;
+ cfg.fmt = codec->id;
+ audioreach_set_default_channel_mapping(cfg.channel_map,
+ cfg.num_channels);
+ memcpy(&cfg.codec, codec, sizeof(*codec));
+
+ ret = q6apm_graph_media_format_shmem(prtd->graph, &cfg);
+ if (ret < 0)
+ return ret;
+
+ ret = q6apm_graph_media_format_pcm(prtd->graph, &cfg);
+ if (ret)
+ return ret;
+
+ ret = q6apm_alloc_fragments(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK,
+ prtd->phys, (prtd->pcm_size / prtd->periods),
+ prtd->periods);
+ if (ret < 0)
+ return -ENOMEM;
+
+ ret = q6apm_graph_prepare(prtd->graph);
+ if (ret)
+ return ret;
+
+ ret = q6apm_graph_start(prtd->graph);
+ if (ret)
+ return ret;
+
+ } else {
+ cfg.direction = dir;
+ cfg.sample_rate = codec->sample_rate;
+ cfg.num_channels = 2;
+ cfg.bit_width = prtd->bits_per_sample;
+ cfg.fmt = codec->id;
+ memcpy(&cfg.codec, codec, sizeof(*codec));
+
+ ret = audioreach_compr_set_param(prtd->graph, &cfg);
+ if (ret < 0)
+ return ret;
+ }
+ prtd->state = Q6APM_STREAM_RUNNING;
+
+ return 0;
+}
+
+static int q6apm_dai_compr_set_metadata(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct snd_compr_metadata *metadata)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+ int ret = 0;
+
+ switch (metadata->key) {
+ case SNDRV_COMPRESS_ENCODER_PADDING:
+ q6apm_remove_trailing_silence(component->dev, prtd->graph,
+ metadata->value[0]);
+ break;
+ case SNDRV_COMPRESS_ENCODER_DELAY:
+ q6apm_remove_initial_silence(component->dev, prtd->graph,
+ metadata->value[0]);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int q6apm_dai_compr_mmap(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct vm_area_struct *vma)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+ struct device *dev = component->dev;
+
+ return dma_mmap_coherent(dev, vma, prtd->dma_buffer.area, prtd->dma_buffer.addr,
+ prtd->dma_buffer.bytes);
+}
+
+static int q6apm_compr_copy(struct snd_soc_component *component,
+ struct snd_compr_stream *stream, char __user *buf,
+ size_t count)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+ void *dstn;
+ size_t copy;
+ u32 wflags = 0;
+ u32 app_pointer;
+ uint64_t bytes_received;
+ uint64_t temp_bytes_received;
+ uint32_t bytes_to_write;
+ uint64_t avail, bytes_in_flight = 0;
+
+ bytes_received = prtd->bytes_received;
+ temp_bytes_received = bytes_received;
+
+ /**
+ * Make sure that next track data pointer is aligned at 32 bit boundary
+ * This is a Mandatory requirement from DSP data buffers alignment
+ */
+ if (prtd->next_track) {
+ bytes_received = ALIGN(prtd->bytes_received, prtd->pcm_count);
+ temp_bytes_received = bytes_received;
+ }
+
+ app_pointer = do_div(temp_bytes_received, prtd->pcm_size);
+ dstn = prtd->dma_buffer.area + app_pointer;
+
+ if (count < prtd->pcm_size - app_pointer) {
+ if (copy_from_user(dstn, buf, count))
+ return -EFAULT;
+ } else {
+ copy = prtd->pcm_size - app_pointer;
+ if (copy_from_user(dstn, buf, copy))
+ return -EFAULT;
+ if (copy_from_user(prtd->dma_buffer.area, buf + copy, count - copy))
+ return -EFAULT;
+ }
+
+ guard(spinlock_irqsave)(&prtd->lock);
+ bytes_in_flight = prtd->bytes_received - prtd->copied_total;
+
+ if (prtd->next_track) {
+ prtd->next_track = false;
+ prtd->copied_total = ALIGN(prtd->copied_total, prtd->pcm_count);
+ prtd->bytes_sent = ALIGN(prtd->bytes_sent, prtd->pcm_count);
+ }
+
+ prtd->bytes_received = bytes_received + count;
+
+ /* Kick off the data to dsp if its starving!! */
+ if (prtd->state == Q6APM_STREAM_RUNNING && (bytes_in_flight == 0)) {
+ bytes_to_write = prtd->pcm_count;
+ avail = prtd->bytes_received - prtd->bytes_sent;
+
+ if (avail < prtd->pcm_count)
+ bytes_to_write = avail;
+
+ q6apm_write_async(prtd->graph, bytes_to_write, 0, 0, wflags);
+ prtd->bytes_sent += bytes_to_write;
+ }
+
+ return count;
+}
+
+static const struct snd_compress_ops q6apm_dai_compress_ops = {
+ .open = q6apm_dai_compr_open,
+ .free = q6apm_dai_compr_free,
+ .get_caps = q6apm_dai_compr_get_caps,
+ .get_codec_caps = q6apm_dai_compr_get_codec_caps,
+ .pointer = q6apm_dai_compr_pointer,
+ .trigger = q6apm_dai_compr_trigger,
+ .ack = q6apm_dai_compr_ack,
+ .set_params = q6apm_dai_compr_set_params,
+ .set_metadata = q6apm_dai_compr_set_metadata,
+ .mmap = q6apm_dai_compr_mmap,
+ .copy = q6apm_compr_copy,
+};
+
+static const struct snd_soc_component_driver q6apm_fe_dai_component = {
+ .name = DRV_NAME,
+ .open = q6apm_dai_open,
+ .close = q6apm_dai_close,
+ .prepare = q6apm_dai_prepare,
+ .pcm_new = q6apm_dai_pcm_new,
+ .pcm_free = q6apm_dai_pcm_free,
+ .hw_params = q6apm_dai_hw_params,
+ .pointer = q6apm_dai_pointer,
+ .trigger = q6apm_dai_trigger,
+ .ack = q6apm_dai_ack,
+ .compress_ops = &q6apm_dai_compress_ops,
+ .use_dai_pcm_id = true,
+ .remove_order = SND_SOC_COMP_ORDER_EARLY,
+};
+
+static int q6apm_dai_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+ struct q6apm_dai_data *pdata;
+ struct of_phandle_args args;
+ int rc;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
+ if (rc < 0)
+ pdata->sid = -1;
+ else
+ pdata->sid = args.args[0] & SID_MASK_DEFAULT;
+
+ dev_set_drvdata(dev, pdata);
+
+ return devm_snd_soc_register_component(dev, &q6apm_fe_dai_component, NULL, 0);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id q6apm_dai_device_id[] = {
+ { .compatible = "qcom,q6apm-dais" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6apm_dai_device_id);
+#endif
+
+static struct platform_driver q6apm_dai_platform_driver = {
+ .driver = {
+ .name = "q6apm-dai",
+ .of_match_table = of_match_ptr(q6apm_dai_device_id),
+ },
+ .probe = q6apm_dai_probe,
+};
+module_platform_driver(q6apm_dai_platform_driver);
+
+MODULE_DESCRIPTION("Q6APM dai driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c
new file mode 100644
index 000000000..e204fd59e
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c
@@ -0,0 +1,565 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2021, Linaro Limited
+
+#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include "q6dsp-lpass-ports.h"
+#include "q6dsp-common.h"
+#include "audioreach.h"
+#include "q6apm.h"
+#include "q6prm.h"
+
+#define AUDIOREACH_BE_PCM_BASE 16
+
+struct q6apm_dai_priv_data {
+ struct clk *mclk;
+ struct clk *bclk;
+ bool mclk_enabled, bclk_enabled;
+};
+
+struct q6apm_lpass_dai_data {
+ struct q6apm_graph *graph[APM_PORT_MAX];
+ bool is_port_started[APM_PORT_MAX];
+ struct audioreach_module_config module_config[APM_PORT_MAX];
+ struct q6apm_dai_priv_data priv[APM_PORT_MAX];
+};
+
+static void q6apm_lpass_dai_disable_clocks(struct q6apm_lpass_dai_data *dai_data, int id)
+{
+ if (dai_data->priv[id].mclk_enabled) {
+ clk_disable_unprepare(dai_data->priv[id].mclk);
+ dai_data->priv[id].mclk_enabled = false;
+ }
+
+ if (dai_data->priv[id].bclk_enabled) {
+ clk_disable_unprepare(dai_data->priv[id].bclk);
+ dai_data->priv[id].bclk_enabled = false;
+ }
+}
+
+static void q6apm_lpass_dai_put_clocks(struct q6apm_lpass_dai_data *dai_data)
+{
+ int i;
+
+ for (i = 0; i < APM_PORT_MAX; i++) {
+ q6apm_lpass_dai_disable_clocks(dai_data, i);
+
+ if (dai_data->priv[i].mclk) {
+ clk_put(dai_data->priv[i].mclk);
+ dai_data->priv[i].mclk = NULL;
+ }
+ if (dai_data->priv[i].bclk) {
+ clk_put(dai_data->priv[i].bclk);
+ dai_data->priv[i].bclk = NULL;
+ }
+ }
+}
+
+static int q6dma_set_channel_map(struct snd_soc_dai *dai,
+ unsigned int tx_num,
+ const unsigned int *tx_ch_mask,
+ unsigned int rx_num,
+ const unsigned int *rx_ch_mask)
+{
+
+ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct audioreach_module_config *cfg = &dai_data->module_config[dai->id];
+ int i;
+
+ switch (dai->id) {
+ case WSA_CODEC_DMA_TX_0:
+ case WSA_CODEC_DMA_TX_1:
+ case WSA_CODEC_DMA_TX_2:
+ case VA_CODEC_DMA_TX_0:
+ case VA_CODEC_DMA_TX_1:
+ case VA_CODEC_DMA_TX_2:
+ case TX_CODEC_DMA_TX_0:
+ case TX_CODEC_DMA_TX_1:
+ case TX_CODEC_DMA_TX_2:
+ case TX_CODEC_DMA_TX_3:
+ case TX_CODEC_DMA_TX_4:
+ case TX_CODEC_DMA_TX_5:
+ if (!tx_ch_mask) {
+ dev_err(dai->dev, "tx slot not found\n");
+ return -EINVAL;
+ }
+
+ if (tx_num > AR_PCM_MAX_NUM_CHANNEL) {
+ dev_err(dai->dev, "invalid tx num %d\n",
+ tx_num);
+ return -EINVAL;
+ }
+ for (i = 0; i < tx_num; i++)
+ cfg->channel_map[i] = tx_ch_mask[i];
+
+ break;
+ case WSA_CODEC_DMA_RX_0:
+ case WSA_CODEC_DMA_RX_1:
+ case RX_CODEC_DMA_RX_0:
+ case RX_CODEC_DMA_RX_1:
+ case RX_CODEC_DMA_RX_2:
+ case RX_CODEC_DMA_RX_3:
+ case RX_CODEC_DMA_RX_4:
+ case RX_CODEC_DMA_RX_5:
+ case RX_CODEC_DMA_RX_6:
+ case RX_CODEC_DMA_RX_7:
+ /* rx */
+ if (!rx_ch_mask) {
+ dev_err(dai->dev, "rx slot not found\n");
+ return -EINVAL;
+ }
+ if (rx_num > APM_PORT_MAX_AUDIO_CHAN_CNT) {
+ dev_err(dai->dev, "invalid rx num %d\n",
+ rx_num);
+ return -EINVAL;
+ }
+ for (i = 0; i < rx_num; i++)
+ cfg->channel_map[i] = rx_ch_mask[i];
+
+ break;
+ default:
+ dev_err(dai->dev, "%s: invalid dai id 0x%x\n",
+ __func__, dai->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int q6hdmi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct audioreach_module_config *cfg = &dai_data->module_config[dai->id];
+ int channels = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS)->max;
+ int ret;
+
+ cfg->bit_width = params_width(params);
+ cfg->sample_rate = params_rate(params);
+ cfg->num_channels = channels;
+ audioreach_set_default_channel_mapping(cfg->channel_map, channels);
+
+ switch (dai->id) {
+ case DISPLAY_PORT_RX_0:
+ cfg->dp_idx = 0;
+ break;
+ case DISPLAY_PORT_RX_1 ... DISPLAY_PORT_RX_7:
+ cfg->dp_idx = dai->id - DISPLAY_PORT_RX_1 + 1;
+ break;
+ }
+
+ ret = q6dsp_get_channel_allocation(channels);
+ if (ret < 0)
+ return ret;
+
+ cfg->channel_allocation = ret;
+
+ return 0;
+}
+
+static int q6dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct audioreach_module_config *cfg = &dai_data->module_config[dai->id];
+ int channels = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS)->max;
+
+ cfg->bit_width = params_width(params);
+ cfg->sample_rate = params_rate(params);
+ cfg->num_channels = channels;
+ audioreach_set_default_channel_mapping(cfg->channel_map, channels);
+
+ return 0;
+}
+
+static void q6apm_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ int rc;
+
+ if (dai_data->is_port_started[dai->id]) {
+ rc = q6apm_graph_stop(dai_data->graph[dai->id]);
+ dai_data->is_port_started[dai->id] = false;
+ if (rc < 0)
+ dev_err(dai->dev, "failed to stop APM port (%d)\n", rc);
+ }
+
+ if (dai_data->graph[dai->id]) {
+ q6apm_graph_close(dai_data->graph[dai->id]);
+ dai_data->graph[dai->id] = NULL;
+ }
+}
+
+static int q6apm_lpass_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!dai_data->is_port_started[dai->id]) {
+ ret = q6apm_graph_start(dai_data->graph[dai->id]);
+ if (ret < 0)
+ dev_err(dai->dev, "Failed to start APM port %d\n", dai->id);
+ else
+ dai_data->is_port_started[dai->id] = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct audioreach_module_config *cfg = &dai_data->module_config[dai->id];
+ struct q6apm_graph *graph;
+ int graph_id = dai->id;
+ int rc;
+
+ if (dai_data->is_port_started[dai->id]) {
+ q6apm_graph_stop(dai_data->graph[dai->id]);
+ dai_data->is_port_started[dai->id] = false;
+
+ }
+
+ /**
+ * It is recommend to load DSP with source graph first and then sink
+ * graph, so sequence for playback and capture will be different
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && dai_data->graph[dai->id] == NULL) {
+ graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id, substream->stream);
+ if (IS_ERR(graph)) {
+ dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id);
+ rc = PTR_ERR(graph);
+ return rc;
+ }
+ dai_data->graph[graph_id] = graph;
+ }
+
+ cfg->direction = substream->stream;
+ rc = q6apm_graph_media_format_pcm(dai_data->graph[dai->id], cfg);
+ if (rc) {
+ dev_err(dai->dev, "Failed to set media format %d\n", rc);
+ goto err;
+ }
+
+ rc = q6apm_graph_prepare(dai_data->graph[dai->id]);
+ if (rc) {
+ dev_err(dai->dev, "Failed to prepare Graph %d\n", rc);
+ goto err;
+ }
+ return 0;
+err:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ q6apm_graph_close(dai_data->graph[dai->id]);
+ dai_data->graph[dai->id] = NULL;
+ }
+ return rc;
+}
+
+static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6apm_graph *graph;
+ int graph_id = dai->id;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id, substream->stream);
+ if (IS_ERR(graph)) {
+ dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id);
+ return PTR_ERR(graph);
+ }
+ dai_data->graph[graph_id] = graph;
+ }
+
+ return 0;
+}
+
+static int q6i2s_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ return q6apm_lpass_dai_startup(substream, dai);
+}
+
+static void q6i2s_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
+
+ q6apm_lpass_dai_shutdown(substream, dai);
+ q6apm_lpass_dai_disable_clocks(dai_data, dai->id);
+}
+
+static int q6i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir)
+{
+ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct clk *sysclk = NULL;
+ bool *enabled = NULL;
+ int ret = 0;
+
+ switch (clk_id) {
+ case LPAIF_MI2S_MCLK:
+ sysclk = dai_data->priv[dai->id].mclk;
+ enabled = &dai_data->priv[dai->id].mclk_enabled;
+ break;
+ case LPAIF_MI2S_BCLK:
+ sysclk = dai_data->priv[dai->id].bclk;
+ enabled = &dai_data->priv[dai->id].bclk_enabled;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (sysclk) {
+ ret = clk_set_rate(sysclk, freq);
+ if (ret) {
+ dev_err(dai->dev, "Error, Unable to set rate (%d) for sysclk %d\n",
+ freq, clk_id);
+ return ret;
+ }
+
+ if (*enabled)
+ return 0;
+
+ ret = clk_prepare_enable(sysclk);
+ if (ret) {
+ dev_err(dai->dev, "Error, Unable to prepare (%d) sysclk\n", clk_id);
+ return ret;
+ }
+
+ *enabled = true;
+ }
+
+ return ret;
+}
+
+static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct audioreach_module_config *cfg = &dai_data->module_config[dai->id];
+
+ cfg->fmt = fmt;
+
+ return 0;
+}
+
+static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask,
+ unsigned int rx_mask,
+ int slots, int slot_width)
+{
+ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct audioreach_module_config *cfg = &dai_data->module_config[dai->id];
+ unsigned int cap_mask, slot_mask;
+
+ if (slot_width != 16 && slot_width != 32) {
+ dev_err(dai->dev, "%s: invalid slot_width %d\n", __func__, slot_width);
+ return -EINVAL;
+ }
+
+ switch (slots) {
+ case 2:
+ case 4:
+ case 8:
+ case 16:
+ cap_mask = GENMASK(slots - 1, 0);
+ break;
+ default:
+ dev_err(dai->dev, "%s: invalid slots %d\n", __func__, slots);
+ return -EINVAL;
+ }
+
+ switch (dai->id) {
+ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7:
+ slot_mask = (dai->id & 0x1) ? tx_mask : rx_mask;
+ if (slot_mask & ~cap_mask) {
+ dev_err(dai->dev, "%s: invalid slot mask 0x%x for %d slots\n",
+ __func__, slot_mask, slots);
+ return -EINVAL;
+ }
+
+ cfg->nslots_per_frame = slots;
+ cfg->slot_width = slot_width;
+ cfg->slot_mask = slot_mask;
+ break;
+ default:
+ dev_err(dai->dev, "%s: invalid dai id 0x%x\n", __func__, dai->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops q6dma_ops = {
+ .prepare = q6apm_lpass_dai_prepare,
+ .startup = q6apm_lpass_dai_startup,
+ .shutdown = q6apm_lpass_dai_shutdown,
+ .set_channel_map = q6dma_set_channel_map,
+ .hw_params = q6dma_hw_params,
+ .trigger = q6apm_lpass_dai_trigger,
+};
+
+static const struct snd_soc_dai_ops q6i2s_ops = {
+ .prepare = q6apm_lpass_dai_prepare,
+ .startup = q6i2s_dai_startup,
+ .shutdown = q6i2s_lpass_dai_shutdown,
+ .set_channel_map = q6dma_set_channel_map,
+ .hw_params = q6dma_hw_params,
+ .set_fmt = q6i2s_set_fmt,
+ .set_sysclk = q6i2s_set_sysclk,
+ .trigger = q6apm_lpass_dai_trigger,
+};
+
+static const struct snd_soc_dai_ops q6hdmi_ops = {
+ .prepare = q6apm_lpass_dai_prepare,
+ .startup = q6apm_lpass_dai_startup,
+ .shutdown = q6apm_lpass_dai_shutdown,
+ .hw_params = q6hdmi_hw_params,
+ .set_fmt = q6i2s_set_fmt,
+ .trigger = q6apm_lpass_dai_trigger,
+};
+
+static const struct snd_soc_dai_ops q6tdm_ops = {
+ .prepare = q6apm_lpass_dai_prepare,
+ .startup = q6apm_lpass_dai_startup,
+ .shutdown = q6i2s_lpass_dai_shutdown,
+ .set_tdm_slot = q6tdm_set_tdm_slot,
+ .hw_params = q6dma_hw_params,
+ .set_fmt = q6i2s_set_fmt,
+ .set_sysclk = q6i2s_set_sysclk,
+ .trigger = q6apm_lpass_dai_trigger,
+};
+
+static const struct snd_soc_component_driver q6apm_lpass_dai_component = {
+ .name = "q6apm-be-dai-component",
+ .of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name,
+ .be_pcm_base = AUDIOREACH_BE_PCM_BASE,
+ .use_dai_pcm_id = true,
+ .remove_order = SND_SOC_COMP_ORDER_FIRST,
+};
+
+static int of_q6apm_parse_dai_data(struct device *dev,
+ struct q6apm_lpass_dai_data *data)
+{
+ int ret;
+
+ for_each_child_of_node_scoped(dev->of_node, node) {
+ struct q6apm_dai_priv_data *priv;
+ int id;
+
+ ret = of_property_read_u32(node, "reg", &id);
+ if (ret || id < 0 || id >= APM_PORT_MAX) {
+ dev_err(dev, "valid dai id not found:%d\n", ret);
+ continue;
+ }
+
+ switch (id) {
+ /* MI2S specific properties */
+ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX:
+ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX:
+ case SENARY_MI2S_RX ... SENARY_MI2S_TX:
+ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7:
+ priv = &data->priv[id];
+ priv->mclk = of_clk_get_by_name(node, "mclk");
+ if (IS_ERR(priv->mclk)) {
+ int err = PTR_ERR(priv->mclk);
+
+ priv->mclk = NULL;
+ if (err == -EPROBE_DEFER) {
+ q6apm_lpass_dai_put_clocks(data);
+ return dev_err_probe(dev, err,
+ "unable to get mi2s mclk\n");
+ }
+ }
+
+ priv->bclk = of_clk_get_by_name(node, "bclk");
+ if (IS_ERR(priv->bclk)) {
+ int err = PTR_ERR(priv->bclk);
+
+ priv->bclk = NULL;
+ if (err == -EPROBE_DEFER) {
+ q6apm_lpass_dai_put_clocks(data);
+ return dev_err_probe(dev, err,
+ "unable to get mi2s bclk\n");
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void q6apm_lpass_dai_clocks_action(void *data)
+{
+ q6apm_lpass_dai_put_clocks(data);
+}
+
+static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev)
+{
+ struct q6dsp_audio_port_dai_driver_config cfg;
+ struct q6apm_lpass_dai_data *dai_data;
+ struct snd_soc_dai_driver *dais;
+ struct device *dev = &pdev->dev;
+ int num_dais;
+ int ret;
+
+ dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL);
+ if (!dai_data)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, dai_data);
+ ret = of_q6apm_parse_dai_data(dev, dai_data);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, q6apm_lpass_dai_clocks_action, dai_data);
+ if (ret)
+ return ret;
+
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.q6i2s_ops = &q6i2s_ops;
+ cfg.q6dma_ops = &q6dma_ops;
+ cfg.q6hdmi_ops = &q6hdmi_ops;
+ cfg.q6tdm_ops = &q6tdm_ops;
+ dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais);
+
+ return devm_snd_soc_register_component(dev, &q6apm_lpass_dai_component, dais, num_dais);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id q6apm_lpass_dai_device_id[] = {
+ { .compatible = "qcom,q6apm-lpass-dais" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6apm_lpass_dai_device_id);
+#endif
+
+static struct platform_driver q6apm_lpass_dai_platform_driver = {
+ .driver = {
+ .name = "q6apm-lpass-dais",
+ .of_match_table = of_match_ptr(q6apm_lpass_dai_device_id),
+ },
+ .probe = q6apm_lpass_dai_dev_probe,
+};
+module_platform_driver(q6apm_lpass_dai_platform_driver);
+
+MODULE_DESCRIPTION("AUDIOREACH APM LPASS dai driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c
new file mode 100644
index 000000000..f167b9dae
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6apm.c
@@ -0,0 +1,1019 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020, Linaro Limited
+
+#include <dt-bindings/soc/qcom,gpr.h>
+#include <linux/delay.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/apr.h>
+#include <linux/wait.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include "audioreach.h"
+#include "q6apm.h"
+
+/* Graph Management */
+struct apm_graph_mgmt_cmd {
+ struct apm_module_param_data param_data;
+ uint32_t num_sub_graphs;
+ uint32_t sub_graph_id_list[];
+} __packed;
+
+#define APM_GRAPH_MGMT_PSIZE(p, n) ALIGN(struct_size(p, sub_graph_id_list, n), 8)
+
+static struct q6apm *g_apm;
+
+int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt,
+ uint32_t rsp_opcode)
+{
+ gpr_device_t *gdev = apm->gdev;
+
+ return audioreach_send_cmd_sync(&gdev->dev, gdev, &apm->result, &apm->lock,
+ NULL, &apm->wait, pkt, rsp_opcode);
+}
+
+static struct audioreach_graph *q6apm_get_audioreach_graph(struct q6apm *apm, uint32_t graph_id)
+{
+ struct audioreach_graph_info *info;
+ struct audioreach_graph *graph;
+ int id;
+
+ mutex_lock(&apm->lock);
+ graph = idr_find(&apm->graph_idr, graph_id);
+ mutex_unlock(&apm->lock);
+
+ if (graph) {
+ kref_get(&graph->refcount);
+ return graph;
+ }
+
+ info = idr_find(&apm->graph_info_idr, graph_id);
+
+ if (!info)
+ return ERR_PTR(-ENODEV);
+
+ graph = kzalloc_obj(*graph);
+ if (!graph)
+ return ERR_PTR(-ENOMEM);
+
+ graph->apm = apm;
+ graph->info = info;
+ graph->id = graph_id;
+
+ graph->graph = audioreach_alloc_graph_pkt(apm, info);
+ if (IS_ERR(graph->graph)) {
+ void *err = graph->graph;
+
+ kfree(graph);
+ return ERR_CAST(err);
+ }
+
+ mutex_lock(&apm->lock);
+ id = idr_alloc(&apm->graph_idr, graph, graph_id, graph_id + 1, GFP_KERNEL);
+ if (id < 0) {
+ dev_err(apm->dev, "Unable to allocate graph id (%d)\n", graph_id);
+ kfree(graph->graph);
+ kfree(graph);
+ mutex_unlock(&apm->lock);
+ return ERR_PTR(id);
+ }
+ mutex_unlock(&apm->lock);
+
+ kref_init(&graph->refcount);
+
+ q6apm_send_cmd_sync(apm, graph->graph, 0);
+
+ return graph;
+}
+
+static int audioreach_graph_mgmt_cmd(struct audioreach_graph *graph, uint32_t opcode)
+{
+ struct audioreach_graph_info *info = graph->info;
+ int num_sub_graphs = info->num_sub_graphs;
+ struct apm_module_param_data *param_data;
+ struct apm_graph_mgmt_cmd *mgmt_cmd;
+ struct audioreach_sub_graph *sg;
+ struct q6apm *apm = graph->apm;
+ int i = 0, payload_size = APM_GRAPH_MGMT_PSIZE(mgmt_cmd, num_sub_graphs);
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size, opcode, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ mgmt_cmd = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ mgmt_cmd->num_sub_graphs = num_sub_graphs;
+
+ param_data = &mgmt_cmd->param_data;
+ param_data->module_instance_id = APM_MODULE_INSTANCE_ID;
+ param_data->param_id = APM_PARAM_ID_SUB_GRAPH_LIST;
+ param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE;
+
+ list_for_each_entry(sg, &info->sg_list, node)
+ mgmt_cmd->sub_graph_id_list[i++] = sg->sub_graph_id;
+
+ return q6apm_send_cmd_sync(apm, pkt, 0);
+}
+
+static void q6apm_put_audioreach_graph(struct kref *ref)
+{
+ struct audioreach_graph *graph;
+ struct q6apm *apm;
+
+ graph = container_of(ref, struct audioreach_graph, refcount);
+ apm = graph->apm;
+
+ audioreach_graph_mgmt_cmd(graph, APM_CMD_GRAPH_CLOSE);
+
+ mutex_lock(&apm->lock);
+ graph = idr_remove(&apm->graph_idr, graph->id);
+ mutex_unlock(&apm->lock);
+
+ kfree(graph->graph);
+ kfree(graph);
+}
+
+
+static int q6apm_get_apm_state(struct q6apm *apm)
+{
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(0,
+ APM_CMD_GET_SPF_STATE, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ q6apm_send_cmd_sync(apm, pkt, APM_CMD_RSP_GET_SPF_STATE);
+
+ return apm->state;
+}
+
+bool q6apm_is_adsp_ready(void)
+{
+ if (g_apm)
+ return q6apm_get_apm_state(g_apm);
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(q6apm_is_adsp_ready);
+
+static struct audioreach_module *__q6apm_find_module_by_mid(struct q6apm *apm,
+ struct audioreach_graph_info *info,
+ uint32_t mid)
+{
+ struct audioreach_container *container;
+ struct audioreach_sub_graph *sgs;
+ struct audioreach_module *module;
+
+ list_for_each_entry(sgs, &info->sg_list, node) {
+ list_for_each_entry(container, &sgs->container_list, node) {
+ list_for_each_entry(module, &container->modules_list, node) {
+ if (mid == module->module_id)
+ return module;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+int q6apm_graph_media_format_shmem(struct q6apm_graph *graph,
+ struct audioreach_module_config *cfg)
+{
+ struct audioreach_module *module;
+
+ if (cfg->direction == SNDRV_PCM_STREAM_CAPTURE) {
+ module = q6apm_find_module_by_mid(graph, MODULE_ID_SH_MEM_PUSH_MODE);
+ if (!module)
+ module = q6apm_find_module_by_mid(graph, MODULE_ID_RD_SHARED_MEM_EP);
+ } else {
+ module = q6apm_find_module_by_mid(graph, MODULE_ID_SH_MEM_PULL_MODE);
+ if (!module)
+ module = q6apm_find_module_by_mid(graph, MODULE_ID_WR_SHARED_MEM_EP);
+ }
+
+ if (!module) {
+ dev_err(graph->dev, "No SHMEM module found in graph\n");
+ return -ENODEV;
+ }
+
+ return audioreach_set_media_format(graph, module, cfg);
+}
+EXPORT_SYMBOL_GPL(q6apm_graph_media_format_shmem);
+
+static int __q6apm_map_memory_fixed_region(struct device *dev, unsigned int graph_id,
+ phys_addr_t phys, size_t sz, bool is_pos_buf)
+{
+ struct audioreach_graph_info *info;
+ struct q6apm *apm = dev_get_drvdata(dev->parent);
+ struct apm_shared_map_region_payload *mregions;
+ struct apm_cmd_shared_mem_map_regions *cmd;
+ int payload_size = sizeof(*cmd) + (sizeof(*mregions));
+ uint32_t buf_sz;
+ void *p;
+ uint32_t pos_mask = is_pos_buf ? APM_MMAP_TOKEN_MAP_TYPE_POS_BUF : 0;
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size,
+ APM_CMD_SHARED_MEM_MAP_REGIONS, (graph_id | pos_mask));
+
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ info = idr_find(&apm->graph_info_idr, graph_id);
+ if (!info)
+ return -ENODEV;
+
+ if (is_pos_buf) {
+ if (info->pos_buf_mem_map_handle)
+ return 0;
+ } else {
+ if (info->mem_map_handle)
+ return 0;
+ }
+
+ /* DSP expects size should be aligned to 4K */
+ buf_sz = ALIGN(sz, 4096);
+
+ p = (void *)pkt + GPR_HDR_SIZE;
+ cmd = p;
+ cmd->mem_pool_id = APM_MEMORY_MAP_SHMEM8_4K_POOL;
+ cmd->num_regions = 1;
+ if (is_pos_buf)
+ cmd->property_flag = 0x2;
+ else
+ cmd->property_flag = 0x0;
+
+ mregions = p + sizeof(*cmd);
+
+ mregions->shm_addr_lsw = lower_32_bits(phys);
+ mregions->shm_addr_msw = upper_32_bits(phys);
+ mregions->mem_size_bytes = buf_sz;
+
+ return q6apm_send_cmd_sync(apm, pkt, APM_CMD_RSP_SHARED_MEM_MAP_REGIONS);
+}
+
+int q6apm_map_pos_buffer(struct device *dev, unsigned int graph_id, phys_addr_t phys, size_t sz)
+{
+ return __q6apm_map_memory_fixed_region(dev, graph_id, phys, sz, true);
+}
+EXPORT_SYMBOL_GPL(q6apm_map_pos_buffer);
+
+int q6apm_map_memory_fixed_region(struct device *dev, unsigned int graph_id,
+ phys_addr_t phys, size_t sz)
+{
+ return __q6apm_map_memory_fixed_region(dev, graph_id, phys, sz, false);
+}
+EXPORT_SYMBOL_GPL(q6apm_map_memory_fixed_region);
+
+int q6apm_alloc_fragments(struct q6apm_graph *graph, unsigned int dir, phys_addr_t phys,
+ size_t period_sz, unsigned int periods)
+{
+ struct audioreach_graph_data *data;
+ struct audio_buffer *buf;
+ int cnt;
+
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ data = &graph->rx_data;
+ else
+ data = &graph->tx_data;
+
+ mutex_lock(&graph->lock);
+
+ data->dsp_buf = 0;
+
+ if (data->buf) {
+ mutex_unlock(&graph->lock);
+ return 0;
+ }
+
+ buf = kzalloc_objs(struct audio_buffer, periods);
+ if (!buf) {
+ mutex_unlock(&graph->lock);
+ return -ENOMEM;
+ }
+
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ data = &graph->rx_data;
+ else
+ data = &graph->tx_data;
+
+ data->buf = buf;
+
+ buf[0].phys = phys;
+ buf[0].size = period_sz;
+
+ for (cnt = 1; cnt < periods; cnt++) {
+ if (period_sz > 0) {
+ buf[cnt].phys = buf[0].phys + (cnt * period_sz);
+ buf[cnt].size = period_sz;
+ }
+ }
+ data->num_periods = periods;
+
+ mutex_unlock(&graph->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(q6apm_alloc_fragments);
+
+static int __q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int graph_id,
+ bool is_pos_buf)
+{
+ struct apm_cmd_shared_mem_unmap_regions *cmd;
+ struct q6apm *apm = dev_get_drvdata(dev->parent);
+ struct audioreach_graph_info *info;
+ uint32_t mem_map_handle;
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(sizeof(*cmd),
+ APM_CMD_SHARED_MEM_UNMAP_REGIONS, graph_id);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ info = idr_find(&apm->graph_info_idr, graph_id);
+ if (!info)
+ return -ENODEV;
+
+ if (is_pos_buf) {
+ if (!info->pos_buf_mem_map_handle)
+ return 0;
+ mem_map_handle = info->pos_buf_mem_map_handle;
+ } else {
+
+ if (!info->mem_map_handle)
+ return 0;
+ mem_map_handle = info->mem_map_handle;
+ }
+
+ cmd = (void *)pkt + GPR_HDR_SIZE;
+ cmd->mem_map_handle = mem_map_handle;
+
+ return q6apm_send_cmd_sync(apm, pkt, APM_CMD_SHARED_MEM_UNMAP_REGIONS);
+}
+
+int q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int graph_id)
+{
+ return __q6apm_unmap_memory_fixed_region(dev, graph_id, false);
+}
+EXPORT_SYMBOL_GPL(q6apm_unmap_memory_fixed_region);
+
+int q6apm_unmap_pos_buffer(struct device *dev, unsigned int graph_id)
+{
+ return __q6apm_unmap_memory_fixed_region(dev, graph_id, true);
+}
+EXPORT_SYMBOL_GPL(q6apm_unmap_pos_buffer);
+
+int q6apm_free_fragments(struct q6apm_graph *graph, unsigned int dir)
+{
+ audioreach_graph_free_buf(graph);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(q6apm_free_fragments);
+
+int q6apm_remove_initial_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples)
+{
+ struct audioreach_module *module;
+
+ module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER);
+ if (!module)
+ return -ENODEV;
+
+ return audioreach_send_u32_param(graph, module, PARAM_ID_REMOVE_INITIAL_SILENCE, samples);
+}
+EXPORT_SYMBOL_GPL(q6apm_remove_initial_silence);
+
+int q6apm_remove_trailing_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples)
+{
+ struct audioreach_module *module;
+
+ module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER);
+ if (!module)
+ return -ENODEV;
+
+ return audioreach_send_u32_param(graph, module, PARAM_ID_REMOVE_TRAILING_SILENCE, samples);
+}
+EXPORT_SYMBOL_GPL(q6apm_remove_trailing_silence);
+
+int q6apm_enable_compress_module(struct device *dev, struct q6apm_graph *graph, bool en)
+{
+ struct audioreach_module *module;
+
+ module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER);
+ if (!module)
+ return -ENODEV;
+
+ return audioreach_send_u32_param(graph, module, PARAM_ID_MODULE_ENABLE, en);
+}
+EXPORT_SYMBOL_GPL(q6apm_enable_compress_module);
+
+int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph,
+ uint32_t codec_id)
+{
+ struct audioreach_module *module;
+ uint32_t module_id;
+
+ module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER);
+ if (!module)
+ return -ENODEV;
+
+ switch (codec_id) {
+ case SND_AUDIOCODEC_MP3:
+ module_id = MODULE_ID_MP3_DECODE;
+ break;
+ case SND_AUDIOCODEC_AAC:
+ module_id = MODULE_ID_AAC_DEC;
+ break;
+ case SND_AUDIOCODEC_FLAC:
+ module_id = MODULE_ID_FLAC_DEC;
+ break;
+ case SND_AUDIOCODEC_OPUS_RAW:
+ module_id = MODULE_ID_OPUS_DEC;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return audioreach_send_u32_param(graph, module, PARAM_ID_REAL_MODULE_ID,
+ module_id);
+}
+EXPORT_SYMBOL_GPL(q6apm_set_real_module_id);
+
+int q6apm_graph_media_format_pcm(struct q6apm_graph *graph, struct audioreach_module_config *cfg)
+{
+ struct audioreach_graph_info *info = graph->info;
+ struct audioreach_sub_graph *sgs;
+ struct audioreach_container *container;
+ struct audioreach_module *module;
+ int ret;
+
+ list_for_each_entry(sgs, &info->sg_list, node) {
+ list_for_each_entry(container, &sgs->container_list, node) {
+ list_for_each_entry(module, &container->modules_list, node) {
+ if ((module->module_id == MODULE_ID_WR_SHARED_MEM_EP) ||
+ (module->module_id == MODULE_ID_RD_SHARED_MEM_EP) ||
+ (module->module_id == MODULE_ID_SH_MEM_PULL_MODE) ||
+ (module->module_id == MODULE_ID_SH_MEM_PUSH_MODE))
+ continue;
+
+ ret = audioreach_set_media_format(graph, module, cfg);
+ if (ret)
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+
+}
+EXPORT_SYMBOL_GPL(q6apm_graph_media_format_pcm);
+
+int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts,
+ uint32_t lsw_ts, uint32_t wflags)
+{
+ struct apm_data_cmd_wr_sh_mem_ep_data_buffer_v2 *write_buffer;
+ struct audio_buffer *ab;
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_pkt(sizeof(*write_buffer),
+ DATA_CMD_WR_SH_MEM_EP_DATA_BUFFER_V2,
+ graph->rx_data.dsp_buf | (len << APM_WRITE_TOKEN_LEN_SHIFT),
+ graph->port->id, graph->shm_iid);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ write_buffer = (void *)pkt + GPR_HDR_SIZE;
+
+ mutex_lock(&graph->lock);
+ ab = &graph->rx_data.buf[graph->rx_data.dsp_buf];
+
+ write_buffer->buf_addr_lsw = lower_32_bits(ab->phys);
+ write_buffer->buf_addr_msw = upper_32_bits(ab->phys);
+ write_buffer->buf_size = len;
+ write_buffer->timestamp_lsw = lsw_ts;
+ write_buffer->timestamp_msw = msw_ts;
+ write_buffer->mem_map_handle = graph->info->mem_map_handle;
+ write_buffer->flags = wflags;
+
+ graph->rx_data.dsp_buf++;
+
+ if (graph->rx_data.dsp_buf >= graph->rx_data.num_periods)
+ graph->rx_data.dsp_buf = 0;
+
+ mutex_unlock(&graph->lock);
+
+ return gpr_send_port_pkt(graph->port, pkt);
+}
+EXPORT_SYMBOL_GPL(q6apm_write_async);
+
+int q6apm_read(struct q6apm_graph *graph)
+{
+ struct data_cmd_rd_sh_mem_ep_data_buffer_v2 *read_buffer;
+ struct audioreach_graph_data *port;
+ struct audio_buffer *ab;
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_pkt(sizeof(*read_buffer),
+ DATA_CMD_RD_SH_MEM_EP_DATA_BUFFER_V2,
+ graph->tx_data.dsp_buf, graph->port->id, graph->shm_iid);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ read_buffer = (void *)pkt + GPR_HDR_SIZE;
+
+ mutex_lock(&graph->lock);
+ port = &graph->tx_data;
+ ab = &port->buf[port->dsp_buf];
+
+ read_buffer->buf_addr_lsw = lower_32_bits(ab->phys);
+ read_buffer->buf_addr_msw = upper_32_bits(ab->phys);
+ read_buffer->mem_map_handle = graph->info->mem_map_handle;
+ read_buffer->buf_size = ab->size;
+
+ port->dsp_buf++;
+
+ if (port->dsp_buf >= port->num_periods)
+ port->dsp_buf = 0;
+
+ mutex_unlock(&graph->lock);
+
+ return gpr_send_port_pkt(graph->port, pkt);
+}
+EXPORT_SYMBOL_GPL(q6apm_read);
+
+int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir)
+{
+ struct audioreach_graph_data *data;
+
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ data = &graph->rx_data;
+ else
+ data = &graph->tx_data;
+
+ return (int)atomic_read(&data->hw_ptr);
+}
+EXPORT_SYMBOL_GPL(q6apm_get_hw_pointer);
+
+static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op)
+{
+ struct data_cmd_rsp_rd_sh_mem_ep_data_buffer_done_v2 *rd_done;
+ struct data_cmd_rsp_wr_sh_mem_ep_data_buffer_done_v2 *done;
+ struct apm_module_event *event;
+ const struct gpr_ibasic_rsp_result_t *result;
+ struct q6apm_graph *graph = priv;
+ const struct gpr_hdr *hdr = &data->hdr;
+ struct device *dev = graph->dev;
+ uint32_t client_event;
+ phys_addr_t phys;
+ int token;
+
+ result = data->payload;
+
+ switch (hdr->opcode) {
+ case APM_EVENT_MODULE_TO_CLIENT:
+ event = data->payload;
+ switch (event->event_id) {
+ case EVENT_ID_SH_MEM_PULL_PUSH_MODE_WATERMARK:
+ client_event = APM_CLIENT_EVENT_WATERMARK_EVENT;
+ graph->cb(client_event, hdr->token, data->payload, graph->priv);
+ break;
+ }
+
+ break;
+ case DATA_CMD_RSP_WR_SH_MEM_EP_DATA_BUFFER_DONE_V2:
+ if (!graph->ar_graph)
+ break;
+ client_event = APM_CLIENT_EVENT_DATA_WRITE_DONE;
+ mutex_lock(&graph->lock);
+ token = hdr->token & APM_WRITE_TOKEN_MASK;
+
+ done = data->payload;
+ if (!graph->rx_data.buf) {
+ mutex_unlock(&graph->lock);
+ break;
+ }
+ phys = graph->rx_data.buf[token].phys;
+ mutex_unlock(&graph->lock);
+ /* token numbering starts at 0 */
+ atomic_set(&graph->rx_data.hw_ptr, token + 1);
+ if (lower_32_bits(phys) == done->buf_addr_lsw &&
+ upper_32_bits(phys) == done->buf_addr_msw) {
+ graph->result.opcode = hdr->opcode;
+ graph->result.status = done->status;
+ if (graph->cb)
+ graph->cb(client_event, hdr->token, data->payload, graph->priv);
+ } else {
+ dev_err(dev, "WR BUFF Unexpected addr %08x-%08x\n", done->buf_addr_lsw,
+ done->buf_addr_msw);
+ }
+
+ break;
+ case DATA_CMD_RSP_RD_SH_MEM_EP_DATA_BUFFER_V2:
+ if (!graph->ar_graph)
+ break;
+ client_event = APM_CLIENT_EVENT_DATA_READ_DONE;
+ mutex_lock(&graph->lock);
+ rd_done = data->payload;
+ if (!graph->tx_data.buf) {
+ mutex_unlock(&graph->lock);
+ break;
+ }
+ phys = graph->tx_data.buf[hdr->token].phys;
+ mutex_unlock(&graph->lock);
+ /* token numbering starts at 0 */
+ atomic_set(&graph->tx_data.hw_ptr, hdr->token + 1);
+
+ if (upper_32_bits(phys) == rd_done->buf_addr_msw &&
+ lower_32_bits(phys) == rd_done->buf_addr_lsw) {
+ graph->result.opcode = hdr->opcode;
+ graph->result.status = rd_done->status;
+ if (graph->cb)
+ graph->cb(client_event, hdr->token, data->payload, graph->priv);
+ } else {
+ dev_err(dev, "RD BUFF Unexpected addr %08x-%08x\n", rd_done->buf_addr_lsw,
+ rd_done->buf_addr_msw);
+ }
+ break;
+ case DATA_CMD_WR_SH_MEM_EP_EOS_RENDERED:
+ client_event = APM_CLIENT_EVENT_CMD_EOS_DONE;
+ if (graph->cb)
+ graph->cb(client_event, hdr->token, data->payload, graph->priv);
+ break;
+ case GPR_BASIC_RSP_RESULT:
+ switch (result->opcode) {
+ case APM_CMD_SHARED_MEM_MAP_REGIONS:
+ case DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT:
+ case APM_CMD_REGISTER_MODULE_EVENTS:
+ case APM_CMD_SET_CFG:
+ graph->result.opcode = result->opcode;
+ graph->result.status = result->status;
+ if (result->status)
+ dev_err(dev, "Error (%d) Processing 0x%08x cmd\n",
+ result->status, result->opcode);
+ wake_up(&graph->cmd_wait);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+int q6apm_register_watermark_event(struct q6apm_graph *graph, int water_mark_level_bytes,
+ int num_levels)
+{
+ return audioreach_shmem_register_event(graph, water_mark_level_bytes, num_levels);
+}
+EXPORT_SYMBOL_GPL(q6apm_register_watermark_event);
+
+int q6apm_push_pull_config(struct q6apm_graph *graph, phys_addr_t bphys,
+ phys_addr_t pphys, uint32_t size)
+{
+ struct audioreach_graph_info *info = graph->info;
+
+ return audioreach_setup_push_pull(graph, bphys, pphys, info->mem_map_handle,
+ info->pos_buf_mem_map_handle, size);
+}
+EXPORT_SYMBOL_GPL(q6apm_push_pull_config);
+
+bool q6apm_is_graph_in_push_pull_mode_from_id(struct device *dev, unsigned int graph_id, int dir)
+{
+ struct audioreach_graph_info *info;
+ struct q6apm *apm = dev_get_drvdata(dev->parent);
+ struct audioreach_module *module;
+
+ info = idr_find(&apm->graph_info_idr, graph_id);
+ if (!info)
+ return false;
+
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ module = __q6apm_find_module_by_mid(apm, info, MODULE_ID_SH_MEM_PULL_MODE);
+ else
+ module = __q6apm_find_module_by_mid(apm, info, MODULE_ID_SH_MEM_PUSH_MODE);
+
+ return !!module;
+
+}
+EXPORT_SYMBOL_GPL(q6apm_is_graph_in_push_pull_mode_from_id);
+
+bool q6apm_is_graph_in_push_pull_mode(struct q6apm_graph *graph)
+{
+ return graph->info->is_push_pull_mode;
+}
+EXPORT_SYMBOL_GPL(q6apm_is_graph_in_push_pull_mode);
+
+static int q6apm_graph_get_module_iid(struct q6apm_graph *graph, uint32_t mid)
+{
+ struct audioreach_module *module;
+
+ module = q6apm_find_module_by_mid(graph, mid);
+ if (!module)
+ return -ENODEV;
+
+ return module->instance_id;
+}
+
+struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb,
+ void *priv, int graph_id, int dir)
+{
+ struct q6apm *apm = dev_get_drvdata(dev->parent);
+ struct audioreach_graph *ar_graph;
+ struct q6apm_graph *graph;
+ int ret, iid = 0;
+
+ ar_graph = q6apm_get_audioreach_graph(apm, graph_id);
+ if (IS_ERR(ar_graph)) {
+ dev_err(dev, "No graph found with id %d\n", graph_id);
+ return ERR_CAST(ar_graph);
+ }
+
+ graph = kzalloc_obj(*graph);
+ if (!graph) {
+ ret = -ENOMEM;
+ goto put_ar_graph;
+ }
+
+ graph->apm = apm;
+ graph->priv = priv;
+ graph->cb = cb;
+ graph->info = ar_graph->info;
+ graph->ar_graph = ar_graph;
+ graph->id = ar_graph->id;
+ graph->dev = dev;
+
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
+ iid = q6apm_graph_get_module_iid(graph, MODULE_ID_SH_MEM_PULL_MODE);
+ if (iid < 0)
+ iid = q6apm_graph_get_module_iid(graph, MODULE_ID_WR_SHARED_MEM_EP);
+ else
+ graph->info->is_push_pull_mode = true;
+
+ } else {
+ iid = q6apm_graph_get_module_iid(graph, MODULE_ID_SH_MEM_PUSH_MODE);
+ if (iid < 0)
+ iid = q6apm_graph_get_module_iid(graph, MODULE_ID_RD_SHARED_MEM_EP);
+ else
+ graph->info->is_push_pull_mode = true;
+ }
+
+ if (iid > 0)
+ graph->shm_iid = iid;
+
+ mutex_init(&graph->lock);
+ init_waitqueue_head(&graph->cmd_wait);
+
+ graph->port = gpr_alloc_port(apm->gdev, dev, graph_callback, graph);
+ if (IS_ERR(graph->port)) {
+ ret = PTR_ERR(graph->port);
+ goto free_graph;
+ }
+
+ return graph;
+
+free_graph:
+ kfree(graph);
+put_ar_graph:
+ kref_put(&ar_graph->refcount, q6apm_put_audioreach_graph);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(q6apm_graph_open);
+
+int q6apm_graph_close(struct q6apm_graph *graph)
+{
+ struct audioreach_graph *ar_graph = graph->ar_graph;
+
+ graph->ar_graph = NULL;
+ kref_put(&ar_graph->refcount, q6apm_put_audioreach_graph);
+ gpr_free_port(graph->port);
+ kfree(graph);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(q6apm_graph_close);
+
+int q6apm_graph_prepare(struct q6apm_graph *graph)
+{
+ return audioreach_graph_mgmt_cmd(graph->ar_graph, APM_CMD_GRAPH_PREPARE);
+}
+EXPORT_SYMBOL_GPL(q6apm_graph_prepare);
+
+int q6apm_graph_start(struct q6apm_graph *graph)
+{
+ struct audioreach_graph *ar_graph = graph->ar_graph;
+ int ret;
+
+ if (ar_graph->start_count == 0) {
+ ret = audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_START);
+ if (ret)
+ return ret;
+ }
+
+ ar_graph->start_count++;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(q6apm_graph_start);
+
+int q6apm_graph_stop(struct q6apm_graph *graph)
+{
+ struct audioreach_graph *ar_graph = graph->ar_graph;
+
+ if (ar_graph->start_count == 0)
+ return 0;
+
+ if (--ar_graph->start_count > 0)
+ return 0;
+
+ return audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_STOP);
+}
+EXPORT_SYMBOL_GPL(q6apm_graph_stop);
+
+int q6apm_graph_flush(struct q6apm_graph *graph)
+{
+ return audioreach_graph_mgmt_cmd(graph->ar_graph, APM_CMD_GRAPH_FLUSH);
+}
+EXPORT_SYMBOL_GPL(q6apm_graph_flush);
+
+static int q6apm_audio_probe(struct snd_soc_component *component)
+{
+ return audioreach_tplg_init(component);
+}
+
+static void q6apm_audio_remove(struct snd_soc_component *component)
+{
+ /* remove topology */
+ snd_soc_tplg_component_remove(component);
+}
+
+#define APM_AUDIO_DRV_NAME "q6apm-audio"
+
+static const struct snd_soc_component_driver q6apm_audio_component = {
+ .name = APM_AUDIO_DRV_NAME,
+ .probe = q6apm_audio_probe,
+ .remove = q6apm_audio_remove,
+ .remove_order = SND_SOC_COMP_ORDER_LAST,
+};
+
+static int apm_probe(gpr_device_t *gdev)
+{
+ struct device *dev = &gdev->dev;
+ struct q6apm *apm;
+ int ret;
+
+ apm = devm_kzalloc(dev, sizeof(*apm), GFP_KERNEL);
+ if (!apm)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, apm);
+
+ mutex_init(&apm->lock);
+ apm->dev = dev;
+ apm->gdev = gdev;
+ init_waitqueue_head(&apm->wait);
+
+ INIT_LIST_HEAD(&apm->widget_list);
+ idr_init(&apm->graph_idr);
+ idr_init(&apm->graph_info_idr);
+ idr_init(&apm->sub_graphs_idr);
+ idr_init(&apm->containers_idr);
+
+ idr_init(&apm->modules_idr);
+
+ g_apm = apm;
+
+ q6apm_get_apm_state(apm);
+
+ ret = snd_soc_register_component(dev, &q6apm_audio_component, NULL, 0);
+ if (ret < 0) {
+ dev_err(dev, "failed to register q6apm: %d\n", ret);
+ return ret;
+ }
+
+ ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
+ if (ret)
+ snd_soc_unregister_component(dev);
+
+ return ret;
+}
+
+static void apm_remove(gpr_device_t *gdev)
+{
+ of_platform_depopulate(&gdev->dev);
+ snd_soc_unregister_component(&gdev->dev);
+}
+
+struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph, uint32_t mid)
+{
+ struct audioreach_graph_info *info = graph->info;
+ struct q6apm *apm = graph->apm;
+
+ return __q6apm_find_module_by_mid(apm, info, mid);
+
+}
+
+static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op)
+{
+ gpr_device_t *gdev = priv;
+ struct audioreach_graph_info *info;
+ struct q6apm *apm = dev_get_drvdata(&gdev->dev);
+ struct apm_cmd_rsp_shared_mem_map_regions *rsp;
+ struct device *dev = &gdev->dev;
+ struct gpr_ibasic_rsp_result_t *result;
+ const struct gpr_hdr *hdr = &data->hdr;
+ int graph_id, is_pos_buf;
+
+ result = data->payload;
+
+ switch (hdr->opcode) {
+ case APM_CMD_RSP_GET_SPF_STATE:
+ apm->result.opcode = hdr->opcode;
+ apm->result.status = 0;
+ /* First word of result it state */
+ apm->state = result->opcode;
+ wake_up(&apm->wait);
+ break;
+ case GPR_BASIC_RSP_RESULT:
+ switch (result->opcode) {
+ case APM_CMD_SHARED_MEM_MAP_REGIONS:
+ case APM_CMD_GRAPH_START:
+ case APM_CMD_GRAPH_OPEN:
+ case APM_CMD_GRAPH_PREPARE:
+ case APM_CMD_GRAPH_CLOSE:
+ case APM_CMD_GRAPH_FLUSH:
+ case APM_CMD_GRAPH_STOP:
+ case APM_CMD_SET_CFG:
+ apm->result.opcode = result->opcode;
+ apm->result.status = result->status;
+ if (result->status)
+ dev_err(dev, "Error (%d) Processing 0x%08x cmd\n", result->status,
+ result->opcode);
+ wake_up(&apm->wait);
+ break;
+ case APM_CMD_SHARED_MEM_UNMAP_REGIONS:
+ apm->result.opcode = hdr->opcode;
+ apm->result.status = 0;
+ rsp = data->payload;
+
+ info = idr_find(&apm->graph_info_idr, hdr->token);
+ if (info)
+ info->mem_map_handle = 0;
+ else
+ dev_err(dev, "Error (%d) Processing 0x%08x cmd\n", result->status,
+ result->opcode);
+
+ wake_up(&apm->wait);
+ break;
+ default:
+ break;
+ }
+ break;
+ case APM_CMD_RSP_SHARED_MEM_MAP_REGIONS:
+ apm->result.opcode = hdr->opcode;
+ apm->result.status = 0;
+ rsp = data->payload;
+ graph_id = hdr->token & APM_MMAP_TOKEN_GID_MASK;
+ is_pos_buf = hdr->token & APM_MMAP_TOKEN_MAP_TYPE_POS_BUF;
+
+ info = idr_find(&apm->graph_info_idr, graph_id);
+ if (info) {
+ if (is_pos_buf)
+ info->pos_buf_mem_map_handle = rsp->mem_map_handle;
+ else
+ info->mem_map_handle = rsp->mem_map_handle;
+ } else {
+ dev_err(dev, "Error (%d) Processing 0x%08x cmd\n", result->status,
+ result->opcode);
+ }
+
+ wake_up(&apm->wait);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id apm_device_id[] = {
+ { .compatible = "qcom,q6apm" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, apm_device_id);
+#endif
+
+static gpr_driver_t apm_driver = {
+ .probe = apm_probe,
+ .remove = apm_remove,
+ .gpr_callback = apm_callback,
+ .driver = {
+ .name = "qcom-apm",
+ .of_match_table = of_match_ptr(apm_device_id),
+ },
+};
+
+module_gpr_driver(apm_driver);
+MODULE_DESCRIPTION("Audio Process Manager");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h
new file mode 100644
index 000000000..5cb51ca49
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6apm.h
@@ -0,0 +1,169 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __Q6APM_H__
+#define __Q6APM_H__
+#include <linux/types.h>
+#include <linux/atomic.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <sound/soc.h>
+#include <linux/of_platform.h>
+#include <linux/jiffies.h>
+#include <linux/soc/qcom/apr.h>
+#include "../common.h"
+#include "audioreach.h"
+
+#define APM_PORT_MAX LPASS_MAX_PORT
+#define APM_PORT_MAX_AUDIO_CHAN_CNT 8
+#define PCM_CHANNEL_NULL 0
+#define PCM_CHANNEL_FL 1 /* Front left channel. */
+#define PCM_CHANNEL_FR 2 /* Front right channel. */
+#define PCM_CHANNEL_FC 3 /* Front center channel. */
+#define PCM_CHANNEL_LS 4 /* Left surround channel. */
+#define PCM_CHANNEL_RS 5 /* Right surround channel. */
+#define PCM_CHANNEL_LFE 6 /* Low frequency effect channel. */
+#define PCM_CHANNEL_CS 7 /* Center surround channel; Rear center ch */
+#define PCM_CHANNEL_LB 8 /* Left back channel; Rear left channel. */
+#define PCM_CHANNEL_RB 9 /* Right back channel; Rear right channel. */
+#define PCM_CHANNELS 10 /* Top surround channel. */
+
+#define APM_TIMESTAMP_FLAG 0x80000000
+#define FORMAT_LINEAR_PCM 0x0000
+/* APM client callback events */
+#define APM_CMD_EOS 0x0003
+#define APM_CLIENT_EVENT_CMD_EOS_DONE 0x1003
+#define APM_CMD_CLOSE 0x0004
+#define APM_CLIENT_EVENT_CMD_CLOSE_DONE 0x1004
+#define APM_CLIENT_EVENT_CMD_RUN_DONE 0x1008
+#define APM_CLIENT_EVENT_DATA_WRITE_DONE 0x1009
+#define APM_CLIENT_EVENT_DATA_READ_DONE 0x100a
+#define APM_CLIENT_EVENT_WATERMARK_EVENT 0x100b
+#define APM_WRITE_TOKEN_MASK GENMASK(15, 0)
+#define APM_WRITE_TOKEN_LEN_MASK GENMASK(31, 16)
+#define APM_WRITE_TOKEN_LEN_SHIFT 16
+
+#define APM_MAX_SESSIONS 8
+#define APM_LAST_BUFFER_FLAG BIT(30)
+#define NO_TIMESTAMP 0xFF00
+
+struct q6apm {
+ struct device *dev;
+ gpr_port_t *port;
+ gpr_device_t *gdev;
+ /* For Graph OPEN/START/STOP/CLOSE operations */
+ wait_queue_head_t wait;
+ struct gpr_ibasic_rsp_result_t result;
+
+ struct mutex cmd_lock;
+ struct mutex lock;
+ uint32_t state;
+
+ struct list_head widget_list;
+ struct idr graph_idr;
+ struct idr graph_info_idr;
+ struct idr sub_graphs_idr;
+ struct idr containers_idr;
+ struct idr modules_idr;
+};
+
+struct audio_buffer {
+ phys_addr_t phys;
+ uint32_t size; /* size of buffer */
+};
+
+struct audioreach_graph_data {
+ struct audio_buffer *buf;
+ uint32_t num_periods;
+ uint32_t dsp_buf;
+ atomic_t hw_ptr;
+};
+
+struct audioreach_graph {
+ struct audioreach_graph_info *info;
+ uint32_t id;
+ int state;
+ int start_count;
+ /* Cached Graph data */
+ void *graph;
+ struct kref refcount;
+ struct q6apm *apm;
+};
+
+typedef void (*q6apm_cb) (uint32_t opcode, uint32_t token,
+ void *payload, void *priv);
+struct q6apm_graph {
+ void *priv;
+ q6apm_cb cb;
+ uint32_t id;
+ uint32_t shm_iid;
+ struct device *dev;
+ struct q6apm *apm;
+ gpr_port_t *port;
+ struct audioreach_graph_data rx_data;
+ struct audioreach_graph_data tx_data;
+ struct gpr_ibasic_rsp_result_t result;
+ wait_queue_head_t cmd_wait;
+ struct mutex lock;
+ struct audioreach_graph *ar_graph;
+ struct audioreach_graph_info *info;
+};
+
+/* Graph Operations */
+struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb,
+ void *priv, int graph_id, int dir);
+int q6apm_graph_close(struct q6apm_graph *graph);
+int q6apm_graph_prepare(struct q6apm_graph *graph);
+int q6apm_graph_start(struct q6apm_graph *graph);
+int q6apm_graph_stop(struct q6apm_graph *graph);
+int q6apm_graph_flush(struct q6apm_graph *graph);
+
+/* Media Format */
+int q6apm_graph_media_format_pcm(struct q6apm_graph *graph,
+ struct audioreach_module_config *cfg);
+
+int q6apm_graph_media_format_shmem(struct q6apm_graph *graph,
+ struct audioreach_module_config *cfg);
+
+/* read/write related */
+int q6apm_read(struct q6apm_graph *graph);
+int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts,
+ uint32_t lsw_ts, uint32_t wflags);
+
+/* Memory Map related */
+int q6apm_map_memory_fixed_region(struct device *dev,
+ unsigned int graph_id, phys_addr_t phys,
+ size_t sz);
+int q6apm_map_pos_buffer(struct device *dev,
+ unsigned int graph_id, phys_addr_t phys,
+ size_t sz);
+int q6apm_unmap_pos_buffer(struct device *dev, unsigned int graph_id);
+int q6apm_alloc_fragments(struct q6apm_graph *graph,
+ unsigned int dir, phys_addr_t phys,
+ size_t period_sz, unsigned int periods);
+int q6apm_free_fragments(struct q6apm_graph *graph, unsigned int dir);
+int q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int graph_id);
+/* Helpers */
+int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt,
+ uint32_t rsp_opcode);
+
+/* Callback for graph specific */
+struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph,
+ uint32_t mid);
+bool q6apm_is_adsp_ready(void);
+
+int q6apm_enable_compress_module(struct device *dev, struct q6apm_graph *graph, bool en);
+int q6apm_remove_initial_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples);
+int q6apm_remove_trailing_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples);
+int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph, uint32_t codec_id);
+int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir);
+bool q6apm_is_graph_in_push_pull_mode(struct q6apm_graph *graph);
+bool q6apm_is_graph_in_push_pull_mode_from_id(struct device *dev, unsigned int graph_id, int dir);
+int q6apm_push_pull_config(struct q6apm_graph *graph, phys_addr_t bphys,
+ phys_addr_t pphys, uint32_t size);
+
+int q6apm_register_watermark_event(struct q6apm_graph *graph, int watermark_bytes, int num_levels);
+#endif /* __APM_GRAPH_ */
diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c
new file mode 100644
index 000000000..4f09fdd40
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6asm-dai.c
@@ -0,0 +1,1345 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include <dt-bindings/sound/qcom,q6asm.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <linux/spinlock.h>
+#include <sound/compress_driver.h>
+#include <asm/div64.h>
+#include <asm/dma.h>
+#include <linux/dma-mapping.h>
+#include <sound/pcm_params.h>
+#include "q6asm.h"
+#include "q6routing.h"
+#include "q6dsp-errno.h"
+
+#define DRV_NAME "q6asm-fe-dai"
+
+#define PLAYBACK_MIN_NUM_PERIODS 2
+#define PLAYBACK_MAX_NUM_PERIODS 8
+#define PLAYBACK_MAX_PERIOD_SIZE 65536
+#define PLAYBACK_MIN_PERIOD_SIZE 128
+#define CAPTURE_MIN_NUM_PERIODS 2
+#define CAPTURE_MAX_NUM_PERIODS 8
+#define CAPTURE_MAX_PERIOD_SIZE 4096
+#define CAPTURE_MIN_PERIOD_SIZE 320
+#define SID_MASK_DEFAULT 0xF
+
+/* Default values used if user space does not set */
+#define COMPR_PLAYBACK_MIN_FRAGMENT_SIZE (8 * 1024)
+#define COMPR_PLAYBACK_MAX_FRAGMENT_SIZE (128 * 1024)
+#define COMPR_PLAYBACK_MIN_NUM_FRAGMENTS (4)
+#define COMPR_PLAYBACK_MAX_NUM_FRAGMENTS (16 * 4)
+
+#define ALAC_CH_LAYOUT_MONO ((101 << 16) | 1)
+#define ALAC_CH_LAYOUT_STEREO ((101 << 16) | 2)
+
+enum stream_state {
+ Q6ASM_STREAM_IDLE = 0,
+ Q6ASM_STREAM_STOPPED,
+ Q6ASM_STREAM_RUNNING,
+};
+
+struct q6asm_dai_rtd {
+ struct snd_pcm_substream *substream;
+ struct snd_compr_stream *cstream;
+ struct snd_codec codec;
+ struct snd_dma_buffer dma_buffer;
+ spinlock_t lock;
+ phys_addr_t phys;
+ unsigned int pcm_size;
+ unsigned int pcm_count;
+ unsigned int periods;
+ uint64_t bytes_sent;
+ uint64_t bytes_received;
+ uint64_t copied_total;
+ uint16_t bits_per_sample;
+ snd_pcm_uframes_t queue_ptr;
+ uint16_t source; /* Encoding source bit mask */
+ struct audio_client *audio_client;
+ uint32_t next_track_stream_id;
+ bool next_track;
+ uint32_t stream_id;
+ uint16_t session_id;
+ enum stream_state state;
+ uint32_t initial_samples_drop;
+ uint32_t trailing_samples_drop;
+ bool notify_on_drain;
+};
+
+struct q6asm_dai_data {
+ struct snd_soc_dai_driver *dais;
+ int num_dais;
+ long long int sid;
+};
+
+static const struct snd_pcm_hardware q6asm_dai_hardware_capture = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_NO_REWINDS | SNDRV_PCM_INFO_SYNC_APPLPTR |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE),
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 4,
+ .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,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware q6asm_dai_hardware_playback = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_NO_REWINDS | SNDRV_PCM_INFO_SYNC_APPLPTR |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE),
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 1,
+ .channels_max = 8,
+ .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,
+ .fifo_size = 0,
+};
+
+#define Q6ASM_FEDAI_DRIVER(num) { \
+ .playback = { \
+ .stream_name = "MultiMedia"#num" Playback", \
+ .rates = (SNDRV_PCM_RATE_8000_48000 | \
+ SNDRV_PCM_RATE_12000 | \
+ SNDRV_PCM_RATE_24000 | \
+ SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_176400 | \
+ SNDRV_PCM_RATE_192000), \
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE), \
+ .channels_min = 1, \
+ .channels_max = 8, \
+ .rate_min = 8000, \
+ .rate_max = 192000, \
+ }, \
+ .capture = { \
+ .stream_name = "MultiMedia"#num" Capture", \
+ .rates = (SNDRV_PCM_RATE_8000_48000 | \
+ SNDRV_PCM_RATE_12000 | \
+ SNDRV_PCM_RATE_24000), \
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE), \
+ .channels_min = 1, \
+ .channels_max = 4, \
+ .rate_min = 8000, \
+ .rate_max = 48000, \
+ }, \
+ .name = "MultiMedia"#num, \
+ .id = MSM_FRONTEND_DAI_MULTIMEDIA##num, \
+ }
+
+static const struct snd_compr_codec_caps q6asm_compr_caps = {
+ .num_descriptors = 1,
+ .descriptor[0].max_ch = 2,
+ .descriptor[0].sample_rates = { 8000, 11025, 12000, 16000, 22050,
+ 24000, 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000 },
+ .descriptor[0].num_sample_rates = 13,
+ .descriptor[0].bit_rate[0] = 320,
+ .descriptor[0].bit_rate[1] = 128,
+ .descriptor[0].num_bitrates = 2,
+ .descriptor[0].profiles = 0,
+ .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO,
+ .descriptor[0].formats = 0,
+};
+
+static void event_handler(uint32_t opcode, uint32_t token,
+ void *payload, void *priv)
+{
+ struct q6asm_dai_rtd *prtd = priv;
+ struct snd_pcm_substream *substream = prtd->substream;
+
+ switch (opcode) {
+ case ASM_CLIENT_EVENT_CMD_RUN_DONE:
+ break;
+ case ASM_CLIENT_EVENT_CMD_EOS_DONE:
+ break;
+ case ASM_CLIENT_EVENT_DATA_WRITE_DONE:
+ snd_pcm_period_elapsed(substream);
+ break;
+ case ASM_CLIENT_EVENT_DATA_READ_DONE:
+ snd_pcm_period_elapsed(substream);
+ if (prtd->state == Q6ASM_STREAM_RUNNING)
+ q6asm_read(prtd->audio_client, prtd->stream_id);
+
+ break;
+ default:
+ break;
+ }
+}
+
+static int q6asm_dai_prepare(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_prtd = snd_soc_substream_to_rtd(substream);
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+ struct q6asm_dai_data *pdata;
+ struct device *dev = component->dev;
+ int ret, i;
+
+ pdata = snd_soc_component_get_drvdata(component);
+ if (!pdata)
+ return -EINVAL;
+
+ if (!prtd || !prtd->audio_client) {
+ dev_err(dev, "%s: private data null or audio client freed\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ prtd->pcm_count = snd_pcm_lib_period_bytes(substream);
+ /* rate and channels are sent to audio driver */
+ if (prtd->state == Q6ASM_STREAM_RUNNING) {
+ /* clear the previous setup if any */
+ ret = q6asm_cmd(prtd->audio_client, prtd->stream_id, CMD_CLOSE);
+ if (ret < 0) {
+ dev_err(dev, "Failed to close q6asm stream %d\n", prtd->stream_id);
+ return ret;
+ }
+
+ ret = q6asm_unmap_memory_regions(substream->stream, prtd->audio_client);
+ if (ret < 0) {
+ dev_err(dev, "Failed to unmap memory regions for q6asm stream %d\n",
+ prtd->stream_id);
+ return ret;
+ }
+
+ q6routing_stream_close(soc_prtd->dai_link->id,
+ substream->stream);
+ prtd->state = Q6ASM_STREAM_STOPPED;
+ }
+
+ ret = q6asm_map_memory_regions(substream->stream, prtd->audio_client,
+ prtd->phys,
+ (prtd->pcm_size / prtd->periods),
+ prtd->periods);
+
+ if (ret < 0) {
+ dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n",
+ ret);
+ return -ENOMEM;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ret = q6asm_open_write(prtd->audio_client, prtd->stream_id,
+ FORMAT_LINEAR_PCM,
+ 0, prtd->bits_per_sample, false);
+ } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ ret = q6asm_open_read(prtd->audio_client, prtd->stream_id,
+ FORMAT_LINEAR_PCM,
+ prtd->bits_per_sample);
+ }
+
+ if (ret < 0) {
+ dev_err(dev, "%s: q6asm_open_write failed\n", __func__);
+ goto open_err;
+ }
+
+ prtd->session_id = q6asm_get_session_id(prtd->audio_client);
+ ret = q6routing_stream_open(soc_prtd->dai_link->id, LEGACY_PCM_MODE,
+ prtd->session_id, substream->stream);
+ if (ret) {
+ dev_err(dev, "%s: stream reg failed ret:%d\n", __func__, ret);
+ goto routing_err;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ret = q6asm_media_format_block_multi_ch_pcm(
+ prtd->audio_client, prtd->stream_id,
+ runtime->rate, runtime->channels, NULL,
+ prtd->bits_per_sample);
+ } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ ret = q6asm_enc_cfg_blk_pcm_format_support(prtd->audio_client,
+ prtd->stream_id,
+ runtime->rate,
+ runtime->channels,
+ prtd->bits_per_sample);
+
+ /* Queue the buffers */
+ for (i = 0; i < runtime->periods; i++)
+ q6asm_read(prtd->audio_client, prtd->stream_id);
+
+ }
+ if (ret < 0)
+ dev_info(dev, "%s: CMD Format block failed\n", __func__);
+ else
+ prtd->state = Q6ASM_STREAM_RUNNING;
+
+ return ret;
+
+routing_err:
+ q6asm_cmd(prtd->audio_client, prtd->stream_id, CMD_CLOSE);
+open_err:
+ q6asm_unmap_memory_regions(substream->stream, prtd->audio_client);
+
+ return ret;
+}
+
+static int q6asm_dai_ack(struct snd_soc_component *component, struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+ int i, ret = 0, avail_periods;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && prtd->state == Q6ASM_STREAM_RUNNING) {
+ avail_periods = (runtime->control->appl_ptr - prtd->queue_ptr)/runtime->period_size;
+ for (i = 0; i < avail_periods; i++) {
+ ret = q6asm_write_async(prtd->audio_client, prtd->stream_id,
+ prtd->pcm_count, 0, 0, 0);
+
+ if (ret < 0) {
+ dev_err(component->dev, "Error queuing playback buffer %d\n", ret);
+ return ret;
+ }
+ prtd->queue_ptr += runtime->period_size;
+ }
+ }
+
+ return ret;
+}
+
+static int q6asm_dai_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ int ret = 0;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = q6asm_run_nowait(prtd->audio_client, prtd->stream_id,
+ 0, 0, 0);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = q6asm_cmd_nowait(prtd->audio_client, prtd->stream_id,
+ CMD_EOS);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = q6asm_cmd_nowait(prtd->audio_client, prtd->stream_id,
+ CMD_PAUSE);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int q6asm_dai_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_prtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_prtd, 0);
+ struct q6asm_dai_rtd *prtd;
+ struct q6asm_dai_data *pdata;
+ struct device *dev = component->dev;
+ int ret = 0;
+ int stream_id;
+
+ stream_id = cpu_dai->driver->id;
+
+ pdata = snd_soc_component_get_drvdata(component);
+ if (!pdata) {
+ dev_err(dev, "Drv data not found ..\n");
+ return -EINVAL;
+ }
+
+ prtd = kzalloc_obj(*prtd);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ prtd->substream = substream;
+ prtd->audio_client = q6asm_audio_client_alloc(dev,
+ (q6asm_cb)event_handler, prtd, stream_id,
+ LEGACY_PCM_MODE);
+ if (IS_ERR(prtd->audio_client)) {
+ dev_info(dev, "%s: Could not allocate memory\n", __func__);
+ ret = PTR_ERR(prtd->audio_client);
+ kfree(prtd);
+ return ret;
+ }
+
+ /* DSP expects stream id from 1 */
+ prtd->stream_id = 1;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ runtime->hw = q6asm_dai_hardware_playback;
+ else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ runtime->hw = q6asm_dai_hardware_capture;
+
+ /* Ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ dev_info(dev, "snd_pcm_hw_constraint_integer failed\n");
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ret = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ PLAYBACK_MIN_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE,
+ PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE);
+ if (ret < 0) {
+ dev_err(dev, "constraint for buffer bytes min max ret = %d\n",
+ ret);
+ }
+ }
+
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 480);
+ if (ret < 0) {
+ dev_err(dev, "constraint for period bytes step ret = %d\n",
+ ret);
+ }
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 480);
+ if (ret < 0) {
+ dev_err(dev, "constraint for buffer bytes step ret = %d\n",
+ ret);
+ }
+
+ runtime->private_data = prtd;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ snd_soc_set_runtime_hwparams(substream, &q6asm_dai_hardware_playback);
+ runtime->dma_bytes = q6asm_dai_hardware_playback.buffer_bytes_max;
+ } else {
+ snd_soc_set_runtime_hwparams(substream, &q6asm_dai_hardware_capture);
+ runtime->dma_bytes = q6asm_dai_hardware_capture.buffer_bytes_max;
+ }
+
+ if (pdata->sid < 0)
+ prtd->phys = substream->dma_buffer.addr;
+ else
+ prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32);
+
+ return 0;
+}
+
+static int q6asm_dai_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_prtd = snd_soc_substream_to_rtd(substream);
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+
+ if (prtd->audio_client) {
+ if (prtd->state == Q6ASM_STREAM_RUNNING) {
+ q6asm_cmd(prtd->audio_client, prtd->stream_id,
+ CMD_CLOSE);
+ q6asm_unmap_memory_regions(substream->stream,
+ prtd->audio_client);
+ }
+ q6asm_audio_client_free(prtd->audio_client);
+ prtd->audio_client = NULL;
+ }
+ q6routing_stream_close(soc_prtd->dai_link->id,
+ substream->stream);
+ kfree(prtd);
+ return 0;
+}
+
+static snd_pcm_uframes_t q6asm_dai_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+ snd_pcm_uframes_t ptr;
+
+ ptr = q6asm_get_hw_pointer(prtd->audio_client, substream->stream) * runtime->period_size;
+ if (ptr)
+ return ptr - 1;
+
+ return 0;
+}
+
+static int q6asm_dai_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+
+ prtd->pcm_size = params_buffer_bytes(params);
+ prtd->periods = params_periods(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ prtd->bits_per_sample = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ prtd->bits_per_sample = 24;
+ break;
+ }
+
+ return 0;
+}
+
+static void compress_event_handler(uint32_t opcode, uint32_t token,
+ void *payload, void *priv)
+{
+ struct q6asm_dai_rtd *prtd = priv;
+ struct snd_compr_stream *substream = prtd->cstream;
+ u32 wflags = 0;
+ uint64_t avail;
+ uint32_t bytes_written, bytes_to_write;
+ bool is_last_buffer = false;
+
+ guard(spinlock_irqsave)(&prtd->lock);
+
+ switch (opcode) {
+ case ASM_CLIENT_EVENT_CMD_RUN_DONE:
+ if (!prtd->bytes_sent) {
+ q6asm_stream_remove_initial_silence(prtd->audio_client,
+ prtd->stream_id,
+ prtd->initial_samples_drop);
+
+ q6asm_write_async(prtd->audio_client, prtd->stream_id,
+ prtd->pcm_count, 0, 0, 0);
+ prtd->bytes_sent += prtd->pcm_count;
+ }
+
+ break;
+
+ case ASM_CLIENT_EVENT_CMD_EOS_DONE:
+ if (prtd->notify_on_drain) {
+ if (substream->partial_drain) {
+ /*
+ * Close old stream and make it stale, switch
+ * the active stream now!
+ */
+ q6asm_cmd_nowait(prtd->audio_client,
+ prtd->stream_id,
+ CMD_CLOSE);
+ /*
+ * vaild stream ids start from 1, So we are
+ * toggling this between 1 and 2.
+ */
+ prtd->stream_id = (prtd->stream_id == 1 ? 2 : 1);
+ }
+
+ snd_compr_drain_notify(prtd->cstream);
+ prtd->notify_on_drain = false;
+
+ }
+ break;
+
+ case ASM_CLIENT_EVENT_DATA_WRITE_DONE:
+
+ bytes_written = token >> ASM_WRITE_TOKEN_LEN_SHIFT;
+ prtd->copied_total += bytes_written;
+ snd_compr_fragment_elapsed(substream);
+
+ if (prtd->state != Q6ASM_STREAM_RUNNING)
+ break;
+
+ avail = prtd->bytes_received - prtd->bytes_sent;
+ if (avail > prtd->pcm_count) {
+ bytes_to_write = prtd->pcm_count;
+ } else {
+ if (substream->partial_drain || prtd->notify_on_drain)
+ is_last_buffer = true;
+ bytes_to_write = avail;
+ }
+
+ if (bytes_to_write) {
+ if (substream->partial_drain && is_last_buffer) {
+ wflags |= ASM_LAST_BUFFER_FLAG;
+ q6asm_stream_remove_trailing_silence(prtd->audio_client,
+ prtd->stream_id,
+ prtd->trailing_samples_drop);
+ }
+
+ q6asm_write_async(prtd->audio_client, prtd->stream_id,
+ bytes_to_write, 0, 0, wflags);
+
+ prtd->bytes_sent += bytes_to_write;
+ }
+
+ if (prtd->notify_on_drain && is_last_buffer)
+ q6asm_cmd_nowait(prtd->audio_client,
+ prtd->stream_id, CMD_EOS);
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int q6asm_dai_compr_open(struct snd_soc_component *component,
+ struct snd_compr_stream *stream)
+{
+ struct snd_soc_pcm_runtime *rtd = stream->private_data;
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct q6asm_dai_data *pdata;
+ struct device *dev = component->dev;
+ struct q6asm_dai_rtd *prtd;
+ int stream_id, size, ret;
+
+ stream_id = cpu_dai->driver->id;
+ pdata = snd_soc_component_get_drvdata(component);
+ if (!pdata) {
+ dev_err(dev, "Drv data not found ..\n");
+ return -EINVAL;
+ }
+
+ prtd = kzalloc_obj(*prtd);
+ if (!prtd)
+ return -ENOMEM;
+
+ /* DSP expects stream id from 1 */
+ prtd->stream_id = 1;
+
+ prtd->cstream = stream;
+ prtd->audio_client = q6asm_audio_client_alloc(dev,
+ (q6asm_cb)compress_event_handler,
+ prtd, stream_id, LEGACY_PCM_MODE);
+ if (IS_ERR(prtd->audio_client)) {
+ dev_err(dev, "Could not allocate memory\n");
+ ret = PTR_ERR(prtd->audio_client);
+ goto free_prtd;
+ }
+
+ size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE *
+ COMPR_PLAYBACK_MAX_NUM_FRAGMENTS;
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size,
+ &prtd->dma_buffer);
+ if (ret) {
+ dev_err(dev, "Cannot allocate buffer(s)\n");
+ goto free_client;
+ }
+
+ if (pdata->sid < 0)
+ prtd->phys = prtd->dma_buffer.addr;
+ else
+ prtd->phys = prtd->dma_buffer.addr | (pdata->sid << 32);
+
+ snd_compr_set_runtime_buffer(stream, &prtd->dma_buffer);
+ spin_lock_init(&prtd->lock);
+ runtime->private_data = prtd;
+
+ return 0;
+
+free_client:
+ q6asm_audio_client_free(prtd->audio_client);
+free_prtd:
+ kfree(prtd);
+
+ return ret;
+}
+
+static int q6asm_dai_compr_free(struct snd_soc_component *component,
+ struct snd_compr_stream *stream)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = stream->private_data;
+
+ if (prtd->audio_client) {
+ if (prtd->state == Q6ASM_STREAM_RUNNING) {
+ q6asm_cmd(prtd->audio_client, prtd->stream_id,
+ CMD_CLOSE);
+ if (prtd->next_track_stream_id) {
+ q6asm_cmd(prtd->audio_client,
+ prtd->next_track_stream_id,
+ CMD_CLOSE);
+ }
+
+ q6asm_unmap_memory_regions(stream->direction,
+ prtd->audio_client);
+ }
+ snd_dma_free_pages(&prtd->dma_buffer);
+ q6asm_audio_client_free(prtd->audio_client);
+ prtd->audio_client = NULL;
+ }
+ q6routing_stream_close(rtd->dai_link->id, stream->direction);
+ kfree(prtd);
+
+ return 0;
+}
+
+static int __q6asm_dai_compr_set_codec_params(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct snd_codec *codec,
+ int stream_id)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+ struct q6asm_flac_cfg flac_cfg;
+ struct q6asm_wma_cfg wma_cfg;
+ struct q6asm_alac_cfg alac_cfg;
+ struct q6asm_ape_cfg ape_cfg;
+ unsigned int wma_v9 = 0;
+ struct device *dev = component->dev;
+ int ret;
+ union snd_codec_options *codec_options;
+ struct snd_dec_flac *flac;
+ struct snd_dec_wma *wma;
+ struct snd_dec_alac *alac;
+ struct snd_dec_ape *ape;
+
+ codec_options = &(prtd->codec.options);
+
+ memcpy(&prtd->codec, codec, sizeof(*codec));
+
+ switch (codec->id) {
+ case SND_AUDIOCODEC_FLAC:
+
+ memset(&flac_cfg, 0x0, sizeof(struct q6asm_flac_cfg));
+ flac = &codec_options->flac_d;
+
+ flac_cfg.ch_cfg = codec->ch_in;
+ flac_cfg.sample_rate = codec->sample_rate;
+ flac_cfg.stream_info_present = 1;
+ flac_cfg.sample_size = flac->sample_size;
+ flac_cfg.min_blk_size = flac->min_blk_size;
+ flac_cfg.max_blk_size = flac->max_blk_size;
+ flac_cfg.max_frame_size = flac->max_frame_size;
+ flac_cfg.min_frame_size = flac->min_frame_size;
+
+ ret = q6asm_stream_media_format_block_flac(prtd->audio_client,
+ stream_id,
+ &flac_cfg);
+ if (ret < 0) {
+ dev_err(dev, "FLAC CMD Format block failed:%d\n", ret);
+ return -EIO;
+ }
+ break;
+
+ case SND_AUDIOCODEC_WMA:
+ wma = &codec_options->wma_d;
+
+ memset(&wma_cfg, 0x0, sizeof(struct q6asm_wma_cfg));
+
+ wma_cfg.sample_rate = codec->sample_rate;
+ wma_cfg.num_channels = codec->ch_in;
+ wma_cfg.bytes_per_sec = codec->bit_rate / 8;
+ wma_cfg.block_align = codec->align;
+ wma_cfg.bits_per_sample = prtd->bits_per_sample;
+ wma_cfg.enc_options = wma->encoder_option;
+ wma_cfg.adv_enc_options = wma->adv_encoder_option;
+ wma_cfg.adv_enc_options2 = wma->adv_encoder_option2;
+
+ if (wma_cfg.num_channels == 1)
+ wma_cfg.channel_mask = 4; /* Mono Center */
+ else if (wma_cfg.num_channels == 2)
+ wma_cfg.channel_mask = 3; /* Stereo FL/FR */
+ else
+ return -EINVAL;
+
+ /* check the codec profile */
+ switch (codec->profile) {
+ case SND_AUDIOPROFILE_WMA9:
+ wma_cfg.fmtag = 0x161;
+ wma_v9 = 1;
+ break;
+
+ case SND_AUDIOPROFILE_WMA10:
+ wma_cfg.fmtag = 0x166;
+ break;
+
+ case SND_AUDIOPROFILE_WMA9_PRO:
+ wma_cfg.fmtag = 0x162;
+ break;
+
+ case SND_AUDIOPROFILE_WMA9_LOSSLESS:
+ wma_cfg.fmtag = 0x163;
+ break;
+
+ case SND_AUDIOPROFILE_WMA10_LOSSLESS:
+ wma_cfg.fmtag = 0x167;
+ break;
+
+ default:
+ dev_err(dev, "Unknown WMA profile:%x\n",
+ codec->profile);
+ return -EIO;
+ }
+
+ if (wma_v9)
+ ret = q6asm_stream_media_format_block_wma_v9(
+ prtd->audio_client, stream_id,
+ &wma_cfg);
+ else
+ ret = q6asm_stream_media_format_block_wma_v10(
+ prtd->audio_client, stream_id,
+ &wma_cfg);
+ if (ret < 0) {
+ dev_err(dev, "WMA9 CMD failed:%d\n", ret);
+ return -EIO;
+ }
+ break;
+
+ case SND_AUDIOCODEC_ALAC:
+ memset(&alac_cfg, 0x0, sizeof(alac_cfg));
+ alac = &codec_options->alac_d;
+
+ alac_cfg.sample_rate = codec->sample_rate;
+ alac_cfg.avg_bit_rate = codec->bit_rate;
+ alac_cfg.bit_depth = prtd->bits_per_sample;
+ alac_cfg.num_channels = codec->ch_in;
+
+ alac_cfg.frame_length = alac->frame_length;
+ alac_cfg.pb = alac->pb;
+ alac_cfg.mb = alac->mb;
+ alac_cfg.kb = alac->kb;
+ alac_cfg.max_run = alac->max_run;
+ alac_cfg.compatible_version = alac->compatible_version;
+ alac_cfg.max_frame_bytes = alac->max_frame_bytes;
+
+ switch (codec->ch_in) {
+ case 1:
+ alac_cfg.channel_layout_tag = ALAC_CH_LAYOUT_MONO;
+ break;
+ case 2:
+ alac_cfg.channel_layout_tag = ALAC_CH_LAYOUT_STEREO;
+ break;
+ }
+ ret = q6asm_stream_media_format_block_alac(prtd->audio_client,
+ stream_id,
+ &alac_cfg);
+ if (ret < 0) {
+ dev_err(dev, "ALAC CMD Format block failed:%d\n", ret);
+ return -EIO;
+ }
+ break;
+
+ case SND_AUDIOCODEC_APE:
+ memset(&ape_cfg, 0x0, sizeof(ape_cfg));
+ ape = &codec_options->ape_d;
+
+ ape_cfg.sample_rate = codec->sample_rate;
+ ape_cfg.num_channels = codec->ch_in;
+ ape_cfg.bits_per_sample = prtd->bits_per_sample;
+
+ ape_cfg.compatible_version = ape->compatible_version;
+ ape_cfg.compression_level = ape->compression_level;
+ ape_cfg.format_flags = ape->format_flags;
+ ape_cfg.blocks_per_frame = ape->blocks_per_frame;
+ ape_cfg.final_frame_blocks = ape->final_frame_blocks;
+ ape_cfg.total_frames = ape->total_frames;
+ ape_cfg.seek_table_present = ape->seek_table_present;
+
+ ret = q6asm_stream_media_format_block_ape(prtd->audio_client,
+ stream_id,
+ &ape_cfg);
+ if (ret < 0) {
+ dev_err(dev, "APE CMD Format block failed:%d\n", ret);
+ return -EIO;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct snd_compr_params *params)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = stream->private_data;
+ int dir = stream->direction;
+ struct q6asm_dai_data *pdata;
+ struct device *dev = component->dev;
+ int ret;
+
+ pdata = snd_soc_component_get_drvdata(component);
+ if (!pdata)
+ return -EINVAL;
+
+ if (!prtd || !prtd->audio_client) {
+ dev_err(dev, "private data null or audio client freed\n");
+ return -EINVAL;
+ }
+
+ prtd->periods = runtime->fragments;
+ prtd->pcm_count = runtime->fragment_size;
+ prtd->pcm_size = runtime->fragments * runtime->fragment_size;
+ prtd->bits_per_sample = 16;
+
+ if (dir == SND_COMPRESS_PLAYBACK) {
+ ret = q6asm_open_write(prtd->audio_client, prtd->stream_id, params->codec.id,
+ params->codec.profile, prtd->bits_per_sample,
+ true);
+
+ if (ret < 0) {
+ dev_err(dev, "q6asm_open_write failed\n");
+ goto open_err;
+ }
+ }
+
+ prtd->session_id = q6asm_get_session_id(prtd->audio_client);
+ ret = q6routing_stream_open(rtd->dai_link->id, LEGACY_PCM_MODE,
+ prtd->session_id, dir);
+ if (ret) {
+ dev_err(dev, "Stream reg failed ret:%d\n", ret);
+ goto routing_err;
+ }
+
+ ret = __q6asm_dai_compr_set_codec_params(component, stream,
+ &params->codec,
+ prtd->stream_id);
+ if (ret) {
+ dev_err(dev, "codec param setup failed ret:%d\n", ret);
+ goto q6_err;
+ }
+
+ ret = q6asm_map_memory_regions(dir, prtd->audio_client, prtd->phys,
+ (prtd->pcm_size / prtd->periods),
+ prtd->periods);
+
+ if (ret < 0) {
+ dev_err(dev, "Buffer Mapping failed ret:%d\n", ret);
+ ret = -ENOMEM;
+ goto q6_err;
+ }
+
+ prtd->state = Q6ASM_STREAM_RUNNING;
+
+ return 0;
+
+q6_err:
+ q6routing_stream_close(rtd->dai_link->id, dir);
+routing_err:
+ q6asm_cmd(prtd->audio_client, prtd->stream_id, CMD_CLOSE);
+
+open_err:
+ return ret;
+}
+
+static int q6asm_dai_compr_set_metadata(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct snd_compr_metadata *metadata)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+ int ret = 0;
+
+ switch (metadata->key) {
+ case SNDRV_COMPRESS_ENCODER_PADDING:
+ prtd->trailing_samples_drop = metadata->value[0];
+ break;
+ case SNDRV_COMPRESS_ENCODER_DELAY:
+ prtd->initial_samples_drop = metadata->value[0];
+ if (prtd->next_track_stream_id) {
+ ret = q6asm_open_write(prtd->audio_client,
+ prtd->next_track_stream_id,
+ prtd->codec.id,
+ prtd->codec.profile,
+ prtd->bits_per_sample,
+ true);
+ if (ret < 0) {
+ dev_err(component->dev, "q6asm_open_write failed\n");
+ return ret;
+ }
+ ret = __q6asm_dai_compr_set_codec_params(component, stream,
+ &prtd->codec,
+ prtd->next_track_stream_id);
+ if (ret < 0) {
+ dev_err(component->dev, "q6asm_open_write failed\n");
+ return ret;
+ }
+
+ ret = q6asm_stream_remove_initial_silence(prtd->audio_client,
+ prtd->next_track_stream_id,
+ prtd->initial_samples_drop);
+ prtd->next_track_stream_id = 0;
+
+ }
+
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int q6asm_dai_compr_trigger(struct snd_soc_component *component,
+ struct snd_compr_stream *stream, int cmd)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = q6asm_run_nowait(prtd->audio_client, prtd->stream_id,
+ 0, 0, 0);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = q6asm_cmd_nowait(prtd->audio_client, prtd->stream_id,
+ CMD_EOS);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = q6asm_cmd_nowait(prtd->audio_client, prtd->stream_id,
+ CMD_PAUSE);
+ break;
+ case SND_COMPR_TRIGGER_NEXT_TRACK:
+ prtd->next_track = true;
+ prtd->next_track_stream_id = (prtd->stream_id == 1 ? 2 : 1);
+ break;
+ case SND_COMPR_TRIGGER_DRAIN:
+ case SND_COMPR_TRIGGER_PARTIAL_DRAIN:
+ prtd->notify_on_drain = true;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int q6asm_dai_compr_pointer(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct snd_compr_tstamp64 *tstamp)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+ uint64_t temp_copied_total;
+
+ guard(spinlock_irqsave)(&prtd->lock);
+
+ tstamp->copied_total = prtd->copied_total;
+ temp_copied_total = tstamp->copied_total;
+ tstamp->byte_offset = do_div(temp_copied_total, prtd->pcm_size);
+
+ return 0;
+}
+
+static int q6asm_compr_copy(struct snd_soc_component *component,
+ struct snd_compr_stream *stream, char __user *buf,
+ size_t count)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+ u32 wflags = 0;
+ uint64_t avail, bytes_in_flight = 0;
+ void *dstn;
+ size_t copy;
+ u32 app_pointer;
+ uint64_t bytes_received;
+ uint64_t temp_bytes_received;
+
+ bytes_received = prtd->bytes_received;
+ temp_bytes_received = bytes_received;
+
+ /**
+ * Make sure that next track data pointer is aligned at 32 bit boundary
+ * This is a Mandatory requirement from DSP data buffers alignment
+ */
+ if (prtd->next_track) {
+ bytes_received = ALIGN(prtd->bytes_received, prtd->pcm_count);
+ temp_bytes_received = bytes_received;
+ }
+
+ app_pointer = do_div(temp_bytes_received, prtd->pcm_size);
+ dstn = prtd->dma_buffer.area + app_pointer;
+
+ if (count < prtd->pcm_size - app_pointer) {
+ if (copy_from_user(dstn, buf, count))
+ return -EFAULT;
+ } else {
+ copy = prtd->pcm_size - app_pointer;
+ if (copy_from_user(dstn, buf, copy))
+ return -EFAULT;
+ if (copy_from_user(prtd->dma_buffer.area, buf + copy,
+ count - copy))
+ return -EFAULT;
+ }
+
+ guard(spinlock_irqsave)(&prtd->lock);
+
+ bytes_in_flight = prtd->bytes_received - prtd->copied_total;
+
+ if (prtd->next_track) {
+ prtd->next_track = false;
+ prtd->copied_total = ALIGN(prtd->copied_total, prtd->pcm_count);
+ prtd->bytes_sent = ALIGN(prtd->bytes_sent, prtd->pcm_count);
+ }
+
+ prtd->bytes_received = bytes_received + count;
+
+ /* Kick off the data to dsp if its starving!! */
+ if (prtd->state == Q6ASM_STREAM_RUNNING && (bytes_in_flight == 0)) {
+ uint32_t bytes_to_write = prtd->pcm_count;
+
+ avail = prtd->bytes_received - prtd->bytes_sent;
+
+ if (avail < prtd->pcm_count)
+ bytes_to_write = avail;
+
+ q6asm_write_async(prtd->audio_client, prtd->stream_id,
+ bytes_to_write, 0, 0, wflags);
+ prtd->bytes_sent += bytes_to_write;
+ }
+
+ return count;
+}
+
+static int q6asm_dai_compr_mmap(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct vm_area_struct *vma)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+ struct device *dev = component->dev;
+
+ return dma_mmap_coherent(dev, vma,
+ prtd->dma_buffer.area, prtd->dma_buffer.addr,
+ prtd->dma_buffer.bytes);
+}
+
+static int q6asm_dai_compr_get_caps(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct snd_compr_caps *caps)
+{
+ caps->direction = SND_COMPRESS_PLAYBACK;
+ caps->min_fragment_size = COMPR_PLAYBACK_MIN_FRAGMENT_SIZE;
+ caps->max_fragment_size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE;
+ caps->min_fragments = COMPR_PLAYBACK_MIN_NUM_FRAGMENTS;
+ caps->max_fragments = COMPR_PLAYBACK_MAX_NUM_FRAGMENTS;
+ caps->num_codecs = 5;
+ caps->codecs[0] = SND_AUDIOCODEC_MP3;
+ caps->codecs[1] = SND_AUDIOCODEC_FLAC;
+ caps->codecs[2] = SND_AUDIOCODEC_WMA;
+ caps->codecs[3] = SND_AUDIOCODEC_ALAC;
+ caps->codecs[4] = SND_AUDIOCODEC_APE;
+
+ return 0;
+}
+
+static int q6asm_dai_compr_get_codec_caps(struct snd_soc_component *component,
+ struct snd_compr_stream *stream,
+ struct snd_compr_codec_caps *codec)
+{
+ switch (codec->codec) {
+ case SND_AUDIOCODEC_MP3:
+ *codec = q6asm_compr_caps;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct snd_compress_ops q6asm_dai_compress_ops = {
+ .open = q6asm_dai_compr_open,
+ .free = q6asm_dai_compr_free,
+ .set_params = q6asm_dai_compr_set_params,
+ .set_metadata = q6asm_dai_compr_set_metadata,
+ .pointer = q6asm_dai_compr_pointer,
+ .trigger = q6asm_dai_compr_trigger,
+ .get_caps = q6asm_dai_compr_get_caps,
+ .get_codec_caps = q6asm_dai_compr_get_codec_caps,
+ .mmap = q6asm_dai_compr_mmap,
+ .copy = q6asm_compr_copy,
+};
+
+static int q6asm_dai_pcm_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_pcm *pcm = rtd->pcm;
+ size_t size = q6asm_dai_hardware_playback.buffer_bytes_max;
+
+ return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ component->dev, size);
+}
+
+static const struct snd_soc_dapm_widget q6asm_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("MM_DL1", "MultiMedia1 Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("MM_DL2", "MultiMedia2 Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("MM_DL3", "MultiMedia3 Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("MM_DL4", "MultiMedia4 Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("MM_DL5", "MultiMedia5 Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("MM_DL6", "MultiMedia6 Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("MM_DL7", "MultiMedia7 Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("MM_DL8", "MultiMedia8 Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("MM_UL1", "MultiMedia1 Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("MM_UL2", "MultiMedia2 Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("MM_UL3", "MultiMedia3 Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("MM_UL4", "MultiMedia4 Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("MM_UL5", "MultiMedia5 Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("MM_UL6", "MultiMedia6 Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("MM_UL7", "MultiMedia7 Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("MM_UL8", "MultiMedia8 Capture", 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_component_driver q6asm_fe_dai_component = {
+ .name = DRV_NAME,
+ .open = q6asm_dai_open,
+ .hw_params = q6asm_dai_hw_params,
+ .close = q6asm_dai_close,
+ .prepare = q6asm_dai_prepare,
+ .trigger = q6asm_dai_trigger,
+ .ack = q6asm_dai_ack,
+ .pointer = q6asm_dai_pointer,
+ .pcm_new = q6asm_dai_pcm_new,
+ .compress_ops = &q6asm_dai_compress_ops,
+ .dapm_widgets = q6asm_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(q6asm_dapm_widgets),
+ .legacy_dai_naming = 1,
+};
+
+static struct snd_soc_dai_driver q6asm_fe_dais_template[] = {
+ Q6ASM_FEDAI_DRIVER(1),
+ Q6ASM_FEDAI_DRIVER(2),
+ Q6ASM_FEDAI_DRIVER(3),
+ Q6ASM_FEDAI_DRIVER(4),
+ Q6ASM_FEDAI_DRIVER(5),
+ Q6ASM_FEDAI_DRIVER(6),
+ Q6ASM_FEDAI_DRIVER(7),
+ Q6ASM_FEDAI_DRIVER(8),
+};
+
+static const struct snd_soc_dai_ops q6asm_dai_ops = {
+ .compress_new = snd_soc_new_compress,
+};
+
+static int of_q6asm_parse_dai_data(struct device *dev,
+ struct q6asm_dai_data *pdata)
+{
+ struct snd_soc_dai_driver *dai_drv;
+ struct snd_soc_pcm_stream empty_stream;
+ struct device_node *node;
+ int ret, id, dir, idx = 0;
+
+
+ pdata->num_dais = of_get_child_count(dev->of_node);
+ if (!pdata->num_dais) {
+ dev_err(dev, "No dais found in DT\n");
+ return -EINVAL;
+ }
+
+ pdata->dais = devm_kcalloc(dev, pdata->num_dais, sizeof(*dai_drv),
+ GFP_KERNEL);
+ if (!pdata->dais)
+ return -ENOMEM;
+
+ memset(&empty_stream, 0, sizeof(empty_stream));
+
+ for_each_child_of_node(dev->of_node, node) {
+ ret = of_property_read_u32(node, "reg", &id);
+ if (ret || id >= MAX_SESSIONS || id < 0) {
+ dev_err(dev, "valid dai id not found:%d\n", ret);
+ continue;
+ }
+
+ dai_drv = &pdata->dais[idx++];
+ *dai_drv = q6asm_fe_dais_template[id];
+
+ ret = of_property_read_u32(node, "direction", &dir);
+ if (ret)
+ continue;
+
+ if (dir == Q6ASM_DAI_RX)
+ dai_drv->capture = empty_stream;
+ else if (dir == Q6ASM_DAI_TX)
+ dai_drv->playback = empty_stream;
+
+ if (of_property_read_bool(node, "is-compress-dai"))
+ dai_drv->ops = &q6asm_dai_ops;
+ }
+
+ return 0;
+}
+
+static int q6asm_dai_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+ struct of_phandle_args args;
+ struct q6asm_dai_data *pdata;
+ int rc;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
+ if (rc < 0)
+ pdata->sid = -1;
+ else
+ pdata->sid = args.args[0] & SID_MASK_DEFAULT;
+
+ dev_set_drvdata(dev, pdata);
+
+ rc = of_q6asm_parse_dai_data(dev, pdata);
+ if (rc)
+ return rc;
+
+ return devm_snd_soc_register_component(dev, &q6asm_fe_dai_component,
+ pdata->dais, pdata->num_dais);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id q6asm_dai_device_id[] = {
+ { .compatible = "qcom,q6asm-dais" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6asm_dai_device_id);
+#endif
+
+static struct platform_driver q6asm_dai_platform_driver = {
+ .driver = {
+ .name = "q6asm-dai",
+ .of_match_table = of_match_ptr(q6asm_dai_device_id),
+ },
+ .probe = q6asm_dai_probe,
+};
+module_platform_driver(q6asm_dai_platform_driver);
+
+MODULE_DESCRIPTION("Q6ASM dai driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c
new file mode 100644
index 000000000..de0bd8fd0
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6asm.c
@@ -0,0 +1,1687 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include <dt-bindings/sound/qcom,q6asm.h>
+#include <linux/mutex.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <linux/atomic.h>
+#include <linux/soc/qcom/apr.h>
+#include <linux/device.h>
+#include <linux/of_platform.h>
+#include <linux/spinlock.h>
+#include <linux/kref.h>
+#include <linux/of.h>
+#include <uapi/sound/asound.h>
+#include <uapi/sound/compress_params.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include "q6asm.h"
+#include "q6core.h"
+#include "q6dsp-errno.h"
+#include "q6dsp-common.h"
+
+#define ASM_STREAM_CMD_CLOSE 0x00010BCD
+#define ASM_STREAM_CMD_FLUSH 0x00010BCE
+#define ASM_SESSION_CMD_PAUSE 0x00010BD3
+#define ASM_DATA_CMD_EOS 0x00010BDB
+#define ASM_DATA_EVENT_RENDERED_EOS 0x00010C1C
+#define ASM_NULL_POPP_TOPOLOGY 0x00010C68
+#define ASM_STREAM_CMD_FLUSH_READBUFS 0x00010C09
+#define ASM_STREAM_CMD_SET_ENCDEC_PARAM 0x00010C10
+#define ASM_STREAM_POSTPROC_TOPO_ID_NONE 0x00010C68
+#define ASM_CMD_SHARED_MEM_MAP_REGIONS 0x00010D92
+#define ASM_CMDRSP_SHARED_MEM_MAP_REGIONS 0x00010D93
+#define ASM_CMD_SHARED_MEM_UNMAP_REGIONS 0x00010D94
+#define ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2 0x00010D98
+#define ASM_DATA_EVENT_WRITE_DONE_V2 0x00010D99
+#define ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2 0x00010DA3
+#define ASM_SESSION_CMD_RUN_V2 0x00010DAA
+#define ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 0x00010DA5
+#define ASM_MEDIA_FMT_MP3 0x00010BE9
+#define ASM_MEDIA_FMT_FLAC 0x00010C16
+#define ASM_MEDIA_FMT_WMA_V9 0x00010DA8
+#define ASM_MEDIA_FMT_WMA_V10 0x00010DA7
+#define ASM_DATA_CMD_WRITE_V2 0x00010DAB
+#define ASM_DATA_CMD_READ_V2 0x00010DAC
+#define ASM_SESSION_CMD_SUSPEND 0x00010DEC
+#define ASM_STREAM_CMD_OPEN_WRITE_V3 0x00010DB3
+#define ASM_STREAM_CMD_OPEN_READ_V3 0x00010DB4
+#define ASM_DATA_EVENT_READ_DONE_V2 0x00010D9A
+#define ASM_STREAM_CMD_OPEN_READWRITE_V2 0x00010D8D
+#define ASM_MEDIA_FMT_ALAC 0x00012f31
+#define ASM_MEDIA_FMT_APE 0x00012f32
+#define ASM_DATA_CMD_REMOVE_INITIAL_SILENCE 0x00010D67
+#define ASM_DATA_CMD_REMOVE_TRAILING_SILENCE 0x00010D68
+
+
+#define ASM_LEGACY_STREAM_SESSION 0
+/* Bit shift for the stream_perf_mode subfield. */
+#define ASM_SHIFT_STREAM_PERF_MODE_FLAG_IN_OPEN_READ 29
+#define ASM_END_POINT_DEVICE_MATRIX 0
+#define ASM_DEFAULT_APP_TYPE 0
+#define ASM_SYNC_IO_MODE 0x0001
+#define ASM_ASYNC_IO_MODE 0x0002
+#define ASM_TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */
+#define ASM_TUN_WRITE_IO_MODE 0x0008 /* tunnel read write mode */
+#define ASM_SHIFT_GAPLESS_MODE_FLAG 31
+#define ADSP_MEMORY_MAP_SHMEM8_4K_POOL 3
+
+struct avs_cmd_shared_mem_map_regions {
+ u16 mem_pool_id;
+ u16 num_regions;
+ u32 property_flag;
+} __packed;
+
+struct avs_shared_map_region_payload {
+ u32 shm_addr_lsw;
+ u32 shm_addr_msw;
+ u32 mem_size_bytes;
+} __packed;
+
+struct avs_cmd_shared_mem_unmap_regions {
+ u32 mem_map_handle;
+} __packed;
+
+struct asm_data_cmd_media_fmt_update_v2 {
+ u32 fmt_blk_size;
+} __packed;
+
+struct asm_multi_channel_pcm_fmt_blk_v2 {
+ struct asm_data_cmd_media_fmt_update_v2 fmt_blk;
+ u16 num_channels;
+ u16 bits_per_sample;
+ u32 sample_rate;
+ u16 is_signed;
+ u16 reserved;
+ u8 channel_mapping[PCM_MAX_NUM_CHANNEL];
+} __packed;
+
+struct asm_flac_fmt_blk_v2 {
+ struct asm_data_cmd_media_fmt_update_v2 fmt_blk;
+ u16 is_stream_info_present;
+ u16 num_channels;
+ u16 min_blk_size;
+ u16 max_blk_size;
+ u16 md5_sum[8];
+ u32 sample_rate;
+ u32 min_frame_size;
+ u32 max_frame_size;
+ u16 sample_size;
+ u16 reserved;
+} __packed;
+
+struct asm_wmastdv9_fmt_blk_v2 {
+ struct asm_data_cmd_media_fmt_update_v2 fmt_blk;
+ u16 fmtag;
+ u16 num_channels;
+ u32 sample_rate;
+ u32 bytes_per_sec;
+ u16 blk_align;
+ u16 bits_per_sample;
+ u32 channel_mask;
+ u16 enc_options;
+ u16 reserved;
+} __packed;
+
+struct asm_wmaprov10_fmt_blk_v2 {
+ struct asm_data_cmd_media_fmt_update_v2 fmt_blk;
+ u16 fmtag;
+ u16 num_channels;
+ u32 sample_rate;
+ u32 bytes_per_sec;
+ u16 blk_align;
+ u16 bits_per_sample;
+ u32 channel_mask;
+ u16 enc_options;
+ u16 advanced_enc_options1;
+ u32 advanced_enc_options2;
+} __packed;
+
+struct asm_alac_fmt_blk_v2 {
+ struct asm_data_cmd_media_fmt_update_v2 fmt_blk;
+ u32 frame_length;
+ u8 compatible_version;
+ u8 bit_depth;
+ u8 pb;
+ u8 mb;
+ u8 kb;
+ u8 num_channels;
+ u16 max_run;
+ u32 max_frame_bytes;
+ u32 avg_bit_rate;
+ u32 sample_rate;
+ u32 channel_layout_tag;
+} __packed;
+
+struct asm_ape_fmt_blk_v2 {
+ struct asm_data_cmd_media_fmt_update_v2 fmt_blk;
+ u16 compatible_version;
+ u16 compression_level;
+ u32 format_flags;
+ u32 blocks_per_frame;
+ u32 final_frame_blocks;
+ u32 total_frames;
+ u16 bits_per_sample;
+ u16 num_channels;
+ u32 sample_rate;
+ u32 seek_table_present;
+} __packed;
+
+struct asm_stream_cmd_set_encdec_param {
+ u32 param_id;
+ u32 param_size;
+} __packed;
+
+struct asm_enc_cfg_blk_param_v2 {
+ u32 frames_per_buf;
+ u32 enc_cfg_blk_size;
+} __packed;
+
+struct asm_multi_channel_pcm_enc_cfg_v2 {
+ struct asm_stream_cmd_set_encdec_param encdec;
+ struct asm_enc_cfg_blk_param_v2 encblk;
+ uint16_t num_channels;
+ uint16_t bits_per_sample;
+ uint32_t sample_rate;
+ uint16_t is_signed;
+ uint16_t reserved;
+ uint8_t channel_mapping[8];
+} __packed;
+
+struct asm_data_cmd_read_v2 {
+ u32 buf_addr_lsw;
+ u32 buf_addr_msw;
+ u32 mem_map_handle;
+ u32 buf_size;
+ u32 seq_id;
+} __packed;
+
+struct asm_data_cmd_read_v2_done {
+ u32 status;
+ u32 buf_addr_lsw;
+ u32 buf_addr_msw;
+};
+
+struct asm_stream_cmd_open_read_v3 {
+ u32 mode_flags;
+ u32 src_endpointype;
+ u32 preprocopo_id;
+ u32 enc_cfg_id;
+ u16 bits_per_sample;
+ u16 reserved;
+} __packed;
+
+struct asm_data_cmd_write_v2 {
+ u32 buf_addr_lsw;
+ u32 buf_addr_msw;
+ u32 mem_map_handle;
+ u32 buf_size;
+ u32 seq_id;
+ u32 timestamp_lsw;
+ u32 timestamp_msw;
+ u32 flags;
+} __packed;
+
+struct asm_stream_cmd_open_write_v3 {
+ uint32_t mode_flags;
+ uint16_t sink_endpointype;
+ uint16_t bits_per_sample;
+ uint32_t postprocopo_id;
+ uint32_t dec_fmt_id;
+} __packed;
+
+struct asm_session_cmd_run_v2 {
+ u32 flags;
+ u32 time_lsw;
+ u32 time_msw;
+} __packed;
+
+struct audio_buffer {
+ phys_addr_t phys;
+ uint32_t size; /* size of buffer */
+};
+
+struct audio_port_data {
+ struct audio_buffer *buf;
+ uint32_t num_periods;
+ uint32_t dsp_buf;
+ uint32_t mem_map_handle;
+ atomic_t hw_ptr;
+};
+
+struct q6asm {
+ struct apr_device *adev;
+ struct device *dev;
+ struct q6core_svc_api_info ainfo;
+ wait_queue_head_t mem_wait;
+ spinlock_t slock;
+ struct audio_client *session[MAX_SESSIONS + 1];
+};
+
+struct audio_client {
+ int session;
+ q6asm_cb cb;
+ void *priv;
+ uint32_t io_mode;
+ struct apr_device *adev;
+ struct mutex cmd_lock;
+ spinlock_t lock;
+ struct kref refcount;
+ /* idx:1 out port, 0: in port */
+ struct audio_port_data port[2];
+ wait_queue_head_t cmd_wait;
+ struct aprv2_ibasic_rsp_result_t result;
+ int perf_mode;
+ struct q6asm *q6asm;
+ struct device *dev;
+};
+
+static inline void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr,
+ uint32_t pkt_size, bool cmd_flg,
+ uint32_t stream_id)
+{
+ hdr->hdr_field = APR_SEQ_CMD_HDR_FIELD;
+ hdr->src_port = ((ac->session << 8) & 0xFF00) | (stream_id);
+ hdr->dest_port = ((ac->session << 8) & 0xFF00) | (stream_id);
+ hdr->pkt_size = pkt_size;
+ if (cmd_flg)
+ hdr->token = ac->session;
+}
+
+static int q6asm_apr_send_session_pkt(struct q6asm *a, struct audio_client *ac,
+ struct apr_pkt *pkt, uint32_t rsp_opcode)
+{
+ struct apr_hdr *hdr = &pkt->hdr;
+ int rc;
+
+ mutex_lock(&ac->cmd_lock);
+ ac->result.opcode = 0;
+ ac->result.status = 0;
+ rc = apr_send_pkt(a->adev, pkt);
+ if (rc < 0)
+ goto err;
+
+ if (rsp_opcode)
+ rc = wait_event_timeout(a->mem_wait,
+ (ac->result.opcode == hdr->opcode) ||
+ (ac->result.opcode == rsp_opcode),
+ 5 * HZ);
+ else
+ rc = wait_event_timeout(a->mem_wait,
+ (ac->result.opcode == hdr->opcode),
+ 5 * HZ);
+
+ if (!rc) {
+ dev_err(a->dev, "CMD %x timeout\n", hdr->opcode);
+ rc = -ETIMEDOUT;
+ } else if (ac->result.status > 0) {
+ dev_err(a->dev, "DSP returned error[%x]\n",
+ ac->result.status);
+ rc = -EINVAL;
+ }
+
+err:
+ mutex_unlock(&ac->cmd_lock);
+ return rc;
+}
+
+static int __q6asm_memory_unmap(struct audio_client *ac,
+ phys_addr_t buf_add, int dir)
+{
+ struct avs_cmd_shared_mem_unmap_regions *mem_unmap;
+ struct q6asm *a = dev_get_drvdata(ac->dev->parent);
+ struct apr_pkt *pkt;
+ int rc, pkt_size;
+
+ if (ac->port[dir].mem_map_handle == 0) {
+ dev_err(ac->dev, "invalid mem handle\n");
+ return -EINVAL;
+ }
+
+ pkt_size = APR_HDR_SIZE + sizeof(*mem_unmap);
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ mem_unmap = p + APR_HDR_SIZE;
+
+ pkt->hdr.hdr_field = APR_SEQ_CMD_HDR_FIELD;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.token = ((ac->session << 8) | dir);
+
+ pkt->hdr.opcode = ASM_CMD_SHARED_MEM_UNMAP_REGIONS;
+ mem_unmap->mem_map_handle = ac->port[dir].mem_map_handle;
+
+ rc = q6asm_apr_send_session_pkt(a, ac, pkt, 0);
+ if (rc < 0)
+ return rc;
+
+ ac->port[dir].mem_map_handle = 0;
+
+ return 0;
+}
+
+
+static void q6asm_audio_client_free_buf(struct audio_client *ac,
+ struct audio_port_data *port)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ac->lock, flags);
+ port->num_periods = 0;
+ spin_unlock_irqrestore(&ac->lock, flags);
+ kfree(port->buf);
+ port->buf = NULL;
+}
+
+/**
+ * q6asm_unmap_memory_regions() - unmap memory regions in the dsp.
+ *
+ * @dir: direction of audio stream
+ * @ac: audio client instanace
+ *
+ * Return: Will be an negative value on failure or zero on success
+ */
+int q6asm_unmap_memory_regions(unsigned int dir, struct audio_client *ac)
+{
+ struct audio_port_data *port;
+ int cnt = 0;
+ int rc = 0;
+
+ port = &ac->port[dir];
+ if (!port->buf) {
+ rc = -EINVAL;
+ goto err;
+ }
+
+ cnt = port->num_periods - 1;
+ if (cnt >= 0) {
+ rc = __q6asm_memory_unmap(ac, port->buf[dir].phys, dir);
+ if (rc < 0) {
+ dev_err(ac->dev, "%s: Memory_unmap_regions failed %d\n",
+ __func__, rc);
+ goto err;
+ }
+ }
+
+ q6asm_audio_client_free_buf(ac, port);
+
+err:
+ return rc;
+}
+EXPORT_SYMBOL_GPL(q6asm_unmap_memory_regions);
+
+static int __q6asm_memory_map_regions(struct audio_client *ac, int dir,
+ size_t period_sz, unsigned int periods,
+ bool is_contiguous)
+{
+ struct avs_cmd_shared_mem_map_regions *cmd = NULL;
+ struct avs_shared_map_region_payload *mregions = NULL;
+ struct q6asm *a = dev_get_drvdata(ac->dev->parent);
+ struct audio_port_data *port = NULL;
+ struct audio_buffer *ab = NULL;
+ struct apr_pkt *pkt;
+ unsigned long flags;
+ uint32_t num_regions, buf_sz;
+ int i, pkt_size;
+
+ if (is_contiguous) {
+ num_regions = 1;
+ buf_sz = period_sz * periods;
+ } else {
+ buf_sz = period_sz;
+ num_regions = periods;
+ }
+
+ /* DSP expects size should be aligned to 4K */
+ buf_sz = ALIGN(buf_sz, 4096);
+
+ pkt_size = APR_HDR_SIZE + sizeof(*cmd) +
+ (sizeof(*mregions) * num_regions);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ cmd = p + APR_HDR_SIZE;
+ mregions = p + APR_HDR_SIZE + sizeof(*cmd);
+
+ pkt->hdr.hdr_field = APR_SEQ_CMD_HDR_FIELD;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.token = ((ac->session << 8) | dir);
+ pkt->hdr.opcode = ASM_CMD_SHARED_MEM_MAP_REGIONS;
+
+ cmd->mem_pool_id = ADSP_MEMORY_MAP_SHMEM8_4K_POOL;
+ cmd->num_regions = num_regions;
+ cmd->property_flag = 0x00;
+
+ spin_lock_irqsave(&ac->lock, flags);
+ port = &ac->port[dir];
+
+ for (i = 0; i < num_regions; i++) {
+ ab = &port->buf[i];
+ mregions->shm_addr_lsw = lower_32_bits(ab->phys);
+ mregions->shm_addr_msw = upper_32_bits(ab->phys);
+ mregions->mem_size_bytes = buf_sz;
+ ++mregions;
+ }
+ spin_unlock_irqrestore(&ac->lock, flags);
+
+ return q6asm_apr_send_session_pkt(a, ac, pkt, ASM_CMDRSP_SHARED_MEM_MAP_REGIONS);
+}
+
+/**
+ * q6asm_map_memory_regions() - map memory regions in the dsp.
+ *
+ * @dir: direction of audio stream
+ * @ac: audio client instanace
+ * @phys: physical address that needs mapping.
+ * @period_sz: audio period size
+ * @periods: number of periods
+ *
+ * Return: Will be an negative value on failure or zero on success
+ */
+int q6asm_map_memory_regions(unsigned int dir, struct audio_client *ac,
+ phys_addr_t phys,
+ size_t period_sz, unsigned int periods)
+{
+ struct audio_buffer *buf;
+ unsigned long flags;
+ int cnt;
+ int rc;
+
+ spin_lock_irqsave(&ac->lock, flags);
+ if (ac->port[dir].buf) {
+ dev_err(ac->dev, "Buffer already allocated\n");
+ spin_unlock_irqrestore(&ac->lock, flags);
+ return 0;
+ }
+
+ buf = kzalloc_objs(*buf, periods, GFP_ATOMIC);
+ if (!buf) {
+ spin_unlock_irqrestore(&ac->lock, flags);
+ return -ENOMEM;
+ }
+
+
+ ac->port[dir].buf = buf;
+
+ buf[0].phys = phys;
+ buf[0].size = period_sz;
+
+ for (cnt = 1; cnt < periods; cnt++) {
+ if (period_sz > 0) {
+ buf[cnt].phys = buf[0].phys + (cnt * period_sz);
+ buf[cnt].size = period_sz;
+ }
+ }
+ ac->port[dir].num_periods = periods;
+
+ spin_unlock_irqrestore(&ac->lock, flags);
+
+ rc = __q6asm_memory_map_regions(ac, dir, period_sz, periods, 1);
+ if (rc < 0) {
+ dev_err(ac->dev, "Memory_map_regions failed\n");
+ q6asm_audio_client_free_buf(ac, &ac->port[dir]);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(q6asm_map_memory_regions);
+
+static void q6asm_audio_client_release(struct kref *ref)
+{
+ struct audio_client *ac;
+ struct q6asm *a;
+ unsigned long flags;
+
+ ac = container_of(ref, struct audio_client, refcount);
+ a = ac->q6asm;
+
+ spin_lock_irqsave(&a->slock, flags);
+ a->session[ac->session] = NULL;
+ spin_unlock_irqrestore(&a->slock, flags);
+
+ kfree(ac);
+}
+
+/**
+ * q6asm_audio_client_free() - Freee allocated audio client
+ *
+ * @ac: audio client to free
+ */
+void q6asm_audio_client_free(struct audio_client *ac)
+{
+ kref_put(&ac->refcount, q6asm_audio_client_release);
+}
+EXPORT_SYMBOL_GPL(q6asm_audio_client_free);
+
+static struct audio_client *q6asm_get_audio_client(struct q6asm *a,
+ int session_id)
+{
+ struct audio_client *ac = NULL;
+
+ guard(spinlock_irqsave)(&a->slock);
+ if ((session_id <= 0) || (session_id > MAX_SESSIONS)) {
+ dev_err(a->dev, "invalid session: %d\n", session_id);
+ goto err;
+ }
+
+ /* check for valid session */
+ if (!a->session[session_id])
+ goto err;
+ else if (a->session[session_id]->session != session_id)
+ goto err;
+
+ ac = a->session[session_id];
+ kref_get(&ac->refcount);
+err:
+ return ac;
+}
+
+int q6asm_get_hw_pointer(struct audio_client *ac, unsigned int dir)
+{
+ struct audio_port_data *data = &ac->port[dir];
+
+ return (int)atomic_read(&data->hw_ptr);
+}
+EXPORT_SYMBOL_GPL(q6asm_get_hw_pointer);
+
+static int32_t q6asm_stream_callback(struct apr_device *adev,
+ const struct apr_resp_pkt *data,
+ int session_id)
+{
+ struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
+ const struct aprv2_ibasic_rsp_result_t *result;
+ const struct apr_hdr *hdr = &data->hdr;
+ struct audio_port_data *port;
+ struct audio_client *ac;
+ uint32_t client_event = 0;
+ int ret = 0;
+
+ ac = q6asm_get_audio_client(q6asm, session_id);
+ if (!ac)/* Audio client might already be freed by now */
+ return 0;
+
+ result = data->payload;
+
+ switch (hdr->opcode) {
+ case APR_BASIC_RSP_RESULT:
+ switch (result->opcode) {
+ case ASM_SESSION_CMD_PAUSE:
+ client_event = ASM_CLIENT_EVENT_CMD_PAUSE_DONE;
+ break;
+ case ASM_SESSION_CMD_SUSPEND:
+ client_event = ASM_CLIENT_EVENT_CMD_SUSPEND_DONE;
+ break;
+ case ASM_STREAM_CMD_FLUSH:
+ client_event = ASM_CLIENT_EVENT_CMD_FLUSH_DONE;
+ break;
+ case ASM_SESSION_CMD_RUN_V2:
+ client_event = ASM_CLIENT_EVENT_CMD_RUN_DONE;
+ break;
+ case ASM_STREAM_CMD_CLOSE:
+ client_event = ASM_CLIENT_EVENT_CMD_CLOSE_DONE;
+ break;
+ case ASM_STREAM_CMD_FLUSH_READBUFS:
+ client_event = ASM_CLIENT_EVENT_CMD_OUT_FLUSH_DONE;
+ break;
+ case ASM_STREAM_CMD_OPEN_WRITE_V3:
+ case ASM_STREAM_CMD_OPEN_READ_V3:
+ case ASM_STREAM_CMD_OPEN_READWRITE_V2:
+ case ASM_STREAM_CMD_SET_ENCDEC_PARAM:
+ case ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2:
+ case ASM_DATA_CMD_REMOVE_INITIAL_SILENCE:
+ case ASM_DATA_CMD_REMOVE_TRAILING_SILENCE:
+ if (result->status != 0) {
+ dev_err(ac->dev,
+ "cmd = 0x%x returned error = 0x%x\n",
+ result->opcode, result->status);
+ ac->result = *result;
+ wake_up(&ac->cmd_wait);
+ ret = 0;
+ goto done;
+ }
+ break;
+ case ASM_DATA_CMD_EOS:
+ case ASM_DATA_CMD_READ_V2:
+ case ASM_DATA_CMD_WRITE_V2:
+ /* response as result of close stream */
+ goto done;
+ default:
+ dev_err(ac->dev, "command[0x%x] not expecting rsp\n",
+ result->opcode);
+ break;
+ }
+
+ ac->result = *result;
+ wake_up(&ac->cmd_wait);
+
+ if (ac->cb)
+ ac->cb(client_event, hdr->token,
+ data->payload, ac->priv);
+
+ ret = 0;
+ goto done;
+
+ case ASM_DATA_EVENT_WRITE_DONE_V2:
+ client_event = ASM_CLIENT_EVENT_DATA_WRITE_DONE;
+ if (ac->io_mode & ASM_SYNC_IO_MODE) {
+ phys_addr_t phys;
+ int token = hdr->token & ASM_WRITE_TOKEN_MASK;
+
+ guard(spinlock_irqsave)(&ac->lock);
+
+ port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK];
+
+ if (!port->buf) {
+ ret = 0;
+ goto done;
+ }
+
+ phys = port->buf[token].phys;
+
+ if (lower_32_bits(phys) != result->opcode ||
+ upper_32_bits(phys) != result->status) {
+ dev_err(ac->dev, "Expected addr %pa\n",
+ &port->buf[token].phys);
+ ret = -EINVAL;
+ goto done;
+ }
+ atomic_set(&port->hw_ptr, token + 1);
+ }
+ break;
+ case ASM_DATA_EVENT_READ_DONE_V2:
+ client_event = ASM_CLIENT_EVENT_DATA_READ_DONE;
+ if (ac->io_mode & ASM_SYNC_IO_MODE) {
+ struct asm_data_cmd_read_v2_done *done = data->payload;
+ phys_addr_t phys;
+
+ guard(spinlock_irqsave)(&ac->lock);
+ port = &ac->port[SNDRV_PCM_STREAM_CAPTURE];
+ if (!port->buf) {
+ ret = 0;
+ goto done;
+ }
+
+ phys = port->buf[hdr->token].phys;
+ atomic_set(&port->hw_ptr, hdr->token + 1);
+
+ if (upper_32_bits(phys) != done->buf_addr_msw ||
+ lower_32_bits(phys) != done->buf_addr_lsw) {
+ dev_err(ac->dev, "Expected addr %pa %08x-%08x\n",
+ &port->buf[hdr->token].phys,
+ done->buf_addr_lsw,
+ done->buf_addr_msw);
+ ret = -EINVAL;
+ goto done;
+ }
+ }
+
+ break;
+ case ASM_DATA_EVENT_RENDERED_EOS:
+ client_event = ASM_CLIENT_EVENT_CMD_EOS_DONE;
+ break;
+ }
+
+ if (ac->cb)
+ ac->cb(client_event, hdr->token, data->payload, ac->priv);
+
+done:
+ kref_put(&ac->refcount, q6asm_audio_client_release);
+ return ret;
+}
+
+static int q6asm_srvc_callback(struct apr_device *adev,
+ const struct apr_resp_pkt *data)
+{
+ struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
+ struct aprv2_ibasic_rsp_result_t *result;
+ struct audio_port_data *port;
+ struct audio_client *ac = NULL;
+ const struct apr_hdr *hdr = &data->hdr;
+ struct q6asm *a;
+ uint32_t sid = 0;
+ uint32_t dir = 0;
+ int session_id;
+
+ session_id = (hdr->dest_port >> 8) & 0xFF;
+ if (session_id)
+ return q6asm_stream_callback(adev, data, session_id);
+
+ sid = (hdr->token >> 8) & 0x0F;
+ ac = q6asm_get_audio_client(q6asm, sid);
+ if (!ac) {
+ dev_err(&adev->dev, "Audio Client not active\n");
+ return 0;
+ }
+
+ a = dev_get_drvdata(ac->dev->parent);
+ dir = (hdr->token & 0x0F);
+ port = &ac->port[dir];
+ result = data->payload;
+
+ switch (hdr->opcode) {
+ case APR_BASIC_RSP_RESULT:
+ switch (result->opcode) {
+ case ASM_CMD_SHARED_MEM_MAP_REGIONS:
+ case ASM_CMD_SHARED_MEM_UNMAP_REGIONS:
+ ac->result = *result;
+ wake_up(&a->mem_wait);
+ break;
+ default:
+ dev_err(&adev->dev, "command[0x%x] not expecting rsp\n",
+ result->opcode);
+ break;
+ }
+ goto done;
+ case ASM_CMDRSP_SHARED_MEM_MAP_REGIONS:
+ ac->result.status = 0;
+ ac->result.opcode = hdr->opcode;
+ port->mem_map_handle = result->opcode;
+ wake_up(&a->mem_wait);
+ break;
+ case ASM_CMD_SHARED_MEM_UNMAP_REGIONS:
+ ac->result.opcode = hdr->opcode;
+ ac->result.status = 0;
+ port->mem_map_handle = 0;
+ wake_up(&a->mem_wait);
+ break;
+ default:
+ dev_dbg(&adev->dev, "command[0x%x]success [0x%x]\n",
+ result->opcode, result->status);
+ break;
+ }
+
+ if (ac->cb)
+ ac->cb(hdr->opcode, hdr->token, data->payload, ac->priv);
+
+done:
+ kref_put(&ac->refcount, q6asm_audio_client_release);
+
+ return 0;
+}
+
+/**
+ * q6asm_get_session_id() - get session id for audio client
+ *
+ * @c: audio client pointer
+ *
+ * Return: Will be an session id of the audio client.
+ */
+int q6asm_get_session_id(struct audio_client *c)
+{
+ return c->session;
+}
+EXPORT_SYMBOL_GPL(q6asm_get_session_id);
+
+/**
+ * q6asm_audio_client_alloc() - Allocate a new audio client
+ *
+ * @dev: Pointer to asm child device.
+ * @cb: event callback.
+ * @priv: private data associated with this client.
+ * @session_id: session id
+ * @perf_mode: performace mode for this client
+ *
+ * Return: Will be an error pointer on error or a valid audio client
+ * on success.
+ */
+struct audio_client *q6asm_audio_client_alloc(struct device *dev, q6asm_cb cb,
+ void *priv, int session_id,
+ int perf_mode)
+{
+ struct q6asm *a = dev_get_drvdata(dev->parent);
+ struct audio_client *ac;
+ unsigned long flags;
+
+ ac = q6asm_get_audio_client(a, session_id + 1);
+ if (ac) {
+ dev_err(dev, "Audio Client already active\n");
+ return ac;
+ }
+
+ ac = kzalloc_obj(*ac);
+ if (!ac)
+ return ERR_PTR(-ENOMEM);
+
+ spin_lock_irqsave(&a->slock, flags);
+ a->session[session_id + 1] = ac;
+ spin_unlock_irqrestore(&a->slock, flags);
+ ac->session = session_id + 1;
+ ac->cb = cb;
+ ac->dev = dev;
+ ac->q6asm = a;
+ ac->priv = priv;
+ ac->io_mode = ASM_SYNC_IO_MODE;
+ ac->perf_mode = perf_mode;
+ ac->adev = a->adev;
+ kref_init(&ac->refcount);
+
+ init_waitqueue_head(&ac->cmd_wait);
+ mutex_init(&ac->cmd_lock);
+ spin_lock_init(&ac->lock);
+
+ return ac;
+}
+EXPORT_SYMBOL_GPL(q6asm_audio_client_alloc);
+
+static int q6asm_ac_send_cmd_sync(struct audio_client *ac, struct apr_pkt *pkt)
+{
+ struct apr_hdr *hdr = &pkt->hdr;
+ int rc;
+
+ mutex_lock(&ac->cmd_lock);
+ ac->result.opcode = 0;
+ ac->result.status = 0;
+
+ rc = apr_send_pkt(ac->adev, pkt);
+ if (rc < 0)
+ goto err;
+
+ rc = wait_event_timeout(ac->cmd_wait,
+ (ac->result.opcode == hdr->opcode), 5 * HZ);
+ if (!rc) {
+ dev_err(ac->dev, "CMD %x timeout\n", hdr->opcode);
+ rc = -ETIMEDOUT;
+ goto err;
+ }
+
+ if (ac->result.status > 0) {
+ dev_err(ac->dev, "DSP returned error[%x]\n",
+ ac->result.status);
+ rc = -EINVAL;
+ } else {
+ rc = 0;
+ }
+
+
+err:
+ mutex_unlock(&ac->cmd_lock);
+ return rc;
+}
+
+/**
+ * q6asm_open_write() - Open audio client for writing
+ * @ac: audio client pointer
+ * @stream_id: stream id of q6asm session
+ * @format: audio sample format
+ * @codec_profile: compressed format profile
+ * @bits_per_sample: bits per sample
+ * @is_gapless: flag to indicate if this is a gapless stream
+ *
+ * Return: Will be an negative value on error or zero on success
+ */
+int q6asm_open_write(struct audio_client *ac, uint32_t stream_id,
+ uint32_t format, u32 codec_profile,
+ uint16_t bits_per_sample, bool is_gapless)
+{
+ struct asm_stream_cmd_open_write_v3 *open;
+ struct apr_pkt *pkt;
+ int rc, pkt_size = APR_HDR_SIZE + sizeof(*open);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ open = p + APR_HDR_SIZE;
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id);
+
+ pkt->hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE_V3;
+ open->mode_flags = 0x00;
+ open->mode_flags |= ASM_LEGACY_STREAM_SESSION;
+ if (is_gapless)
+ open->mode_flags |= BIT(ASM_SHIFT_GAPLESS_MODE_FLAG);
+
+ /* source endpoint : matrix */
+ open->sink_endpointype = ASM_END_POINT_DEVICE_MATRIX;
+ open->bits_per_sample = bits_per_sample;
+ open->postprocopo_id = ASM_NULL_POPP_TOPOLOGY;
+
+ switch (format) {
+ case SND_AUDIOCODEC_MP3:
+ open->dec_fmt_id = ASM_MEDIA_FMT_MP3;
+ break;
+ case FORMAT_LINEAR_PCM:
+ open->dec_fmt_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2;
+ break;
+ case SND_AUDIOCODEC_FLAC:
+ open->dec_fmt_id = ASM_MEDIA_FMT_FLAC;
+ break;
+ case SND_AUDIOCODEC_WMA:
+ switch (codec_profile) {
+ case SND_AUDIOPROFILE_WMA9:
+ open->dec_fmt_id = ASM_MEDIA_FMT_WMA_V9;
+ break;
+ case SND_AUDIOPROFILE_WMA10:
+ case SND_AUDIOPROFILE_WMA9_PRO:
+ case SND_AUDIOPROFILE_WMA9_LOSSLESS:
+ case SND_AUDIOPROFILE_WMA10_LOSSLESS:
+ open->dec_fmt_id = ASM_MEDIA_FMT_WMA_V10;
+ break;
+ default:
+ dev_err(ac->dev, "Invalid codec profile 0x%x\n",
+ codec_profile);
+ rc = -EINVAL;
+ goto err;
+ }
+ break;
+ case SND_AUDIOCODEC_ALAC:
+ open->dec_fmt_id = ASM_MEDIA_FMT_ALAC;
+ break;
+ case SND_AUDIOCODEC_APE:
+ open->dec_fmt_id = ASM_MEDIA_FMT_APE;
+ break;
+ default:
+ dev_err(ac->dev, "Invalid format 0x%x\n", format);
+ rc = -EINVAL;
+ goto err;
+ }
+
+ rc = q6asm_ac_send_cmd_sync(ac, pkt);
+ if (rc < 0)
+ goto err;
+
+ ac->io_mode |= ASM_TUN_WRITE_IO_MODE;
+
+err:
+ return rc;
+}
+EXPORT_SYMBOL_GPL(q6asm_open_write);
+
+static int __q6asm_run(struct audio_client *ac, uint32_t stream_id,
+ uint32_t flags, uint32_t msw_ts, uint32_t lsw_ts,
+ bool wait)
+{
+ struct asm_session_cmd_run_v2 *run;
+ struct apr_pkt *pkt;
+ int rc, pkt_size = APR_HDR_SIZE + sizeof(*run);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_ATOMIC);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ run = p + APR_HDR_SIZE;
+
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id);
+
+ pkt->hdr.opcode = ASM_SESSION_CMD_RUN_V2;
+ run->flags = flags;
+ run->time_lsw = lsw_ts;
+ run->time_msw = msw_ts;
+ if (wait) {
+ rc = q6asm_ac_send_cmd_sync(ac, pkt);
+ } else {
+ rc = apr_send_pkt(ac->adev, pkt);
+ if (rc == pkt_size)
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/**
+ * q6asm_run() - start the audio client
+ *
+ * @ac: audio client pointer
+ * @stream_id: stream id of q6asm session
+ * @flags: flags associated with write
+ * @msw_ts: timestamp msw
+ * @lsw_ts: timestamp lsw
+ *
+ * Return: Will be an negative value on error or zero on success
+ */
+int q6asm_run(struct audio_client *ac, uint32_t stream_id, uint32_t flags,
+ uint32_t msw_ts, uint32_t lsw_ts)
+{
+ return __q6asm_run(ac, stream_id, flags, msw_ts, lsw_ts, true);
+}
+EXPORT_SYMBOL_GPL(q6asm_run);
+
+/**
+ * q6asm_run_nowait() - start the audio client withou blocking
+ *
+ * @ac: audio client pointer
+ * @stream_id: stream id
+ * @flags: flags associated with write
+ * @msw_ts: timestamp msw
+ * @lsw_ts: timestamp lsw
+ *
+ * Return: Will be an negative value on error or zero on success
+ */
+int q6asm_run_nowait(struct audio_client *ac, uint32_t stream_id,
+ uint32_t flags, uint32_t msw_ts, uint32_t lsw_ts)
+{
+ return __q6asm_run(ac, stream_id, flags, msw_ts, lsw_ts, false);
+}
+EXPORT_SYMBOL_GPL(q6asm_run_nowait);
+
+/**
+ * q6asm_media_format_block_multi_ch_pcm() - setup pcm configuration
+ *
+ * @ac: audio client pointer
+ * @stream_id: stream id
+ * @rate: audio sample rate
+ * @channels: number of audio channels.
+ * @channel_map: channel map pointer
+ * @bits_per_sample: bits per sample
+ *
+ * Return: Will be an negative value on error or zero on success
+ */
+int q6asm_media_format_block_multi_ch_pcm(struct audio_client *ac,
+ uint32_t stream_id,
+ uint32_t rate, uint32_t channels,
+ u8 channel_map[PCM_MAX_NUM_CHANNEL],
+ uint16_t bits_per_sample)
+{
+ struct asm_multi_channel_pcm_fmt_blk_v2 *fmt;
+ struct apr_pkt *pkt;
+ u8 *channel_mapping;
+ int pkt_size = APR_HDR_SIZE + sizeof(*fmt);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ fmt = p + APR_HDR_SIZE;
+
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id);
+
+ pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2;
+ fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk);
+ fmt->num_channels = channels;
+ fmt->bits_per_sample = bits_per_sample;
+ fmt->sample_rate = rate;
+ fmt->is_signed = 1;
+
+ channel_mapping = fmt->channel_mapping;
+
+ if (channel_map) {
+ memcpy(channel_mapping, channel_map, PCM_MAX_NUM_CHANNEL);
+ } else {
+ if (q6dsp_map_channels(channel_mapping, channels)) {
+ dev_err(ac->dev, " map channels failed %d\n", channels);
+ return -EINVAL;
+ }
+ }
+
+ return q6asm_ac_send_cmd_sync(ac, pkt);
+}
+EXPORT_SYMBOL_GPL(q6asm_media_format_block_multi_ch_pcm);
+
+int q6asm_stream_media_format_block_flac(struct audio_client *ac,
+ uint32_t stream_id,
+ struct q6asm_flac_cfg *cfg)
+{
+ struct asm_flac_fmt_blk_v2 *fmt;
+ struct apr_pkt *pkt;
+ int pkt_size = APR_HDR_SIZE + sizeof(*fmt);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ fmt = p + APR_HDR_SIZE;
+
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id);
+
+ pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2;
+ fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk);
+ fmt->is_stream_info_present = cfg->stream_info_present;
+ fmt->num_channels = cfg->ch_cfg;
+ fmt->min_blk_size = cfg->min_blk_size;
+ fmt->max_blk_size = cfg->max_blk_size;
+ fmt->sample_rate = cfg->sample_rate;
+ fmt->min_frame_size = cfg->min_frame_size;
+ fmt->max_frame_size = cfg->max_frame_size;
+ fmt->sample_size = cfg->sample_size;
+
+ return q6asm_ac_send_cmd_sync(ac, pkt);
+}
+EXPORT_SYMBOL_GPL(q6asm_stream_media_format_block_flac);
+
+int q6asm_stream_media_format_block_wma_v9(struct audio_client *ac,
+ uint32_t stream_id,
+ struct q6asm_wma_cfg *cfg)
+{
+ struct asm_wmastdv9_fmt_blk_v2 *fmt;
+ struct apr_pkt *pkt;
+ int pkt_size = APR_HDR_SIZE + sizeof(*fmt);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ fmt = p + APR_HDR_SIZE;
+
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id);
+
+ pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2;
+ fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk);
+ fmt->fmtag = cfg->fmtag;
+ fmt->num_channels = cfg->num_channels;
+ fmt->sample_rate = cfg->sample_rate;
+ fmt->bytes_per_sec = cfg->bytes_per_sec;
+ fmt->blk_align = cfg->block_align;
+ fmt->bits_per_sample = cfg->bits_per_sample;
+ fmt->channel_mask = cfg->channel_mask;
+ fmt->enc_options = cfg->enc_options;
+ fmt->reserved = 0;
+
+ return q6asm_ac_send_cmd_sync(ac, pkt);
+}
+EXPORT_SYMBOL_GPL(q6asm_stream_media_format_block_wma_v9);
+
+int q6asm_stream_media_format_block_wma_v10(struct audio_client *ac,
+ uint32_t stream_id,
+ struct q6asm_wma_cfg *cfg)
+{
+ struct asm_wmaprov10_fmt_blk_v2 *fmt;
+ struct apr_pkt *pkt;
+ int pkt_size = APR_HDR_SIZE + sizeof(*fmt);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ fmt = p + APR_HDR_SIZE;
+
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id);
+
+ pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2;
+ fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk);
+ fmt->fmtag = cfg->fmtag;
+ fmt->num_channels = cfg->num_channels;
+ fmt->sample_rate = cfg->sample_rate;
+ fmt->bytes_per_sec = cfg->bytes_per_sec;
+ fmt->blk_align = cfg->block_align;
+ fmt->bits_per_sample = cfg->bits_per_sample;
+ fmt->channel_mask = cfg->channel_mask;
+ fmt->enc_options = cfg->enc_options;
+ fmt->advanced_enc_options1 = cfg->adv_enc_options;
+ fmt->advanced_enc_options2 = cfg->adv_enc_options2;
+
+ return q6asm_ac_send_cmd_sync(ac, pkt);
+}
+EXPORT_SYMBOL_GPL(q6asm_stream_media_format_block_wma_v10);
+
+int q6asm_stream_media_format_block_alac(struct audio_client *ac,
+ uint32_t stream_id,
+ struct q6asm_alac_cfg *cfg)
+{
+ struct asm_alac_fmt_blk_v2 *fmt;
+ struct apr_pkt *pkt;
+ int pkt_size = APR_HDR_SIZE + sizeof(*fmt);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ fmt = p + APR_HDR_SIZE;
+
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id);
+
+ pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2;
+ fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk);
+
+ fmt->frame_length = cfg->frame_length;
+ fmt->compatible_version = cfg->compatible_version;
+ fmt->bit_depth = cfg->bit_depth;
+ fmt->num_channels = cfg->num_channels;
+ fmt->max_run = cfg->max_run;
+ fmt->max_frame_bytes = cfg->max_frame_bytes;
+ fmt->avg_bit_rate = cfg->avg_bit_rate;
+ fmt->sample_rate = cfg->sample_rate;
+ fmt->channel_layout_tag = cfg->channel_layout_tag;
+ fmt->pb = cfg->pb;
+ fmt->mb = cfg->mb;
+ fmt->kb = cfg->kb;
+
+ return q6asm_ac_send_cmd_sync(ac, pkt);
+}
+EXPORT_SYMBOL_GPL(q6asm_stream_media_format_block_alac);
+
+int q6asm_stream_media_format_block_ape(struct audio_client *ac,
+ uint32_t stream_id,
+ struct q6asm_ape_cfg *cfg)
+{
+ struct asm_ape_fmt_blk_v2 *fmt;
+ struct apr_pkt *pkt;
+ int pkt_size = APR_HDR_SIZE + sizeof(*fmt);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ fmt = p + APR_HDR_SIZE;
+
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id);
+
+ pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2;
+ fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk);
+
+ fmt->compatible_version = cfg->compatible_version;
+ fmt->compression_level = cfg->compression_level;
+ fmt->format_flags = cfg->format_flags;
+ fmt->blocks_per_frame = cfg->blocks_per_frame;
+ fmt->final_frame_blocks = cfg->final_frame_blocks;
+ fmt->total_frames = cfg->total_frames;
+ fmt->bits_per_sample = cfg->bits_per_sample;
+ fmt->num_channels = cfg->num_channels;
+ fmt->sample_rate = cfg->sample_rate;
+ fmt->seek_table_present = cfg->seek_table_present;
+
+ return q6asm_ac_send_cmd_sync(ac, pkt);
+}
+EXPORT_SYMBOL_GPL(q6asm_stream_media_format_block_ape);
+
+static int q6asm_stream_remove_silence(struct audio_client *ac, uint32_t stream_id,
+ uint32_t cmd,
+ uint32_t num_samples)
+{
+ uint32_t *samples;
+ struct apr_pkt *pkt;
+ int rc, pkt_size = APR_HDR_SIZE + sizeof(uint32_t);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_ATOMIC);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ samples = p + APR_HDR_SIZE;
+
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id);
+
+ pkt->hdr.opcode = cmd;
+ *samples = num_samples;
+ rc = apr_send_pkt(ac->adev, pkt);
+ if (rc == pkt_size)
+ rc = 0;
+
+ return rc;
+}
+
+int q6asm_stream_remove_initial_silence(struct audio_client *ac,
+ uint32_t stream_id,
+ uint32_t initial_samples)
+{
+ return q6asm_stream_remove_silence(ac, stream_id,
+ ASM_DATA_CMD_REMOVE_INITIAL_SILENCE,
+ initial_samples);
+}
+EXPORT_SYMBOL_GPL(q6asm_stream_remove_initial_silence);
+
+int q6asm_stream_remove_trailing_silence(struct audio_client *ac, uint32_t stream_id,
+ uint32_t trailing_samples)
+{
+ return q6asm_stream_remove_silence(ac, stream_id,
+ ASM_DATA_CMD_REMOVE_TRAILING_SILENCE,
+ trailing_samples);
+}
+EXPORT_SYMBOL_GPL(q6asm_stream_remove_trailing_silence);
+
+/**
+ * q6asm_enc_cfg_blk_pcm_format_support() - setup pcm configuration for capture
+ *
+ * @ac: audio client pointer
+ * @stream_id: stream id
+ * @rate: audio sample rate
+ * @channels: number of audio channels.
+ * @bits_per_sample: bits per sample
+ *
+ * Return: Will be an negative value on error or zero on success
+ */
+int q6asm_enc_cfg_blk_pcm_format_support(struct audio_client *ac,
+ uint32_t stream_id, uint32_t rate,
+ uint32_t channels,
+ uint16_t bits_per_sample)
+{
+ struct asm_multi_channel_pcm_enc_cfg_v2 *enc_cfg;
+ struct apr_pkt *pkt;
+ u8 *channel_mapping;
+ u32 frames_per_buf = 0;
+ int pkt_size = APR_HDR_SIZE + sizeof(*enc_cfg);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ enc_cfg = p + APR_HDR_SIZE;
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id);
+
+ pkt->hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM;
+ enc_cfg->encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2;
+ enc_cfg->encdec.param_size = sizeof(*enc_cfg) - sizeof(enc_cfg->encdec);
+ enc_cfg->encblk.frames_per_buf = frames_per_buf;
+ enc_cfg->encblk.enc_cfg_blk_size = enc_cfg->encdec.param_size -
+ sizeof(struct asm_enc_cfg_blk_param_v2);
+
+ enc_cfg->num_channels = channels;
+ enc_cfg->bits_per_sample = bits_per_sample;
+ enc_cfg->sample_rate = rate;
+ enc_cfg->is_signed = 1;
+ channel_mapping = enc_cfg->channel_mapping;
+
+ if (q6dsp_map_channels(channel_mapping, channels))
+ return -EINVAL;
+
+ return q6asm_ac_send_cmd_sync(ac, pkt);
+}
+EXPORT_SYMBOL_GPL(q6asm_enc_cfg_blk_pcm_format_support);
+
+
+/**
+ * q6asm_read() - read data of period size from audio client
+ *
+ * @ac: audio client pointer
+ * @stream_id: stream id
+ *
+ * Return: Will be an negative value on error or zero on success
+ */
+int q6asm_read(struct audio_client *ac, uint32_t stream_id)
+{
+ struct asm_data_cmd_read_v2 *read;
+ struct audio_port_data *port;
+ struct audio_buffer *ab;
+ struct apr_pkt *pkt;
+ unsigned long flags;
+ int pkt_size = APR_HDR_SIZE + sizeof(*read);
+ int rc = 0;
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_ATOMIC);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ read = p + APR_HDR_SIZE;
+
+ spin_lock_irqsave(&ac->lock, flags);
+ port = &ac->port[SNDRV_PCM_STREAM_CAPTURE];
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, false, stream_id);
+ ab = &port->buf[port->dsp_buf];
+ pkt->hdr.opcode = ASM_DATA_CMD_READ_V2;
+ read->buf_addr_lsw = lower_32_bits(ab->phys);
+ read->buf_addr_msw = upper_32_bits(ab->phys);
+ read->mem_map_handle = port->mem_map_handle;
+
+ read->buf_size = ab->size;
+ read->seq_id = port->dsp_buf;
+ pkt->hdr.token = port->dsp_buf;
+
+ port->dsp_buf++;
+
+ if (port->dsp_buf >= port->num_periods)
+ port->dsp_buf = 0;
+
+ spin_unlock_irqrestore(&ac->lock, flags);
+ rc = apr_send_pkt(ac->adev, pkt);
+ if (rc == pkt_size)
+ rc = 0;
+ else
+ pr_err("read op[0x%x]rc[%d]\n", pkt->hdr.opcode, rc);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(q6asm_read);
+
+static int __q6asm_open_read(struct audio_client *ac, uint32_t stream_id,
+ uint32_t format, uint16_t bits_per_sample)
+{
+ struct asm_stream_cmd_open_read_v3 *open;
+ struct apr_pkt *pkt;
+ int pkt_size = APR_HDR_SIZE + sizeof(*open);
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ open = p + APR_HDR_SIZE;
+
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id);
+ pkt->hdr.opcode = ASM_STREAM_CMD_OPEN_READ_V3;
+ /* Stream prio : High, provide meta info with encoded frames */
+ open->src_endpointype = ASM_END_POINT_DEVICE_MATRIX;
+
+ open->preprocopo_id = ASM_STREAM_POSTPROC_TOPO_ID_NONE;
+ open->bits_per_sample = bits_per_sample;
+ open->mode_flags = 0x0;
+
+ open->mode_flags |= ASM_LEGACY_STREAM_SESSION <<
+ ASM_SHIFT_STREAM_PERF_MODE_FLAG_IN_OPEN_READ;
+
+ switch (format) {
+ case FORMAT_LINEAR_PCM:
+ open->mode_flags |= 0x00;
+ open->enc_cfg_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2;
+ break;
+ default:
+ pr_err("Invalid format[%d]\n", format);
+ }
+
+ return q6asm_ac_send_cmd_sync(ac, pkt);
+}
+
+/**
+ * q6asm_open_read() - Open audio client for reading
+ *
+ * @ac: audio client pointer
+ * @stream_id: stream id
+ * @format: audio sample format
+ * @bits_per_sample: bits per sample
+ *
+ * Return: Will be an negative value on error or zero on success
+ */
+int q6asm_open_read(struct audio_client *ac, uint32_t stream_id,
+ uint32_t format, uint16_t bits_per_sample)
+{
+ return __q6asm_open_read(ac, stream_id, format, bits_per_sample);
+}
+EXPORT_SYMBOL_GPL(q6asm_open_read);
+
+/**
+ * q6asm_write_async() - non blocking write
+ *
+ * @ac: audio client pointer
+ * @stream_id: stream id
+ * @len: length in bytes
+ * @msw_ts: timestamp msw
+ * @lsw_ts: timestamp lsw
+ * @wflags: flags associated with write
+ *
+ * Return: Will be an negative value on error or zero on success
+ */
+int q6asm_write_async(struct audio_client *ac, uint32_t stream_id, uint32_t len,
+ uint32_t msw_ts, uint32_t lsw_ts, uint32_t wflags)
+{
+ struct asm_data_cmd_write_v2 *write;
+ struct audio_port_data *port;
+ struct audio_buffer *ab;
+ unsigned long flags;
+ struct apr_pkt *pkt;
+ int pkt_size = APR_HDR_SIZE + sizeof(*write);
+ int rc = 0;
+
+ void *p __free(kfree) = kzalloc(pkt_size, GFP_ATOMIC);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ write = p + APR_HDR_SIZE;
+
+ spin_lock_irqsave(&ac->lock, flags);
+ port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK];
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, false, stream_id);
+
+ ab = &port->buf[port->dsp_buf];
+ pkt->hdr.token = port->dsp_buf | (len << ASM_WRITE_TOKEN_LEN_SHIFT);
+ pkt->hdr.opcode = ASM_DATA_CMD_WRITE_V2;
+ write->buf_addr_lsw = lower_32_bits(ab->phys);
+ write->buf_addr_msw = upper_32_bits(ab->phys);
+ write->buf_size = len;
+ write->seq_id = port->dsp_buf;
+ write->timestamp_lsw = lsw_ts;
+ write->timestamp_msw = msw_ts;
+ write->mem_map_handle =
+ ac->port[SNDRV_PCM_STREAM_PLAYBACK].mem_map_handle;
+
+ write->flags = wflags;
+
+ port->dsp_buf++;
+
+ if (port->dsp_buf >= port->num_periods)
+ port->dsp_buf = 0;
+
+ spin_unlock_irqrestore(&ac->lock, flags);
+ rc = apr_send_pkt(ac->adev, pkt);
+ if (rc == pkt_size)
+ rc = 0;
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(q6asm_write_async);
+
+static void q6asm_reset_buf_state(struct audio_client *ac)
+{
+ struct audio_port_data *port;
+
+ guard(spinlock_irqsave)(&ac->lock);
+ port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK];
+ port->dsp_buf = 0;
+ port = &ac->port[SNDRV_PCM_STREAM_CAPTURE];
+ port->dsp_buf = 0;
+}
+
+static int __q6asm_cmd(struct audio_client *ac, uint32_t stream_id, int cmd,
+ bool wait)
+{
+ struct apr_pkt pkt;
+ int rc;
+
+ q6asm_add_hdr(ac, &pkt.hdr, APR_HDR_SIZE, true, stream_id);
+
+ switch (cmd) {
+ case CMD_PAUSE:
+ pkt.hdr.opcode = ASM_SESSION_CMD_PAUSE;
+ break;
+ case CMD_SUSPEND:
+ pkt.hdr.opcode = ASM_SESSION_CMD_SUSPEND;
+ break;
+ case CMD_FLUSH:
+ pkt.hdr.opcode = ASM_STREAM_CMD_FLUSH;
+ break;
+ case CMD_OUT_FLUSH:
+ pkt.hdr.opcode = ASM_STREAM_CMD_FLUSH_READBUFS;
+ break;
+ case CMD_EOS:
+ pkt.hdr.opcode = ASM_DATA_CMD_EOS;
+ break;
+ case CMD_CLOSE:
+ pkt.hdr.opcode = ASM_STREAM_CMD_CLOSE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (wait)
+ rc = q6asm_ac_send_cmd_sync(ac, &pkt);
+ else
+ return apr_send_pkt(ac->adev, &pkt);
+
+ if (rc < 0)
+ return rc;
+
+ if (cmd == CMD_FLUSH)
+ q6asm_reset_buf_state(ac);
+
+ return 0;
+}
+
+/**
+ * q6asm_cmd() - run cmd on audio client
+ *
+ * @ac: audio client pointer
+ * @stream_id: stream id
+ * @cmd: command to run on audio client.
+ *
+ * Return: Will be an negative value on error or zero on success
+ */
+int q6asm_cmd(struct audio_client *ac, uint32_t stream_id, int cmd)
+{
+ return __q6asm_cmd(ac, stream_id, cmd, true);
+}
+EXPORT_SYMBOL_GPL(q6asm_cmd);
+
+/**
+ * q6asm_cmd_nowait() - non blocking, run cmd on audio client
+ *
+ * @ac: audio client pointer
+ * @stream_id: stream id
+ * @cmd: command to run on audio client.
+ *
+ * Return: Will be an negative value on error or zero on success
+ */
+int q6asm_cmd_nowait(struct audio_client *ac, uint32_t stream_id, int cmd)
+{
+ return __q6asm_cmd(ac, stream_id, cmd, false);
+}
+EXPORT_SYMBOL_GPL(q6asm_cmd_nowait);
+
+static int q6asm_probe(struct apr_device *adev)
+{
+ struct device *dev = &adev->dev;
+ struct q6asm *q6asm;
+
+ q6asm = devm_kzalloc(dev, sizeof(*q6asm), GFP_KERNEL);
+ if (!q6asm)
+ return -ENOMEM;
+
+ q6core_get_svc_api_info(adev->svc_id, &q6asm->ainfo);
+
+ q6asm->dev = dev;
+ q6asm->adev = adev;
+ init_waitqueue_head(&q6asm->mem_wait);
+ spin_lock_init(&q6asm->slock);
+ dev_set_drvdata(dev, q6asm);
+
+ return devm_of_platform_populate(dev);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id q6asm_device_id[] = {
+ { .compatible = "qcom,q6asm" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6asm_device_id);
+#endif
+
+static struct apr_driver qcom_q6asm_driver = {
+ .probe = q6asm_probe,
+ .callback = q6asm_srvc_callback,
+ .driver = {
+ .name = "qcom-q6asm",
+ .of_match_table = of_match_ptr(q6asm_device_id),
+ },
+};
+
+module_apr_driver(qcom_q6asm_driver);
+MODULE_DESCRIPTION("Q6 Audio Stream Manager driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h
new file mode 100644
index 000000000..6fafda5bd
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6asm.h
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __Q6_ASM_H__
+#define __Q6_ASM_H__
+#include "q6dsp-common.h"
+
+/* ASM client callback events */
+#define CMD_PAUSE 0x0001
+#define ASM_CLIENT_EVENT_CMD_PAUSE_DONE 0x1001
+#define CMD_FLUSH 0x0002
+#define ASM_CLIENT_EVENT_CMD_FLUSH_DONE 0x1002
+#define CMD_EOS 0x0003
+#define ASM_CLIENT_EVENT_CMD_EOS_DONE 0x1003
+#define CMD_CLOSE 0x0004
+#define ASM_CLIENT_EVENT_CMD_CLOSE_DONE 0x1004
+#define CMD_OUT_FLUSH 0x0005
+#define ASM_CLIENT_EVENT_CMD_OUT_FLUSH_DONE 0x1005
+#define CMD_SUSPEND 0x0006
+#define ASM_CLIENT_EVENT_CMD_SUSPEND_DONE 0x1006
+#define ASM_CLIENT_EVENT_CMD_RUN_DONE 0x1008
+#define ASM_CLIENT_EVENT_DATA_WRITE_DONE 0x1009
+#define ASM_CLIENT_EVENT_DATA_READ_DONE 0x100a
+#define ASM_WRITE_TOKEN_MASK GENMASK(15, 0)
+#define ASM_WRITE_TOKEN_LEN_MASK GENMASK(31, 16)
+#define ASM_WRITE_TOKEN_LEN_SHIFT 16
+
+enum {
+ LEGACY_PCM_MODE = 0,
+ LOW_LATENCY_PCM_MODE,
+ ULTRA_LOW_LATENCY_PCM_MODE,
+ ULL_POST_PROCESSING_PCM_MODE,
+};
+
+#define MAX_SESSIONS 8
+#define FORMAT_LINEAR_PCM 0x0000
+#define ASM_LAST_BUFFER_FLAG BIT(30)
+
+struct q6asm_flac_cfg {
+ u32 sample_rate;
+ u32 ext_sample_rate;
+ u32 min_frame_size;
+ u32 max_frame_size;
+ u16 stream_info_present;
+ u16 min_blk_size;
+ u16 max_blk_size;
+ u16 ch_cfg;
+ u16 sample_size;
+ u16 md5_sum;
+};
+
+struct q6asm_wma_cfg {
+ u32 fmtag;
+ u32 num_channels;
+ u32 sample_rate;
+ u32 bytes_per_sec;
+ u32 block_align;
+ u32 bits_per_sample;
+ u32 channel_mask;
+ u32 enc_options;
+ u32 adv_enc_options;
+ u32 adv_enc_options2;
+};
+
+struct q6asm_alac_cfg {
+ u32 frame_length;
+ u8 compatible_version;
+ u8 bit_depth;
+ u8 pb;
+ u8 mb;
+ u8 kb;
+ u8 num_channels;
+ u16 max_run;
+ u32 max_frame_bytes;
+ u32 avg_bit_rate;
+ u32 sample_rate;
+ u32 channel_layout_tag;
+};
+
+struct q6asm_ape_cfg {
+ u16 compatible_version;
+ u16 compression_level;
+ u32 format_flags;
+ u32 blocks_per_frame;
+ u32 final_frame_blocks;
+ u32 total_frames;
+ u16 bits_per_sample;
+ u16 num_channels;
+ u32 sample_rate;
+ u32 seek_table_present;
+};
+
+typedef void (*q6asm_cb) (uint32_t opcode, uint32_t token,
+ void *payload, void *priv);
+struct audio_client;
+struct audio_client *q6asm_audio_client_alloc(struct device *dev,
+ q6asm_cb cb, void *priv,
+ int session_id, int perf_mode);
+void q6asm_audio_client_free(struct audio_client *ac);
+int q6asm_write_async(struct audio_client *ac, uint32_t stream_id, uint32_t len,
+ uint32_t msw_ts, uint32_t lsw_ts, uint32_t wflags);
+int q6asm_open_write(struct audio_client *ac, uint32_t stream_id,
+ uint32_t format, u32 codec_profile,
+ uint16_t bits_per_sample, bool is_gapless);
+
+int q6asm_open_read(struct audio_client *ac, uint32_t stream_id,
+ uint32_t format, uint16_t bits_per_sample);
+int q6asm_enc_cfg_blk_pcm_format_support(struct audio_client *ac,
+ uint32_t stream_id, uint32_t rate,
+ uint32_t channels,
+ uint16_t bits_per_sample);
+
+int q6asm_read(struct audio_client *ac, uint32_t stream_id);
+
+int q6asm_media_format_block_multi_ch_pcm(struct audio_client *ac,
+ uint32_t stream_id,
+ uint32_t rate, uint32_t channels,
+ u8 channel_map[PCM_MAX_NUM_CHANNEL],
+ uint16_t bits_per_sample);
+int q6asm_stream_media_format_block_flac(struct audio_client *ac,
+ uint32_t stream_id,
+ struct q6asm_flac_cfg *cfg);
+int q6asm_stream_media_format_block_wma_v9(struct audio_client *ac,
+ uint32_t stream_id,
+ struct q6asm_wma_cfg *cfg);
+int q6asm_stream_media_format_block_wma_v10(struct audio_client *ac,
+ uint32_t stream_id,
+ struct q6asm_wma_cfg *cfg);
+int q6asm_stream_media_format_block_alac(struct audio_client *ac,
+ uint32_t stream_id,
+ struct q6asm_alac_cfg *cfg);
+int q6asm_stream_media_format_block_ape(struct audio_client *ac,
+ uint32_t stream_id,
+ struct q6asm_ape_cfg *cfg);
+int q6asm_run(struct audio_client *ac, uint32_t stream_id, uint32_t flags,
+ uint32_t msw_ts, uint32_t lsw_ts);
+int q6asm_run_nowait(struct audio_client *ac, uint32_t stream_id,
+ uint32_t flags, uint32_t msw_ts, uint32_t lsw_ts);
+int q6asm_stream_remove_initial_silence(struct audio_client *ac,
+ uint32_t stream_id,
+ uint32_t initial_samples);
+int q6asm_stream_remove_trailing_silence(struct audio_client *ac,
+ uint32_t stream_id,
+ uint32_t trailing_samples);
+int q6asm_cmd(struct audio_client *ac, uint32_t stream_id, int cmd);
+int q6asm_cmd_nowait(struct audio_client *ac, uint32_t stream_id, int cmd);
+int q6asm_get_session_id(struct audio_client *c);
+int q6asm_map_memory_regions(unsigned int dir,
+ struct audio_client *ac,
+ phys_addr_t phys,
+ size_t period_sz, unsigned int periods);
+int q6asm_unmap_memory_regions(unsigned int dir, struct audio_client *ac);
+int q6asm_get_hw_pointer(struct audio_client *ac, unsigned int dir);
+#endif /* __Q6_ASM_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6core.c b/sound/soc/qcom/qdsp6/q6core.c
new file mode 100644
index 000000000..48825756a
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6core.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/jiffies.h>
+#include <linux/soc/qcom/apr.h>
+#include "q6core.h"
+#include "q6dsp-errno.h"
+
+#define ADSP_STATE_READY_TIMEOUT_MS 3000
+#define Q6_READY_TIMEOUT_MS 100
+#define AVCS_CMD_ADSP_EVENT_GET_STATE 0x0001290C
+#define AVCS_CMDRSP_ADSP_EVENT_GET_STATE 0x0001290D
+#define AVCS_GET_VERSIONS 0x00012905
+#define AVCS_GET_VERSIONS_RSP 0x00012906
+#define AVCS_CMD_GET_FWK_VERSION 0x001292c
+#define AVCS_CMDRSP_GET_FWK_VERSION 0x001292d
+
+struct avcs_svc_info {
+ uint32_t service_id;
+ uint32_t version;
+} __packed;
+
+struct avcs_cmdrsp_get_version {
+ uint32_t build_id;
+ uint32_t num_services;
+ struct avcs_svc_info svc_api_info[];
+} __packed;
+
+/* for ADSP2.8 and above */
+struct avcs_svc_api_info {
+ uint32_t service_id;
+ uint32_t api_version;
+ uint32_t api_branch_version;
+} __packed;
+
+struct avcs_cmdrsp_get_fwk_version {
+ uint32_t build_major_version;
+ uint32_t build_minor_version;
+ uint32_t build_branch_version;
+ uint32_t build_subbranch_version;
+ uint32_t num_services;
+ struct avcs_svc_api_info svc_api_info[];
+} __packed;
+
+struct q6core {
+ struct apr_device *adev;
+ wait_queue_head_t wait;
+ uint32_t avcs_state;
+ struct mutex lock;
+ bool resp_received;
+ uint32_t num_services;
+ struct avcs_cmdrsp_get_fwk_version *fwk_version;
+ struct avcs_cmdrsp_get_version *svc_version;
+ bool fwk_version_supported;
+ bool get_state_supported;
+ bool get_version_supported;
+ bool is_version_requested;
+};
+
+static struct q6core *g_core;
+
+static int q6core_callback(struct apr_device *adev, const struct apr_resp_pkt *data)
+{
+ struct q6core *core = dev_get_drvdata(&adev->dev);
+ const struct aprv2_ibasic_rsp_result_t *result;
+ const struct apr_hdr *hdr = &data->hdr;
+
+ result = data->payload;
+ switch (hdr->opcode) {
+ case APR_BASIC_RSP_RESULT:{
+ result = data->payload;
+ switch (result->opcode) {
+ case AVCS_GET_VERSIONS:
+ if (result->status == ADSP_EUNSUPPORTED)
+ core->get_version_supported = false;
+ core->resp_received = true;
+ break;
+ case AVCS_CMD_GET_FWK_VERSION:
+ if (result->status == ADSP_EUNSUPPORTED)
+ core->fwk_version_supported = false;
+ core->resp_received = true;
+ break;
+ case AVCS_CMD_ADSP_EVENT_GET_STATE:
+ if (result->status == ADSP_EUNSUPPORTED)
+ core->get_state_supported = false;
+ core->resp_received = true;
+ break;
+ }
+ break;
+ }
+ case AVCS_CMDRSP_GET_FWK_VERSION: {
+ struct avcs_cmdrsp_get_fwk_version *fwk;
+
+ fwk = data->payload;
+
+ core->fwk_version = kmemdup(data->payload,
+ struct_size(fwk, svc_api_info,
+ fwk->num_services),
+ GFP_ATOMIC);
+ if (!core->fwk_version)
+ return -ENOMEM;
+
+ core->fwk_version_supported = true;
+ core->resp_received = true;
+
+ break;
+ }
+ case AVCS_GET_VERSIONS_RSP: {
+ struct avcs_cmdrsp_get_version *v;
+
+ v = data->payload;
+
+ core->svc_version = kmemdup(data->payload,
+ struct_size(v, svc_api_info,
+ v->num_services),
+ GFP_ATOMIC);
+ if (!core->svc_version)
+ return -ENOMEM;
+
+ core->get_version_supported = true;
+ core->resp_received = true;
+
+ break;
+ }
+ case AVCS_CMDRSP_ADSP_EVENT_GET_STATE:
+ core->get_state_supported = true;
+ core->avcs_state = result->opcode;
+
+ core->resp_received = true;
+ break;
+ default:
+ dev_err(&adev->dev, "Message id from adsp core svc: 0x%x\n",
+ hdr->opcode);
+ break;
+ }
+
+ if (core->resp_received)
+ wake_up(&core->wait);
+
+ return 0;
+}
+
+static int q6core_get_fwk_versions(struct q6core *core)
+{
+ struct apr_device *adev = core->adev;
+ struct apr_pkt pkt;
+ int rc;
+
+ pkt.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
+ pkt.hdr.pkt_size = APR_HDR_SIZE;
+ pkt.hdr.opcode = AVCS_CMD_GET_FWK_VERSION;
+
+ rc = apr_send_pkt(adev, &pkt);
+ if (rc < 0)
+ return rc;
+
+ rc = wait_event_timeout(core->wait, (core->resp_received),
+ msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
+ if (rc > 0 && core->resp_received) {
+ core->resp_received = false;
+
+ if (!core->fwk_version_supported)
+ return -ENOTSUPP;
+ else
+ return 0;
+ }
+
+
+ return rc;
+}
+
+static int q6core_get_svc_versions(struct q6core *core)
+{
+ struct apr_device *adev = core->adev;
+ struct apr_pkt pkt;
+ int rc;
+
+ pkt.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
+ pkt.hdr.pkt_size = APR_HDR_SIZE;
+ pkt.hdr.opcode = AVCS_GET_VERSIONS;
+
+ rc = apr_send_pkt(adev, &pkt);
+ if (rc < 0)
+ return rc;
+
+ rc = wait_event_timeout(core->wait, (core->resp_received),
+ msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
+ if (rc > 0 && core->resp_received) {
+ core->resp_received = false;
+ return 0;
+ }
+
+ return rc;
+}
+
+static bool __q6core_is_adsp_ready(struct q6core *core)
+{
+ struct apr_device *adev = core->adev;
+ struct apr_pkt pkt;
+ int rc;
+
+ core->get_state_supported = false;
+
+ pkt.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
+ pkt.hdr.pkt_size = APR_HDR_SIZE;
+ pkt.hdr.opcode = AVCS_CMD_ADSP_EVENT_GET_STATE;
+
+ rc = apr_send_pkt(adev, &pkt);
+ if (rc < 0)
+ return false;
+
+ rc = wait_event_timeout(core->wait, (core->resp_received),
+ msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
+ if (rc > 0 && core->resp_received) {
+ core->resp_received = false;
+
+ if (core->avcs_state)
+ return true;
+ }
+
+ /* assume that the adsp is up if we not support this command */
+ if (!core->get_state_supported)
+ return true;
+
+ return false;
+}
+
+/**
+ * q6core_get_svc_api_info() - Get version number of a service.
+ *
+ * @svc_id: service id of the service.
+ * @ainfo: Valid struct pointer to fill svc api information.
+ *
+ * Return: zero on success and error code on failure or unsupported
+ */
+int q6core_get_svc_api_info(int svc_id, struct q6core_svc_api_info *ainfo)
+{
+ int i;
+ int ret = -ENOTSUPP;
+
+ if (!g_core || !ainfo)
+ return 0;
+
+ mutex_lock(&g_core->lock);
+ if (!g_core->is_version_requested) {
+ if (q6core_get_fwk_versions(g_core) == -ENOTSUPP)
+ q6core_get_svc_versions(g_core);
+ g_core->is_version_requested = true;
+ }
+
+ if (g_core->fwk_version_supported) {
+ for (i = 0; i < g_core->fwk_version->num_services; i++) {
+ struct avcs_svc_api_info *info;
+
+ info = &g_core->fwk_version->svc_api_info[i];
+ if (svc_id != info->service_id)
+ continue;
+
+ ainfo->api_version = info->api_version;
+ ainfo->api_branch_version = info->api_branch_version;
+ ret = 0;
+ break;
+ }
+ } else if (g_core->get_version_supported) {
+ for (i = 0; i < g_core->svc_version->num_services; i++) {
+ struct avcs_svc_info *info;
+
+ info = &g_core->svc_version->svc_api_info[i];
+ if (svc_id != info->service_id)
+ continue;
+
+ ainfo->api_version = info->version;
+ ainfo->api_branch_version = 0;
+ ret = 0;
+ break;
+ }
+ }
+
+ mutex_unlock(&g_core->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(q6core_get_svc_api_info);
+
+/**
+ * q6core_is_adsp_ready() - Get status of adsp
+ *
+ * Return: Will be an true if adsp is ready and false if not.
+ */
+bool q6core_is_adsp_ready(void)
+{
+ unsigned long timeout;
+ bool ret = false;
+
+ if (!g_core)
+ return false;
+
+ mutex_lock(&g_core->lock);
+ timeout = jiffies + msecs_to_jiffies(ADSP_STATE_READY_TIMEOUT_MS);
+ for (;;) {
+ if (__q6core_is_adsp_ready(g_core)) {
+ ret = true;
+ break;
+ }
+
+ if (!time_after(timeout, jiffies)) {
+ ret = false;
+ break;
+ }
+ }
+
+ mutex_unlock(&g_core->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(q6core_is_adsp_ready);
+
+static int q6core_probe(struct apr_device *adev)
+{
+ g_core = kzalloc_obj(*g_core);
+ if (!g_core)
+ return -ENOMEM;
+
+ dev_set_drvdata(&adev->dev, g_core);
+
+ mutex_init(&g_core->lock);
+ g_core->adev = adev;
+ init_waitqueue_head(&g_core->wait);
+ return 0;
+}
+
+static void q6core_exit(struct apr_device *adev)
+{
+ struct q6core *core = dev_get_drvdata(&adev->dev);
+
+ if (core->fwk_version_supported)
+ kfree(core->fwk_version);
+ if (core->get_version_supported)
+ kfree(core->svc_version);
+
+ g_core = NULL;
+ kfree(core);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id q6core_device_id[] = {
+ { .compatible = "qcom,q6core" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6core_device_id);
+#endif
+
+static struct apr_driver qcom_q6core_driver = {
+ .probe = q6core_probe,
+ .remove = q6core_exit,
+ .callback = q6core_callback,
+ .driver = {
+ .name = "qcom-q6core",
+ .of_match_table = of_match_ptr(q6core_device_id),
+ },
+};
+
+module_apr_driver(qcom_q6core_driver);
+MODULE_DESCRIPTION("q6 core");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/qdsp6/q6core.h b/sound/soc/qcom/qdsp6/q6core.h
new file mode 100644
index 000000000..4105b1d73
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6core.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __Q6CORE_H__
+#define __Q6CORE_H__
+
+struct q6core_svc_api_info {
+ uint32_t service_id;
+ uint32_t api_version;
+ uint32_t api_branch_version;
+};
+
+bool q6core_is_adsp_ready(void);
+int q6core_get_svc_api_info(int svc_id, struct q6core_svc_api_info *ainfo);
+
+#endif /* __Q6CORE_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6dsp-common.c b/sound/soc/qcom/qdsp6/q6dsp-common.c
new file mode 100644
index 000000000..f74585d88
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6dsp-common.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include "q6dsp-common.h"
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+
+int q6dsp_map_channels(u8 ch_map[PCM_MAX_NUM_CHANNEL], int ch)
+{
+ memset(ch_map, 0, PCM_MAX_NUM_CHANNEL);
+
+ switch (ch) {
+ case 1:
+ ch_map[0] = PCM_CHANNEL_FC;
+ break;
+ case 2:
+ ch_map[0] = PCM_CHANNEL_FL;
+ ch_map[1] = PCM_CHANNEL_FR;
+ break;
+ case 3:
+ ch_map[0] = PCM_CHANNEL_FL;
+ ch_map[1] = PCM_CHANNEL_FR;
+ ch_map[2] = PCM_CHANNEL_FC;
+ break;
+ case 4:
+ ch_map[0] = PCM_CHANNEL_FL;
+ ch_map[1] = PCM_CHANNEL_FR;
+ ch_map[2] = PCM_CHANNEL_LS;
+ ch_map[3] = PCM_CHANNEL_RS;
+ break;
+ case 5:
+ ch_map[0] = PCM_CHANNEL_FL;
+ ch_map[1] = PCM_CHANNEL_FR;
+ ch_map[2] = PCM_CHANNEL_FC;
+ ch_map[3] = PCM_CHANNEL_LS;
+ ch_map[4] = PCM_CHANNEL_RS;
+ break;
+ case 6:
+ ch_map[0] = PCM_CHANNEL_FL;
+ ch_map[1] = PCM_CHANNEL_FR;
+ ch_map[2] = PCM_CHANNEL_LFE;
+ ch_map[3] = PCM_CHANNEL_FC;
+ ch_map[4] = PCM_CHANNEL_LS;
+ ch_map[5] = PCM_CHANNEL_RS;
+ break;
+ case 8:
+ ch_map[0] = PCM_CHANNEL_FL;
+ ch_map[1] = PCM_CHANNEL_FR;
+ ch_map[2] = PCM_CHANNEL_LFE;
+ ch_map[3] = PCM_CHANNEL_FC;
+ ch_map[4] = PCM_CHANNEL_LS;
+ ch_map[5] = PCM_CHANNEL_RS;
+ ch_map[6] = PCM_CHANNEL_LB;
+ ch_map[7] = PCM_CHANNEL_RB;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(q6dsp_map_channels);
+
+int q6dsp_get_channel_allocation(int channels)
+{
+ int channel_allocation;
+
+ /* HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4 */
+ switch (channels) {
+ case 2:
+ channel_allocation = 0;
+ break;
+ case 3:
+ channel_allocation = 0x02;
+ break;
+ case 4:
+ channel_allocation = 0x06;
+ break;
+ case 5:
+ channel_allocation = 0x0A;
+ break;
+ case 6:
+ channel_allocation = 0x0B;
+ break;
+ case 7:
+ channel_allocation = 0x12;
+ break;
+ case 8:
+ channel_allocation = 0x13;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return channel_allocation;
+}
+EXPORT_SYMBOL_GPL(q6dsp_get_channel_allocation);
+
+MODULE_DESCRIPTION("ASoC MSM QDSP6 helper functions");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/qdsp6/q6dsp-common.h b/sound/soc/qcom/qdsp6/q6dsp-common.h
new file mode 100644
index 000000000..9e704db5f
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6dsp-common.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __Q6DSP_COMMON_H__
+#define __Q6DSP_COMMON_H__
+
+#include <linux/kernel.h>
+
+#define PCM_MAX_NUM_CHANNEL 8
+#define PCM_CHANNEL_NULL 0
+
+#define PCM_CHANNEL_FL 1 /* Front left channel. */
+#define PCM_CHANNEL_FR 2 /* Front right channel. */
+#define PCM_CHANNEL_FC 3 /* Front center channel. */
+#define PCM_CHANNEL_LS 4 /* Left surround channel. */
+#define PCM_CHANNEL_RS 5 /* Right surround channel. */
+#define PCM_CHANNEL_LFE 6 /* Low frequency effect channel. */
+#define PCM_CHANNEL_CS 7 /* Center surround channel; Rear center ch */
+#define PCM_CHANNEL_LB 8 /* Left back channel; Rear left channel. */
+#define PCM_CHANNEL_RB 9 /* Right back channel; Rear right channel. */
+#define PCM_CHANNELS 10 /* Top surround channel. */
+
+int q6dsp_map_channels(u8 ch_map[PCM_MAX_NUM_CHANNEL], int ch);
+int q6dsp_get_channel_allocation(int channels);
+
+#endif /* __Q6DSP_COMMON_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6dsp-errno.h b/sound/soc/qcom/qdsp6/q6dsp-errno.h
new file mode 100644
index 000000000..1ec00ff8c
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6dsp-errno.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __Q6DSP_ERR_NO_H__
+#define __Q6DSP_ERR_NO_H__
+#include <linux/kernel.h>
+
+/* Success. The operation completed with no errors. */
+#define ADSP_EOK 0x00000000
+/* General failure. */
+#define ADSP_EFAILED 0x00000001
+/* Bad operation parameter. */
+#define ADSP_EBADPARAM 0x00000002
+/* Unsupported routine or operation. */
+#define ADSP_EUNSUPPORTED 0x00000003
+/* Unsupported version. */
+#define ADSP_EVERSION 0x00000004
+/* Unexpected problem encountered. */
+#define ADSP_EUNEXPECTED 0x00000005
+/* Unhandled problem occurred. */
+#define ADSP_EPANIC 0x00000006
+/* Unable to allocate resource. */
+#define ADSP_ENORESOURCE 0x00000007
+/* Invalid handle. */
+#define ADSP_EHANDLE 0x00000008
+/* Operation is already processed. */
+#define ADSP_EALREADY 0x00000009
+/* Operation is not ready to be processed. */
+#define ADSP_ENOTREADY 0x0000000A
+/* Operation is pending completion. */
+#define ADSP_EPENDING 0x0000000B
+/* Operation could not be accepted or processed. */
+#define ADSP_EBUSY 0x0000000C
+/* Operation aborted due to an error. */
+#define ADSP_EABORTED 0x0000000D
+/* Operation preempted by a higher priority. */
+#define ADSP_EPREEMPTED 0x0000000E
+/* Operation requests intervention to complete. */
+#define ADSP_ECONTINUE 0x0000000F
+/* Operation requests immediate intervention to complete. */
+#define ADSP_EIMMEDIATE 0x00000010
+/* Operation is not implemented. */
+#define ADSP_ENOTIMPL 0x00000011
+/* Operation needs more data or resources. */
+#define ADSP_ENEEDMORE 0x00000012
+/* Operation does not have memory. */
+#define ADSP_ENOMEMORY 0x00000014
+/* Item does not exist. */
+#define ADSP_ENOTEXIST 0x00000015
+/* Max count for adsp error code sent to HLOS*/
+
+#endif /*__Q6DSP_ERR_NO_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c
new file mode 100644
index 000000000..ab7d20580
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020, Linaro Limited
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h>
+#include "q6dsp-lpass-clocks.h"
+
+#define Q6DSP_MAX_CLK_ID 105
+#define Q6DSP_LPASS_CLK_ROOT_DEFAULT 0
+
+
+struct q6dsp_clk {
+ struct device *dev;
+ int q6dsp_clk_id;
+ int attributes;
+ int rate;
+ uint32_t handle;
+ struct clk_hw hw;
+};
+
+#define to_q6dsp_clk(_hw) container_of(_hw, struct q6dsp_clk, hw)
+
+struct q6dsp_cc {
+ struct device *dev;
+ struct q6dsp_clk *clks[Q6DSP_MAX_CLK_ID];
+ const struct q6dsp_clk_desc *desc;
+};
+
+static int clk_q6dsp_prepare(struct clk_hw *hw)
+{
+ struct q6dsp_clk *clk = to_q6dsp_clk(hw);
+ struct q6dsp_cc *cc = dev_get_drvdata(clk->dev);
+
+ return cc->desc->lpass_set_clk(clk->dev, clk->q6dsp_clk_id, clk->attributes,
+ Q6DSP_LPASS_CLK_ROOT_DEFAULT, clk->rate);
+}
+
+static void clk_q6dsp_unprepare(struct clk_hw *hw)
+{
+ struct q6dsp_clk *clk = to_q6dsp_clk(hw);
+ struct q6dsp_cc *cc = dev_get_drvdata(clk->dev);
+
+ cc->desc->lpass_set_clk(clk->dev, clk->q6dsp_clk_id, clk->attributes,
+ Q6DSP_LPASS_CLK_ROOT_DEFAULT, 0);
+}
+
+static int clk_q6dsp_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct q6dsp_clk *clk = to_q6dsp_clk(hw);
+
+ clk->rate = rate;
+
+ return 0;
+}
+
+static unsigned long clk_q6dsp_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct q6dsp_clk *clk = to_q6dsp_clk(hw);
+
+ return clk->rate;
+}
+
+static int clk_q6dsp_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ return 0;
+}
+
+static const struct clk_ops clk_q6dsp_ops = {
+ .prepare = clk_q6dsp_prepare,
+ .unprepare = clk_q6dsp_unprepare,
+ .set_rate = clk_q6dsp_set_rate,
+ .determine_rate = clk_q6dsp_determine_rate,
+ .recalc_rate = clk_q6dsp_recalc_rate,
+};
+
+static int clk_vote_q6dsp_block(struct clk_hw *hw)
+{
+ struct q6dsp_clk *clk = to_q6dsp_clk(hw);
+ struct q6dsp_cc *cc = dev_get_drvdata(clk->dev);
+
+ return cc->desc->lpass_vote_clk(clk->dev, clk->q6dsp_clk_id,
+ clk_hw_get_name(&clk->hw), &clk->handle);
+}
+
+static void clk_unvote_q6dsp_block(struct clk_hw *hw)
+{
+ struct q6dsp_clk *clk = to_q6dsp_clk(hw);
+ struct q6dsp_cc *cc = dev_get_drvdata(clk->dev);
+
+ cc->desc->lpass_unvote_clk(clk->dev, clk->q6dsp_clk_id, clk->handle);
+}
+
+static const struct clk_ops clk_vote_q6dsp_ops = {
+ .prepare = clk_vote_q6dsp_block,
+ .unprepare = clk_unvote_q6dsp_block,
+};
+
+
+static struct clk_hw *q6dsp_of_clk_hw_get(struct of_phandle_args *clkspec,
+ void *data)
+{
+ struct q6dsp_cc *cc = data;
+ unsigned int idx = clkspec->args[0];
+ unsigned int attr = clkspec->args[1];
+
+ if (idx >= Q6DSP_MAX_CLK_ID || attr > LPASS_CLK_ATTRIBUTE_COUPLE_DIVISOR) {
+ dev_err(cc->dev, "Invalid clk specifier (%d, %d)\n", idx, attr);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (cc->clks[idx]) {
+ cc->clks[idx]->attributes = attr;
+ return &cc->clks[idx]->hw;
+ }
+
+ return ERR_PTR(-ENOENT);
+}
+
+int q6dsp_clock_dev_probe(struct platform_device *pdev)
+{
+ struct q6dsp_cc *cc;
+ struct device *dev = &pdev->dev;
+ const struct q6dsp_clk_init *q6dsp_clks;
+ const struct q6dsp_clk_desc *desc;
+ int i, ret;
+
+ cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL);
+ if (!cc)
+ return -ENOMEM;
+
+ desc = of_device_get_match_data(&pdev->dev);
+ if (!desc)
+ return -EINVAL;
+
+ cc->desc = desc;
+ cc->dev = dev;
+ q6dsp_clks = desc->clks;
+
+ for (i = 0; i < desc->num_clks; i++) {
+ unsigned int id = q6dsp_clks[i].clk_id;
+ struct clk_init_data init = {
+ .name = q6dsp_clks[i].name,
+ };
+ struct q6dsp_clk *clk;
+
+ clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL);
+ if (!clk)
+ return -ENOMEM;
+
+ clk->dev = dev;
+ clk->q6dsp_clk_id = q6dsp_clks[i].q6dsp_clk_id;
+ clk->rate = q6dsp_clks[i].rate;
+ clk->hw.init = &init;
+
+ if (clk->rate)
+ init.ops = &clk_q6dsp_ops;
+ else
+ init.ops = &clk_vote_q6dsp_ops;
+
+ cc->clks[id] = clk;
+
+ ret = devm_clk_hw_register(dev, &clk->hw);
+ if (ret)
+ return ret;
+ }
+
+ ret = devm_of_clk_add_hw_provider(dev, q6dsp_of_clk_hw_get, cc);
+ if (ret)
+ return ret;
+
+ dev_set_drvdata(dev, cc);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(q6dsp_clock_dev_probe);
diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.h b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.h
new file mode 100644
index 000000000..3770d81f2
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __Q6DSP_AUDIO_CLOCKS_H__
+#define __Q6DSP_AUDIO_CLOCKS_H__
+
+struct q6dsp_clk_init {
+ int clk_id;
+ int q6dsp_clk_id;
+ char *name;
+ int rate;
+};
+
+#define Q6DSP_VOTE_CLK(id, blkid, n) { \
+ .clk_id = id, \
+ .q6dsp_clk_id = blkid, \
+ .name = n, \
+ }
+
+struct q6dsp_clk_desc {
+ const struct q6dsp_clk_init *clks;
+ size_t num_clks;
+ int (*lpass_set_clk)(struct device *dev, int clk_id, int attr,
+ int root_clk, unsigned int freq);
+ int (*lpass_vote_clk)(struct device *dev, uint32_t hid, const char *n, uint32_t *h);
+ int (*lpass_unvote_clk)(struct device *dev, uint32_t hid, uint32_t h);
+};
+
+int q6dsp_clock_dev_probe(struct platform_device *pdev);
+
+#endif /* __Q6DSP_AUDIO_CLOCKS_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c
new file mode 100644
index 000000000..c3d8116ad
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c
@@ -0,0 +1,739 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020, Linaro Limited
+
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include "q6dsp-lpass-ports.h"
+
+#define Q6AFE_TDM_PB_DAI(pre, num, did) { \
+ .playback = { \
+ .stream_name = pre" TDM"#num" Playback", \
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_176400, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ .channels_min = 1, \
+ .channels_max = 8, \
+ .rate_min = 8000, \
+ .rate_max = 176400, \
+ }, \
+ .name = #did, \
+ .id = did, \
+ }
+
+#define Q6AFE_TDM_CAP_DAI(pre, num, did) { \
+ .capture = { \
+ .stream_name = pre" TDM"#num" Capture", \
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_176400, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ .channels_min = 1, \
+ .channels_max = 8, \
+ .rate_min = 8000, \
+ .rate_max = 176400, \
+ }, \
+ .name = #did, \
+ .id = did, \
+ }
+
+#define Q6AFE_CDC_DMA_RX_DAI(did) { \
+ .playback = { \
+ .stream_name = #did" Playback", \
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_176400, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ .channels_min = 1, \
+ .channels_max = 8, \
+ .rate_min = 8000, \
+ .rate_max = 176400, \
+ }, \
+ .name = #did, \
+ .id = did, \
+ }
+
+#define Q6AFE_CDC_DMA_TX_DAI(did) { \
+ .capture = { \
+ .stream_name = #did" Capture", \
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_176400, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ .channels_min = 1, \
+ .channels_max = 8, \
+ .rate_min = 8000, \
+ .rate_max = 176400, \
+ }, \
+ .name = #did, \
+ .id = did, \
+ }
+
+#define Q6AFE_DP_RX_DAI(did) { \
+ .playback = { \
+ .stream_name = #did" Playback", \
+ .rates = SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_192000, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE, \
+ .channels_min = 2, \
+ .channels_max = 8, \
+ .rate_min = 48000, \
+ .rate_max = 192000, \
+ }, \
+ .name = #did, \
+ .id = did, \
+ }
+
+#define Q6AFE_MI2S_RX_DAI(pre, did) { \
+ .playback = { \
+ .stream_name = pre" MI2S Playback", \
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_176400, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ .channels_min = 1, \
+ .channels_max = 8, \
+ .rate_min = 8000, \
+ .rate_max = 176400, \
+ }, \
+ .name = #did, \
+ .id = did, \
+ }
+
+#define Q6AFE_MI2S_TX_DAI(pre, did) { \
+ .capture = { \
+ .stream_name = pre" MI2S Capture", \
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_176400, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ .channels_min = 1, \
+ .channels_max = 8, \
+ .rate_min = 8000, \
+ .rate_max = 176400, \
+ }, \
+ .name = #did, \
+ .id = did, \
+ }
+
+static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = {
+ {
+ .playback = {
+ .stream_name = "USB Playback",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |
+ SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE |
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |
+ SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .id = USB_RX,
+ .name = "USB_RX",
+ },
+ {
+ .playback = {
+ .stream_name = "HDMI Playback",
+ .rates = SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_max = 192000,
+ .rate_min = 48000,
+ },
+ .id = HDMI_RX,
+ .name = "HDMI",
+ }, {
+ .name = "SLIMBUS_0_RX",
+ .id = SLIMBUS_0_RX,
+ .playback = {
+ .stream_name = "Slimbus Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ }, {
+ .name = "SLIMBUS_0_TX",
+ .id = SLIMBUS_0_TX,
+ .capture = {
+ .stream_name = "Slimbus Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ }, {
+ .playback = {
+ .stream_name = "Slimbus1 Playback",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .name = "SLIMBUS_1_RX",
+ .id = SLIMBUS_1_RX,
+ }, {
+ .name = "SLIMBUS_1_TX",
+ .id = SLIMBUS_1_TX,
+ .capture = {
+ .stream_name = "Slimbus1 Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ }, {
+ .playback = {
+ .stream_name = "Slimbus2 Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .name = "SLIMBUS_2_RX",
+ .id = SLIMBUS_2_RX,
+
+ }, {
+ .name = "SLIMBUS_2_TX",
+ .id = SLIMBUS_2_TX,
+ .capture = {
+ .stream_name = "Slimbus2 Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ }, {
+ .playback = {
+ .stream_name = "Slimbus3 Playback",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .name = "SLIMBUS_3_RX",
+ .id = SLIMBUS_3_RX,
+
+ }, {
+ .name = "SLIMBUS_3_TX",
+ .id = SLIMBUS_3_TX,
+ .capture = {
+ .stream_name = "Slimbus3 Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ }, {
+ .playback = {
+ .stream_name = "Slimbus4 Playback",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .name = "SLIMBUS_4_RX",
+ .id = SLIMBUS_4_RX,
+
+ }, {
+ .name = "SLIMBUS_4_TX",
+ .id = SLIMBUS_4_TX,
+ .capture = {
+ .stream_name = "Slimbus4 Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ }, {
+ .playback = {
+ .stream_name = "Slimbus5 Playback",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .name = "SLIMBUS_5_RX",
+ .id = SLIMBUS_5_RX,
+
+ }, {
+ .name = "SLIMBUS_5_TX",
+ .id = SLIMBUS_5_TX,
+ .capture = {
+ .stream_name = "Slimbus5 Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ }, {
+ .playback = {
+ .stream_name = "Slimbus6 Playback",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .name = "SLIMBUS_6_RX",
+ .id = SLIMBUS_6_RX,
+
+ }, {
+ .name = "SLIMBUS_6_TX",
+ .id = SLIMBUS_6_TX,
+ .capture = {
+ .stream_name = "Slimbus6 Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ }, {
+ .playback = {
+ .stream_name = "Primary MI2S Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .id = PRIMARY_MI2S_RX,
+ .name = "PRI_MI2S_RX",
+ }, {
+ .capture = {
+ .stream_name = "Primary MI2S Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .id = PRIMARY_MI2S_TX,
+ .name = "PRI_MI2S_TX",
+ }, {
+ .playback = {
+ .stream_name = "Secondary MI2S Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .name = "SEC_MI2S_RX",
+ .id = SECONDARY_MI2S_RX,
+ }, {
+ .capture = {
+ .stream_name = "Secondary MI2S Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .id = SECONDARY_MI2S_TX,
+ .name = "SEC_MI2S_TX",
+ }, {
+ .playback = {
+ .stream_name = "Tertiary MI2S Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .name = "TERT_MI2S_RX",
+ .id = TERTIARY_MI2S_RX,
+ }, {
+ .capture = {
+ .stream_name = "Tertiary MI2S Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .id = TERTIARY_MI2S_TX,
+ .name = "TERT_MI2S_TX",
+ }, {
+ .playback = {
+ .stream_name = "Quaternary MI2S Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .name = "QUAT_MI2S_RX",
+ .id = QUATERNARY_MI2S_RX,
+ }, {
+ .capture = {
+ .stream_name = "Quaternary MI2S Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .id = QUATERNARY_MI2S_TX,
+ .name = "QUAT_MI2S_TX",
+ }, {
+ .playback = {
+ .stream_name = "Quinary MI2S Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .id = QUINARY_MI2S_RX,
+ .name = "QUIN_MI2S_RX",
+ }, {
+ .capture = {
+ .stream_name = "Quinary MI2S Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .id = QUINARY_MI2S_TX,
+ .name = "QUIN_MI2S_TX",
+ }, {
+ .playback = {
+ .stream_name = "Senary MI2S Playback",
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .id = SENARY_MI2S_RX,
+ .name = "SEN_MI2S_RX",
+ }, {
+ .capture = {
+ .stream_name = "Senary MI2S Capture",
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .id = SENARY_MI2S_TX,
+ .name = "SEN_MI2S_TX",
+ },
+ Q6AFE_MI2S_RX_DAI("LPI RX0", LPI_MI2S_RX_0),
+ Q6AFE_MI2S_RX_DAI("LPI RX1", LPI_MI2S_RX_1),
+ Q6AFE_MI2S_RX_DAI("LPI RX2", LPI_MI2S_RX_2),
+ Q6AFE_MI2S_RX_DAI("LPI RX3", LPI_MI2S_RX_3),
+ Q6AFE_MI2S_RX_DAI("LPI RX4", LPI_MI2S_RX_4),
+ Q6AFE_MI2S_RX_DAI("LPI RX5", LPI_MI2S_RX_5),
+ Q6AFE_MI2S_RX_DAI("LPI RX6", LPI_MI2S_RX_6),
+ Q6AFE_MI2S_TX_DAI("LPI TX0", LPI_MI2S_TX_0),
+ Q6AFE_MI2S_TX_DAI("LPI TX1", LPI_MI2S_TX_1),
+ Q6AFE_MI2S_TX_DAI("LPI TX2", LPI_MI2S_TX_2),
+ Q6AFE_MI2S_TX_DAI("LPI TX3", LPI_MI2S_TX_3),
+ Q6AFE_MI2S_TX_DAI("LPI TX4", LPI_MI2S_TX_4),
+ Q6AFE_MI2S_TX_DAI("LPI TX5", LPI_MI2S_TX_5),
+ Q6AFE_MI2S_TX_DAI("LPI TX6", LPI_MI2S_TX_6),
+ Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0),
+ Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1),
+ Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2),
+ Q6AFE_TDM_PB_DAI("Primary", 3, PRIMARY_TDM_RX_3),
+ Q6AFE_TDM_PB_DAI("Primary", 4, PRIMARY_TDM_RX_4),
+ Q6AFE_TDM_PB_DAI("Primary", 5, PRIMARY_TDM_RX_5),
+ Q6AFE_TDM_PB_DAI("Primary", 6, PRIMARY_TDM_RX_6),
+ Q6AFE_TDM_PB_DAI("Primary", 7, PRIMARY_TDM_RX_7),
+ Q6AFE_TDM_CAP_DAI("Primary", 0, PRIMARY_TDM_TX_0),
+ Q6AFE_TDM_CAP_DAI("Primary", 1, PRIMARY_TDM_TX_1),
+ Q6AFE_TDM_CAP_DAI("Primary", 2, PRIMARY_TDM_TX_2),
+ Q6AFE_TDM_CAP_DAI("Primary", 3, PRIMARY_TDM_TX_3),
+ Q6AFE_TDM_CAP_DAI("Primary", 4, PRIMARY_TDM_TX_4),
+ Q6AFE_TDM_CAP_DAI("Primary", 5, PRIMARY_TDM_TX_5),
+ Q6AFE_TDM_CAP_DAI("Primary", 6, PRIMARY_TDM_TX_6),
+ Q6AFE_TDM_CAP_DAI("Primary", 7, PRIMARY_TDM_TX_7),
+ Q6AFE_TDM_PB_DAI("Secondary", 0, SECONDARY_TDM_RX_0),
+ Q6AFE_TDM_PB_DAI("Secondary", 1, SECONDARY_TDM_RX_1),
+ Q6AFE_TDM_PB_DAI("Secondary", 2, SECONDARY_TDM_RX_2),
+ Q6AFE_TDM_PB_DAI("Secondary", 3, SECONDARY_TDM_RX_3),
+ Q6AFE_TDM_PB_DAI("Secondary", 4, SECONDARY_TDM_RX_4),
+ Q6AFE_TDM_PB_DAI("Secondary", 5, SECONDARY_TDM_RX_5),
+ Q6AFE_TDM_PB_DAI("Secondary", 6, SECONDARY_TDM_RX_6),
+ Q6AFE_TDM_PB_DAI("Secondary", 7, SECONDARY_TDM_RX_7),
+ Q6AFE_TDM_CAP_DAI("Secondary", 0, SECONDARY_TDM_TX_0),
+ Q6AFE_TDM_CAP_DAI("Secondary", 1, SECONDARY_TDM_TX_1),
+ Q6AFE_TDM_CAP_DAI("Secondary", 2, SECONDARY_TDM_TX_2),
+ Q6AFE_TDM_CAP_DAI("Secondary", 3, SECONDARY_TDM_TX_3),
+ Q6AFE_TDM_CAP_DAI("Secondary", 4, SECONDARY_TDM_TX_4),
+ Q6AFE_TDM_CAP_DAI("Secondary", 5, SECONDARY_TDM_TX_5),
+ Q6AFE_TDM_CAP_DAI("Secondary", 6, SECONDARY_TDM_TX_6),
+ Q6AFE_TDM_CAP_DAI("Secondary", 7, SECONDARY_TDM_TX_7),
+ Q6AFE_TDM_PB_DAI("Tertiary", 0, TERTIARY_TDM_RX_0),
+ Q6AFE_TDM_PB_DAI("Tertiary", 1, TERTIARY_TDM_RX_1),
+ Q6AFE_TDM_PB_DAI("Tertiary", 2, TERTIARY_TDM_RX_2),
+ Q6AFE_TDM_PB_DAI("Tertiary", 3, TERTIARY_TDM_RX_3),
+ Q6AFE_TDM_PB_DAI("Tertiary", 4, TERTIARY_TDM_RX_4),
+ Q6AFE_TDM_PB_DAI("Tertiary", 5, TERTIARY_TDM_RX_5),
+ Q6AFE_TDM_PB_DAI("Tertiary", 6, TERTIARY_TDM_RX_6),
+ Q6AFE_TDM_PB_DAI("Tertiary", 7, TERTIARY_TDM_RX_7),
+ Q6AFE_TDM_CAP_DAI("Tertiary", 0, TERTIARY_TDM_TX_0),
+ Q6AFE_TDM_CAP_DAI("Tertiary", 1, TERTIARY_TDM_TX_1),
+ Q6AFE_TDM_CAP_DAI("Tertiary", 2, TERTIARY_TDM_TX_2),
+ Q6AFE_TDM_CAP_DAI("Tertiary", 3, TERTIARY_TDM_TX_3),
+ Q6AFE_TDM_CAP_DAI("Tertiary", 4, TERTIARY_TDM_TX_4),
+ Q6AFE_TDM_CAP_DAI("Tertiary", 5, TERTIARY_TDM_TX_5),
+ Q6AFE_TDM_CAP_DAI("Tertiary", 6, TERTIARY_TDM_TX_6),
+ Q6AFE_TDM_CAP_DAI("Tertiary", 7, TERTIARY_TDM_TX_7),
+ Q6AFE_TDM_PB_DAI("Quaternary", 0, QUATERNARY_TDM_RX_0),
+ Q6AFE_TDM_PB_DAI("Quaternary", 1, QUATERNARY_TDM_RX_1),
+ Q6AFE_TDM_PB_DAI("Quaternary", 2, QUATERNARY_TDM_RX_2),
+ Q6AFE_TDM_PB_DAI("Quaternary", 3, QUATERNARY_TDM_RX_3),
+ Q6AFE_TDM_PB_DAI("Quaternary", 4, QUATERNARY_TDM_RX_4),
+ Q6AFE_TDM_PB_DAI("Quaternary", 5, QUATERNARY_TDM_RX_5),
+ Q6AFE_TDM_PB_DAI("Quaternary", 6, QUATERNARY_TDM_RX_6),
+ Q6AFE_TDM_PB_DAI("Quaternary", 7, QUATERNARY_TDM_RX_7),
+ Q6AFE_TDM_CAP_DAI("Quaternary", 0, QUATERNARY_TDM_TX_0),
+ Q6AFE_TDM_CAP_DAI("Quaternary", 1, QUATERNARY_TDM_TX_1),
+ Q6AFE_TDM_CAP_DAI("Quaternary", 2, QUATERNARY_TDM_TX_2),
+ Q6AFE_TDM_CAP_DAI("Quaternary", 3, QUATERNARY_TDM_TX_3),
+ Q6AFE_TDM_CAP_DAI("Quaternary", 4, QUATERNARY_TDM_TX_4),
+ Q6AFE_TDM_CAP_DAI("Quaternary", 5, QUATERNARY_TDM_TX_5),
+ Q6AFE_TDM_CAP_DAI("Quaternary", 6, QUATERNARY_TDM_TX_6),
+ Q6AFE_TDM_CAP_DAI("Quaternary", 7, QUATERNARY_TDM_TX_7),
+ Q6AFE_TDM_PB_DAI("Quinary", 0, QUINARY_TDM_RX_0),
+ Q6AFE_TDM_PB_DAI("Quinary", 1, QUINARY_TDM_RX_1),
+ Q6AFE_TDM_PB_DAI("Quinary", 2, QUINARY_TDM_RX_2),
+ Q6AFE_TDM_PB_DAI("Quinary", 3, QUINARY_TDM_RX_3),
+ Q6AFE_TDM_PB_DAI("Quinary", 4, QUINARY_TDM_RX_4),
+ Q6AFE_TDM_PB_DAI("Quinary", 5, QUINARY_TDM_RX_5),
+ Q6AFE_TDM_PB_DAI("Quinary", 6, QUINARY_TDM_RX_6),
+ Q6AFE_TDM_PB_DAI("Quinary", 7, QUINARY_TDM_RX_7),
+ Q6AFE_TDM_CAP_DAI("Quinary", 0, QUINARY_TDM_TX_0),
+ Q6AFE_TDM_CAP_DAI("Quinary", 1, QUINARY_TDM_TX_1),
+ Q6AFE_TDM_CAP_DAI("Quinary", 2, QUINARY_TDM_TX_2),
+ Q6AFE_TDM_CAP_DAI("Quinary", 3, QUINARY_TDM_TX_3),
+ Q6AFE_TDM_CAP_DAI("Quinary", 4, QUINARY_TDM_TX_4),
+ Q6AFE_TDM_CAP_DAI("Quinary", 5, QUINARY_TDM_TX_5),
+ Q6AFE_TDM_CAP_DAI("Quinary", 6, QUINARY_TDM_TX_6),
+ Q6AFE_TDM_CAP_DAI("Quinary", 7, QUINARY_TDM_TX_7),
+ Q6AFE_DP_RX_DAI(DISPLAY_PORT_RX_0),
+ Q6AFE_DP_RX_DAI(DISPLAY_PORT_RX_1),
+ Q6AFE_DP_RX_DAI(DISPLAY_PORT_RX_2),
+ Q6AFE_DP_RX_DAI(DISPLAY_PORT_RX_3),
+ Q6AFE_DP_RX_DAI(DISPLAY_PORT_RX_4),
+ Q6AFE_DP_RX_DAI(DISPLAY_PORT_RX_5),
+ Q6AFE_DP_RX_DAI(DISPLAY_PORT_RX_6),
+ Q6AFE_DP_RX_DAI(DISPLAY_PORT_RX_7),
+ Q6AFE_CDC_DMA_RX_DAI(WSA_CODEC_DMA_RX_0),
+ Q6AFE_CDC_DMA_TX_DAI(WSA_CODEC_DMA_TX_0),
+ Q6AFE_CDC_DMA_RX_DAI(WSA_CODEC_DMA_RX_1),
+ Q6AFE_CDC_DMA_TX_DAI(WSA_CODEC_DMA_TX_1),
+ Q6AFE_CDC_DMA_TX_DAI(WSA_CODEC_DMA_TX_2),
+ Q6AFE_CDC_DMA_TX_DAI(VA_CODEC_DMA_TX_0),
+ Q6AFE_CDC_DMA_TX_DAI(VA_CODEC_DMA_TX_1),
+ Q6AFE_CDC_DMA_TX_DAI(VA_CODEC_DMA_TX_2),
+ Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_0),
+ Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_0),
+ Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_1),
+ Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_1),
+ Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_2),
+ Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_2),
+ Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_3),
+ Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_3),
+ Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_4),
+ Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_4),
+ Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_5),
+ Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_5),
+ Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_6),
+ Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_7),
+};
+
+int q6dsp_audio_ports_of_xlate_dai_name(struct snd_soc_component *component,
+ const struct of_phandle_args *args,
+ const char **dai_name)
+{
+ int id = args->args[0];
+ int ret = -EINVAL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(q6dsp_audio_fe_dais); i++) {
+ if (q6dsp_audio_fe_dais[i].id == id) {
+ *dai_name = q6dsp_audio_fe_dais[i].name;
+ ret = 0;
+ break;
+ }
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(q6dsp_audio_ports_of_xlate_dai_name);
+
+struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev,
+ struct q6dsp_audio_port_dai_driver_config *cfg,
+ int *num_dais)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(q6dsp_audio_fe_dais); i++) {
+ switch (q6dsp_audio_fe_dais[i].id) {
+ case HDMI_RX:
+ case DISPLAY_PORT_RX:
+ q6dsp_audio_fe_dais[i].ops = cfg->q6hdmi_ops;
+ break;
+ case DISPLAY_PORT_RX_1 ... DISPLAY_PORT_RX_7:
+ q6dsp_audio_fe_dais[i].ops = cfg->q6hdmi_ops;
+ break;
+ case SLIMBUS_0_RX ... SLIMBUS_6_TX:
+ q6dsp_audio_fe_dais[i].ops = cfg->q6slim_ops;
+ break;
+ case SENARY_MI2S_RX ... SENARY_MI2S_TX:
+ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX:
+ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX:
+ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4:
+ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6:
+ q6dsp_audio_fe_dais[i].ops = cfg->q6i2s_ops;
+ break;
+ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7:
+ q6dsp_audio_fe_dais[i].ops = cfg->q6tdm_ops;
+ break;
+ case WSA_CODEC_DMA_RX_0 ... RX_CODEC_DMA_RX_7:
+ q6dsp_audio_fe_dais[i].ops = cfg->q6dma_ops;
+ break;
+ case USB_RX:
+ q6dsp_audio_fe_dais[i].ops = cfg->q6usb_ops;
+ break;
+ default:
+ break;
+ }
+ }
+
+ *num_dais = ARRAY_SIZE(q6dsp_audio_fe_dais);
+ return q6dsp_audio_fe_dais;
+}
+EXPORT_SYMBOL_GPL(q6dsp_audio_ports_set_config);
diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h
new file mode 100644
index 000000000..d8dde6dd0
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __Q6DSP_AUDIO_PORTS_H__
+#define __Q6DSP_AUDIO_PORTS_H__
+
+struct q6dsp_audio_port_dai_driver_config {
+ int (*probe)(struct snd_soc_dai *dai);
+ int (*remove)(struct snd_soc_dai *dai);
+ const struct snd_soc_dai_ops *q6hdmi_ops;
+ const struct snd_soc_dai_ops *q6slim_ops;
+ const struct snd_soc_dai_ops *q6i2s_ops;
+ const struct snd_soc_dai_ops *q6tdm_ops;
+ const struct snd_soc_dai_ops *q6dma_ops;
+ const struct snd_soc_dai_ops *q6usb_ops;
+};
+
+struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev,
+ struct q6dsp_audio_port_dai_driver_config *cfg,
+ int *num_dais);
+int q6dsp_audio_ports_of_xlate_dai_name(struct snd_soc_component *component,
+ const struct of_phandle_args *args,
+ const char **dai_name);
+#endif /* __Q6DSP_AUDIO_PORTS_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c
new file mode 100644
index 000000000..b4f9abbe0
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2021, Linaro Limited
+
+#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include "q6dsp-lpass-clocks.h"
+#include "q6prm.h"
+
+#define Q6PRM_CLK(id) { \
+ .clk_id = id, \
+ .q6dsp_clk_id = Q6PRM_##id, \
+ .name = #id, \
+ .rate = 19200000, \
+ }
+
+static const struct q6dsp_clk_init q6prm_clks[] = {
+ Q6PRM_CLK(LPASS_CLK_ID_PRI_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_PRI_MI2S_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_SEC_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_SEC_MI2S_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_TER_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_TER_MI2S_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QUAD_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QUAD_MI2S_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_SPEAKER_I2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_SPEAKER_I2S_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_SPEAKER_I2S_OSR),
+ Q6PRM_CLK(LPASS_CLK_ID_QUI_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QUI_MI2S_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_SEN_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_SEN_MI2S_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_INT0_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_INT1_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_INT2_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_INT3_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_INT4_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_INT5_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_INT6_MI2S_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QUI_MI2S_OSR),
+ Q6PRM_CLK(LPASS_CLK_ID_MCLK_1),
+ Q6PRM_CLK(LPASS_CLK_ID_MCLK_2),
+ Q6PRM_CLK(LPASS_CLK_ID_MCLK_3),
+ Q6PRM_CLK(LPASS_CLK_ID_MCLK_4),
+ Q6PRM_CLK(LPASS_CLK_ID_MCLK_5),
+ Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_NPL_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_VA_CORE_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_TX_CORE_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_TX_CORE_NPL_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_NPL_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_VA_CORE_2X_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_2X_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_TX_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_TX_2X_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_TX_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_TX_2X_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_EBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_IBIT),
+ Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_EBIT),
+ Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS,
+ "LPASS_HW_MACRO"),
+ Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC,
+ "LPASS_HW_DCODEC"),
+ Q6DSP_VOTE_CLK(LPASS_HW_LPR_VOTE, Q6PRM_HW_LPR_VOTE,
+ "LPASS_HW_LPR_VOTE"),
+};
+
+static const struct q6dsp_clk_desc q6dsp_clk_q6prm __maybe_unused = {
+ .clks = q6prm_clks,
+ .num_clks = ARRAY_SIZE(q6prm_clks),
+ .lpass_set_clk = q6prm_set_lpass_clock,
+ .lpass_vote_clk = q6prm_vote_lpass_core_hw,
+ .lpass_unvote_clk = q6prm_unvote_lpass_core_hw,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id q6prm_clock_device_id[] = {
+ { .compatible = "qcom,q6prm-lpass-clocks", .data = &q6dsp_clk_q6prm },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6prm_clock_device_id);
+#endif
+
+static struct platform_driver q6prm_clock_platform_driver = {
+ .driver = {
+ .name = "q6prm-lpass-clock",
+ .of_match_table = of_match_ptr(q6prm_clock_device_id),
+ },
+ .probe = q6dsp_clock_dev_probe,
+};
+module_platform_driver(q6prm_clock_platform_driver);
+
+MODULE_DESCRIPTION("Q6 Proxy Resource Manager LPASS clock driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/qdsp6/q6prm.c b/sound/soc/qcom/qdsp6/q6prm.c
new file mode 100644
index 000000000..1f3ce4cc0
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6prm.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2021, Linaro Limited
+
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/of_platform.h>
+#include <linux/jiffies.h>
+#include <linux/soc/qcom/apr.h>
+#include <dt-bindings/soc/qcom,gpr.h>
+#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h>
+#include "q6apm.h"
+#include "q6prm.h"
+#include "audioreach.h"
+
+struct q6prm {
+ struct device *dev;
+ gpr_device_t *gdev;
+ wait_queue_head_t wait;
+ struct gpr_ibasic_rsp_result_t result;
+ struct mutex lock;
+};
+
+#define PRM_CMD_REQUEST_HW_RSC 0x0100100F
+#define PRM_CMD_RSP_REQUEST_HW_RSC 0x02001002
+#define PRM_CMD_RELEASE_HW_RSC 0x01001010
+#define PRM_CMD_RSP_RELEASE_HW_RSC 0x02001003
+#define PARAM_ID_RSC_HW_CORE 0x08001032
+#define PARAM_ID_RSC_LPASS_CORE 0x0800102B
+#define PARAM_ID_RSC_AUDIO_HW_CLK 0x0800102C
+#define PARAM_ID_RSC_CPU_LPR 0x08001A6E
+
+#define LPR_CPU_SS_SLEEP_DISABLE 0x1
+
+struct prm_cmd_request_hw_core {
+ struct apm_module_param_data param_data;
+ union {
+ u32 hw_clk_id;
+ u32 lpr_state;
+ };
+} __packed;
+
+struct prm_cmd_request_rsc {
+ struct apm_module_param_data param_data;
+ uint32_t num_clk_id;
+ struct audio_hw_clk_cfg clock_id;
+} __packed;
+
+struct prm_cmd_release_rsc {
+ struct apm_module_param_data param_data;
+ uint32_t num_clk_id;
+ struct audio_hw_clk_rel_cfg clock_id;
+} __packed;
+
+static int q6prm_send_cmd_sync(struct q6prm *prm, struct gpr_pkt *pkt, uint32_t rsp_opcode)
+{
+ return audioreach_send_cmd_sync(prm->dev, prm->gdev, &prm->result, &prm->lock,
+ NULL, &prm->wait, pkt, rsp_opcode);
+}
+
+static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool enable)
+{
+ struct q6prm *prm = dev_get_drvdata(dev->parent);
+ struct apm_module_param_data *param_data;
+ struct prm_cmd_request_hw_core *req;
+ gpr_device_t *gdev = prm->gdev;
+ uint32_t opcode, rsp_opcode;
+ bool lpr_req = (hw_block_id == Q6PRM_HW_LPR_VOTE);
+
+ if (enable) {
+ opcode = PRM_CMD_REQUEST_HW_RSC;
+ rsp_opcode = PRM_CMD_RSP_REQUEST_HW_RSC;
+ } else {
+ opcode = PRM_CMD_RELEASE_HW_RSC;
+ rsp_opcode = PRM_CMD_RSP_RELEASE_HW_RSC;
+ }
+
+ struct gpr_pkt *pkt __free(kfree) =
+ audioreach_alloc_cmd_pkt(sizeof(*req), opcode, 0, gdev->svc.id, GPR_PRM_MODULE_IID);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ req = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ param_data = &req->param_data;
+
+ param_data->module_instance_id = GPR_PRM_MODULE_IID;
+ param_data->error_code = 0;
+ param_data->param_id = lpr_req ? PARAM_ID_RSC_CPU_LPR : PARAM_ID_RSC_HW_CORE;
+ param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE;
+
+ if (lpr_req)
+ req->lpr_state = LPR_CPU_SS_SLEEP_DISABLE;
+ else
+ req->hw_clk_id = hw_block_id;
+
+ return q6prm_send_cmd_sync(prm, pkt, rsp_opcode);
+}
+
+int q6prm_vote_lpass_core_hw(struct device *dev, uint32_t hw_block_id,
+ const char *client_name, uint32_t *client_handle)
+{
+ return q6prm_set_hw_core_req(dev, hw_block_id, true);
+
+}
+EXPORT_SYMBOL_GPL(q6prm_vote_lpass_core_hw);
+
+int q6prm_unvote_lpass_core_hw(struct device *dev, uint32_t hw_block_id, uint32_t client_handle)
+{
+ return q6prm_set_hw_core_req(dev, hw_block_id, false);
+}
+EXPORT_SYMBOL_GPL(q6prm_unvote_lpass_core_hw);
+
+static int q6prm_request_lpass_clock(struct device *dev, int clk_id, int clk_attr, int clk_root,
+ unsigned int freq)
+{
+ struct q6prm *prm = dev_get_drvdata(dev->parent);
+ struct apm_module_param_data *param_data;
+ struct prm_cmd_request_rsc *req;
+ gpr_device_t *gdev = prm->gdev;
+
+ struct gpr_pkt *pkt __free(kfree) =
+ audioreach_alloc_cmd_pkt(sizeof(*req), PRM_CMD_REQUEST_HW_RSC, 0,
+ gdev->svc.id, GPR_PRM_MODULE_IID);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ req = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ param_data = &req->param_data;
+
+ param_data->module_instance_id = GPR_PRM_MODULE_IID;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_RSC_AUDIO_HW_CLK;
+ param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE;
+
+ req->num_clk_id = 1;
+ req->clock_id.clock_id = clk_id;
+ req->clock_id.clock_freq = freq;
+ req->clock_id.clock_attri = clk_attr;
+ req->clock_id.clock_root = clk_root;
+
+ return q6prm_send_cmd_sync(prm, pkt, PRM_CMD_RSP_REQUEST_HW_RSC);
+}
+
+static int q6prm_release_lpass_clock(struct device *dev, int clk_id, int clk_attr, int clk_root,
+ unsigned int freq)
+{
+ struct q6prm *prm = dev_get_drvdata(dev->parent);
+ struct apm_module_param_data *param_data;
+ struct prm_cmd_release_rsc *rel;
+ gpr_device_t *gdev = prm->gdev;
+
+ struct gpr_pkt *pkt __free(kfree) =
+ audioreach_alloc_cmd_pkt(sizeof(*rel), PRM_CMD_RELEASE_HW_RSC, 0,
+ gdev->svc.id, GPR_PRM_MODULE_IID);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ rel = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ param_data = &rel->param_data;
+
+ param_data->module_instance_id = GPR_PRM_MODULE_IID;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_RSC_AUDIO_HW_CLK;
+ param_data->param_size = sizeof(*rel) - APM_MODULE_PARAM_DATA_SIZE;
+
+ rel->num_clk_id = 1;
+ rel->clock_id.clock_id = clk_id;
+
+ return q6prm_send_cmd_sync(prm, pkt, PRM_CMD_RSP_RELEASE_HW_RSC);
+}
+
+int q6prm_set_lpass_clock(struct device *dev, int clk_id, int clk_attr, int clk_root,
+ unsigned int freq)
+{
+ if (freq)
+ return q6prm_request_lpass_clock(dev, clk_id, clk_attr, clk_root, freq);
+
+ return q6prm_release_lpass_clock(dev, clk_id, clk_attr, clk_root, freq);
+}
+EXPORT_SYMBOL_GPL(q6prm_set_lpass_clock);
+
+static int prm_callback(const struct gpr_resp_pkt *data, void *priv, int op)
+{
+ gpr_device_t *gdev = priv;
+ struct q6prm *prm = dev_get_drvdata(&gdev->dev);
+ const struct gpr_ibasic_rsp_result_t *result;
+ const struct gpr_hdr *hdr = &data->hdr;
+
+ switch (hdr->opcode) {
+ case PRM_CMD_RSP_REQUEST_HW_RSC:
+ case PRM_CMD_RSP_RELEASE_HW_RSC:
+ result = data->payload;
+ prm->result.opcode = hdr->opcode;
+ prm->result.status = result->status;
+ wake_up(&prm->wait);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int prm_probe(gpr_device_t *gdev)
+{
+ struct device *dev = &gdev->dev;
+ struct q6prm *cc;
+
+ cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL);
+ if (!cc)
+ return -ENOMEM;
+
+ cc->dev = dev;
+ cc->gdev = gdev;
+ mutex_init(&cc->lock);
+ init_waitqueue_head(&cc->wait);
+ dev_set_drvdata(dev, cc);
+
+ if (!q6apm_is_adsp_ready())
+ return -EPROBE_DEFER;
+
+ return devm_of_platform_populate(dev);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id prm_device_id[] = {
+ { .compatible = "qcom,q6prm" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, prm_device_id);
+#endif
+
+static gpr_driver_t prm_driver = {
+ .probe = prm_probe,
+ .gpr_callback = prm_callback,
+ .driver = {
+ .name = "qcom-prm",
+ .of_match_table = of_match_ptr(prm_device_id),
+ },
+};
+
+module_gpr_driver(prm_driver);
+MODULE_DESCRIPTION("Q6 Proxy Resource Manager");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h
new file mode 100644
index 000000000..cf619ef65
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6prm.h
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __Q6PRM_H__
+#define __Q6PRM_H__
+
+#define LPAIF_MI2S_MCLK 1
+#define LPAIF_MI2S_BCLK 2
+
+/* Clock ID for Primary I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100
+/* Clock ID for Primary I2S EBIT */
+#define Q6PRM_LPASS_CLK_ID_PRI_MI2S_EBIT 0x101
+/* Clock ID for Secondary I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_SEC_MI2S_IBIT 0x102
+/* Clock ID for Secondary I2S EBIT */
+#define Q6PRM_LPASS_CLK_ID_SEC_MI2S_EBIT 0x103
+/* Clock ID for Tertiary I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_TER_MI2S_IBIT 0x104
+/* Clock ID for Tertiary I2S EBIT */
+#define Q6PRM_LPASS_CLK_ID_TER_MI2S_EBIT 0x105
+/* Clock ID for Quartnery I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_QUAD_MI2S_IBIT 0x106
+/* Clock ID for Quartnery I2S EBIT */
+#define Q6PRM_LPASS_CLK_ID_QUAD_MI2S_EBIT 0x107
+/* Clock ID for Speaker I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_SPEAKER_I2S_IBIT 0x108
+/* Clock ID for Speaker I2S EBIT */
+#define Q6PRM_LPASS_CLK_ID_SPEAKER_I2S_EBIT 0x109
+/* Clock ID for Speaker I2S OSR */
+#define Q6PRM_LPASS_CLK_ID_SPEAKER_I2S_OSR 0x10A
+
+/* Clock ID for QUINARY I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_QUI_MI2S_IBIT 0x10B
+/* Clock ID for QUINARY I2S EBIT */
+#define Q6PRM_LPASS_CLK_ID_QUI_MI2S_EBIT 0x10C
+/* Clock ID for SENARY I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_SEN_MI2S_IBIT 0x10D
+/* Clock ID for SENARY I2S EBIT */
+#define Q6PRM_LPASS_CLK_ID_SEN_MI2S_EBIT 0x10E
+/* Clock ID for INT0 I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_INT0_MI2S_IBIT 0x10F
+/* Clock ID for INT1 I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_INT1_MI2S_IBIT 0x110
+/* Clock ID for INT2 I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_INT2_MI2S_IBIT 0x111
+/* Clock ID for INT3 I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_INT3_MI2S_IBIT 0x112
+/* Clock ID for INT4 I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_INT4_MI2S_IBIT 0x113
+/* Clock ID for INT5 I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_INT5_MI2S_IBIT 0x114
+/* Clock ID for INT6 I2S IBIT */
+#define Q6PRM_LPASS_CLK_ID_INT6_MI2S_IBIT 0x115
+
+/* Clock ID for QUINARY MI2S OSR CLK */
+#define Q6PRM_LPASS_CLK_ID_QUI_MI2S_OSR 0x116
+
+/* Clock ID for MCLK1 */
+#define Q6PRM_LPASS_CLK_ID_MCLK_1 0x300
+/* Clock ID for MCLK2 */
+#define Q6PRM_LPASS_CLK_ID_MCLK_2 0x301
+/* Clock ID for MCLK3 */
+#define Q6PRM_LPASS_CLK_ID_MCLK_3 0x302
+/* Clock ID for MCLK4 */
+#define Q6PRM_LPASS_CLK_ID_MCLK_4 0x303
+/* Clock ID for MCLK5 */
+#define Q6PRM_LPASS_CLK_ID_MCLK_5 0x304
+
+#define Q6PRM_LPASS_CLK_ID_WSA_CORE_MCLK 0x305
+#define Q6PRM_LPASS_CLK_ID_WSA_CORE_NPL_MCLK 0x306
+
+#define Q6PRM_LPASS_CLK_ID_VA_CORE_MCLK 0x307
+#define Q6PRM_LPASS_CLK_ID_VA_CORE_2X_MCLK 0x308
+
+#define Q6PRM_LPASS_CLK_ID_TX_CORE_MCLK 0x30c
+#define Q6PRM_LPASS_CLK_ID_TX_CORE_NPL_MCLK 0x30d
+
+#define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK 0x30e
+#define Q6PRM_LPASS_CLK_ID_RX_CORE_NPL_MCLK 0x30f
+
+/* Clock ID for MCLK for WSA2 core */
+#define Q6PRM_LPASS_CLK_ID_WSA2_CORE_MCLK 0x310
+/* Clock ID for NPL MCLK for WSA2 core */
+#define Q6PRM_LPASS_CLK_ID_WSA2_CORE_2X_MCLK 0x311
+/* Clock ID for RX Core TX MCLK */
+#define Q6PRM_LPASS_CLK_ID_RX_CORE_TX_MCLK 0x312
+/* Clock ID for RX CORE TX 2X MCLK */
+#define Q6PRM_LPASS_CLK_ID_RX_CORE_TX_2X_MCLK 0x313
+/* Clock ID for WSA core TX MCLK */
+#define Q6PRM_LPASS_CLK_ID_WSA_CORE_TX_MCLK 0x314
+/* Clock ID for WSA core TX 2X MCLK */
+#define Q6PRM_LPASS_CLK_ID_WSA_CORE_TX_2X_MCLK 0x315
+/* Clock ID for WSA2 core TX MCLK */
+#define Q6PRM_LPASS_CLK_ID_WSA2_CORE_TX_MCLK 0x316
+/* Clock ID for WSA2 core TX 2X MCLK */
+#define Q6PRM_LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK 0x317
+/* Clock ID for RX CORE MCLK2 2X MCLK */
+#define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318
+
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_IBIT 0x500
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_EBIT 0x501
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_IBIT 0x502
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_EBIT 0x503
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_IBIT 0x504
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_EBIT 0x505
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_IBIT 0x506
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_EBIT 0x507
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_IBIT 0x508
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_EBIT 0x509
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_IBIT 0x50A
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_EBIT 0x50B
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_IBIT 0x50C
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_EBIT 0x50D
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_IBIT 0x50E
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_EBIT 0x50F
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_IBIT 0x510
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_EBIT 0x511
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_IBIT 0x512
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_EBIT 0x513
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_IBIT 0x514
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_EBIT 0x515
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_IBIT 0x516
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_EBIT 0x517
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_IBIT 0x518
+#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_EBIT 0x519
+#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_IBIT 0x550
+#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_EBIT 0x551
+
+#define Q6PRM_LPASS_CLK_SRC_INTERNAL 1
+#define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0
+#define Q6PRM_HW_CORE_ID_LPASS 1
+#define Q6PRM_HW_CORE_ID_DCODEC 2
+#define Q6PRM_HW_LPR_VOTE 3
+
+int q6prm_set_lpass_clock(struct device *dev, int clk_id, int clk_attr,
+ int clk_root, unsigned int freq);
+int q6prm_vote_lpass_core_hw(struct device *dev, uint32_t hw_block_id,
+ const char *client_name, uint32_t *client_handle);
+int q6prm_unvote_lpass_core_hw(struct device *dev, uint32_t hw_block_id,
+ uint32_t client_handle);
+#endif /* __Q6PRM_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c
new file mode 100644
index 000000000..d076c99f1
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6routing.c
@@ -0,0 +1,1250 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include <dt-bindings/sound/qcom,q6asm.h>
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/bitops.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/asound.h>
+#include <sound/pcm_params.h>
+#include "q6afe.h"
+#include "q6asm.h"
+#include "q6adm.h"
+#include "q6routing.h"
+
+#define DRV_NAME "q6routing-component"
+
+#define Q6ROUTING_RX_MIXERS(id) \
+ SOC_SINGLE_EXT("MultiMedia1", id, \
+ MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer,\
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("MultiMedia2", id, \
+ MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer,\
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("MultiMedia3", id, \
+ MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer,\
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("MultiMedia4", id, \
+ MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer,\
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("MultiMedia5", id, \
+ MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer,\
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("MultiMedia6", id, \
+ MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer,\
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("MultiMedia7", id, \
+ MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer,\
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("MultiMedia8", id, \
+ MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer,\
+ msm_routing_put_audio_mixer),
+
+#define Q6ROUTING_RX_DAPM_ROUTE(mix_name, s) \
+ { mix_name, "MultiMedia1", "MM_DL1" }, \
+ { mix_name, "MultiMedia2", "MM_DL2" }, \
+ { mix_name, "MultiMedia3", "MM_DL3" }, \
+ { mix_name, "MultiMedia4", "MM_DL4" }, \
+ { mix_name, "MultiMedia5", "MM_DL5" }, \
+ { mix_name, "MultiMedia6", "MM_DL6" }, \
+ { mix_name, "MultiMedia7", "MM_DL7" }, \
+ { mix_name, "MultiMedia8", "MM_DL8" }, \
+ { s, NULL, mix_name }
+
+#define Q6ROUTING_TX_DAPM_ROUTE(mix_name) \
+ { mix_name, "PRI_MI2S_TX", "PRI_MI2S_TX" }, \
+ { mix_name, "SEC_MI2S_TX", "SEC_MI2S_TX" }, \
+ { mix_name, "QUAT_MI2S_TX", "QUAT_MI2S_TX" }, \
+ { mix_name, "QUIN_MI2S_TX", "QUIN_MI2S_TX" }, \
+ { mix_name, "TERT_MI2S_TX", "TERT_MI2S_TX" }, \
+ { mix_name, "SLIMBUS_0_TX", "SLIMBUS_0_TX" }, \
+ { mix_name, "SLIMBUS_1_TX", "SLIMBUS_1_TX" }, \
+ { mix_name, "SLIMBUS_2_TX", "SLIMBUS_2_TX" }, \
+ { mix_name, "SLIMBUS_3_TX", "SLIMBUS_3_TX" }, \
+ { mix_name, "SLIMBUS_4_TX", "SLIMBUS_4_TX" }, \
+ { mix_name, "SLIMBUS_5_TX", "SLIMBUS_5_TX" }, \
+ { mix_name, "SLIMBUS_6_TX", "SLIMBUS_6_TX" }, \
+ { mix_name, "PRIMARY_TDM_TX_0", "PRIMARY_TDM_TX_0"}, \
+ { mix_name, "PRIMARY_TDM_TX_1", "PRIMARY_TDM_TX_1"}, \
+ { mix_name, "PRIMARY_TDM_TX_2", "PRIMARY_TDM_TX_2"}, \
+ { mix_name, "PRIMARY_TDM_TX_3", "PRIMARY_TDM_TX_3"}, \
+ { mix_name, "PRIMARY_TDM_TX_4", "PRIMARY_TDM_TX_4"}, \
+ { mix_name, "PRIMARY_TDM_TX_5", "PRIMARY_TDM_TX_5"}, \
+ { mix_name, "PRIMARY_TDM_TX_6", "PRIMARY_TDM_TX_6"}, \
+ { mix_name, "PRIMARY_TDM_TX_7", "PRIMARY_TDM_TX_7"}, \
+ { mix_name, "SEC_TDM_TX_0", "SEC_TDM_TX_0"}, \
+ { mix_name, "SEC_TDM_TX_1", "SEC_TDM_TX_1"}, \
+ { mix_name, "SEC_TDM_TX_2", "SEC_TDM_TX_2"}, \
+ { mix_name, "SEC_TDM_TX_3", "SEC_TDM_TX_3"}, \
+ { mix_name, "SEC_TDM_TX_4", "SEC_TDM_TX_4"}, \
+ { mix_name, "SEC_TDM_TX_5", "SEC_TDM_TX_5"}, \
+ { mix_name, "SEC_TDM_TX_6", "SEC_TDM_TX_6"}, \
+ { mix_name, "SEC_TDM_TX_7", "SEC_TDM_TX_7"}, \
+ { mix_name, "TERT_TDM_TX_0", "TERT_TDM_TX_0"}, \
+ { mix_name, "TERT_TDM_TX_1", "TERT_TDM_TX_1"}, \
+ { mix_name, "TERT_TDM_TX_2", "TERT_TDM_TX_2"}, \
+ { mix_name, "TERT_TDM_TX_3", "TERT_TDM_TX_3"}, \
+ { mix_name, "TERT_TDM_TX_4", "TERT_TDM_TX_4"}, \
+ { mix_name, "TERT_TDM_TX_5", "TERT_TDM_TX_5"}, \
+ { mix_name, "TERT_TDM_TX_6", "TERT_TDM_TX_6"}, \
+ { mix_name, "TERT_TDM_TX_7", "TERT_TDM_TX_7"}, \
+ { mix_name, "QUAT_TDM_TX_0", "QUAT_TDM_TX_0"}, \
+ { mix_name, "QUAT_TDM_TX_1", "QUAT_TDM_TX_1"}, \
+ { mix_name, "QUAT_TDM_TX_2", "QUAT_TDM_TX_2"}, \
+ { mix_name, "QUAT_TDM_TX_3", "QUAT_TDM_TX_3"}, \
+ { mix_name, "QUAT_TDM_TX_4", "QUAT_TDM_TX_4"}, \
+ { mix_name, "QUAT_TDM_TX_5", "QUAT_TDM_TX_5"}, \
+ { mix_name, "QUAT_TDM_TX_6", "QUAT_TDM_TX_6"}, \
+ { mix_name, "QUAT_TDM_TX_7", "QUAT_TDM_TX_7"}, \
+ { mix_name, "QUIN_TDM_TX_0", "QUIN_TDM_TX_0"}, \
+ { mix_name, "QUIN_TDM_TX_1", "QUIN_TDM_TX_1"}, \
+ { mix_name, "QUIN_TDM_TX_2", "QUIN_TDM_TX_2"}, \
+ { mix_name, "QUIN_TDM_TX_3", "QUIN_TDM_TX_3"}, \
+ { mix_name, "QUIN_TDM_TX_4", "QUIN_TDM_TX_4"}, \
+ { mix_name, "QUIN_TDM_TX_5", "QUIN_TDM_TX_5"}, \
+ { mix_name, "QUIN_TDM_TX_6", "QUIN_TDM_TX_6"}, \
+ { mix_name, "QUIN_TDM_TX_7", "QUIN_TDM_TX_7"}, \
+ { mix_name, "WSA_CODEC_DMA_TX_0", "WSA_CODEC_DMA_TX_0"}, \
+ { mix_name, "WSA_CODEC_DMA_TX_1", "WSA_CODEC_DMA_TX_1"}, \
+ { mix_name, "WSA_CODEC_DMA_TX_2", "WSA_CODEC_DMA_TX_2"}, \
+ { mix_name, "VA_CODEC_DMA_TX_0", "VA_CODEC_DMA_TX_0"}, \
+ { mix_name, "VA_CODEC_DMA_TX_1", "VA_CODEC_DMA_TX_1"}, \
+ { mix_name, "VA_CODEC_DMA_TX_2", "VA_CODEC_DMA_TX_2"}, \
+ { mix_name, "TX_CODEC_DMA_TX_0", "TX_CODEC_DMA_TX_0"}, \
+ { mix_name, "TX_CODEC_DMA_TX_1", "TX_CODEC_DMA_TX_1"}, \
+ { mix_name, "TX_CODEC_DMA_TX_2", "TX_CODEC_DMA_TX_2"}, \
+ { mix_name, "TX_CODEC_DMA_TX_3", "TX_CODEC_DMA_TX_3"}, \
+ { mix_name, "TX_CODEC_DMA_TX_4", "TX_CODEC_DMA_TX_4"}, \
+ { mix_name, "TX_CODEC_DMA_TX_5", "TX_CODEC_DMA_TX_5"}, \
+ { mix_name, "LPI_MI2S_TX_0", "LPI_MI2S_TX_0" }, \
+ { mix_name, "LPI_MI2S_TX_1", "LPI_MI2S_TX_1" }, \
+ { mix_name, "LPI_MI2S_TX_2", "LPI_MI2S_TX_2" }, \
+ { mix_name, "LPI_MI2S_TX_3", "LPI_MI2S_TX_3" }, \
+ { mix_name, "LPI_MI2S_TX_4", "LPI_MI2S_TX_4" }, \
+ { mix_name, "LPI_MI2S_TX_5", "LPI_MI2S_TX_5" }, \
+ { mix_name, "LPI_MI2S_TX_6", "LPI_MI2S_TX_6" }
+
+#define Q6ROUTING_TX_MIXERS(id) \
+ SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SEC_MI2S_TX", SECONDARY_MI2S_TX, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TERT_MI2S_TX", TERTIARY_MI2S_TX, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUAT_MI2S_TX", QUATERNARY_MI2S_TX, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUIN_MI2S_TX", QUINARY_MI2S_TX, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SLIMBUS_0_TX", SLIMBUS_0_TX, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SLIMBUS_1_TX", SLIMBUS_1_TX, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SLIMBUS_2_TX", SLIMBUS_2_TX, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SLIMBUS_3_TX", SLIMBUS_3_TX, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SLIMBUS_4_TX", SLIMBUS_4_TX, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SLIMBUS_5_TX", SLIMBUS_5_TX, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SLIMBUS_6_TX", SLIMBUS_6_TX, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("PRIMARY_TDM_TX_0", PRIMARY_TDM_TX_0, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("PRIMARY_TDM_TX_1", PRIMARY_TDM_TX_1, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("PRIMARY_TDM_TX_2", PRIMARY_TDM_TX_2, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("PRIMARY_TDM_TX_3", PRIMARY_TDM_TX_3, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("PRIMARY_TDM_TX_4", PRIMARY_TDM_TX_4, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("PRIMARY_TDM_TX_5", PRIMARY_TDM_TX_5, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("PRIMARY_TDM_TX_6", PRIMARY_TDM_TX_6, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("PRIMARY_TDM_TX_7", PRIMARY_TDM_TX_7, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SEC_TDM_TX_0", SECONDARY_TDM_TX_0, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SEC_TDM_TX_1", SECONDARY_TDM_TX_1, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SEC_TDM_TX_2", SECONDARY_TDM_TX_2, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SEC_TDM_TX_3", SECONDARY_TDM_TX_3, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SEC_TDM_TX_4", SECONDARY_TDM_TX_4, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SEC_TDM_TX_5", SECONDARY_TDM_TX_5, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SEC_TDM_TX_6", SECONDARY_TDM_TX_6, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("SEC_TDM_TX_7", SECONDARY_TDM_TX_7, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TERT_TDM_TX_0", TERTIARY_TDM_TX_0, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TERT_TDM_TX_1", TERTIARY_TDM_TX_1, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TERT_TDM_TX_2", TERTIARY_TDM_TX_2, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TERT_TDM_TX_3", TERTIARY_TDM_TX_3, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TERT_TDM_TX_4", TERTIARY_TDM_TX_4, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TERT_TDM_TX_5", TERTIARY_TDM_TX_5, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TERT_TDM_TX_6", TERTIARY_TDM_TX_6, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TERT_TDM_TX_7", TERTIARY_TDM_TX_7, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUAT_TDM_TX_0", QUATERNARY_TDM_TX_0, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUAT_TDM_TX_1", QUATERNARY_TDM_TX_1, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUAT_TDM_TX_2", QUATERNARY_TDM_TX_2, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUAT_TDM_TX_3", QUATERNARY_TDM_TX_3, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUAT_TDM_TX_4", QUATERNARY_TDM_TX_4, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUAT_TDM_TX_5", QUATERNARY_TDM_TX_5, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUAT_TDM_TX_6", QUATERNARY_TDM_TX_6, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUAT_TDM_TX_7", QUATERNARY_TDM_TX_7, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUIN_TDM_TX_0", QUINARY_TDM_TX_0, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUIN_TDM_TX_1", QUINARY_TDM_TX_1, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUIN_TDM_TX_2", QUINARY_TDM_TX_2, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUIN_TDM_TX_3", QUINARY_TDM_TX_3, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUIN_TDM_TX_4", QUINARY_TDM_TX_4, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUIN_TDM_TX_5", QUINARY_TDM_TX_5, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUIN_TDM_TX_6", QUINARY_TDM_TX_6, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("QUIN_TDM_TX_7", QUINARY_TDM_TX_7, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("WSA_CODEC_DMA_TX_0", WSA_CODEC_DMA_TX_0, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("WSA_CODEC_DMA_TX_1", WSA_CODEC_DMA_TX_1, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("WSA_CODEC_DMA_TX_2", WSA_CODEC_DMA_TX_2, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("VA_CODEC_DMA_TX_0", VA_CODEC_DMA_TX_0, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("VA_CODEC_DMA_TX_1", VA_CODEC_DMA_TX_1, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("VA_CODEC_DMA_TX_2", VA_CODEC_DMA_TX_2, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TX_CODEC_DMA_TX_0", TX_CODEC_DMA_TX_0, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TX_CODEC_DMA_TX_1", TX_CODEC_DMA_TX_1, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TX_CODEC_DMA_TX_2", TX_CODEC_DMA_TX_2, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TX_CODEC_DMA_TX_3", TX_CODEC_DMA_TX_3, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TX_CODEC_DMA_TX_4", TX_CODEC_DMA_TX_4, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("TX_CODEC_DMA_TX_5", TX_CODEC_DMA_TX_5, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("LPI_MI2S_TX_0", LPI_MI2S_TX_0, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("LPI_MI2S_TX_1", LPI_MI2S_TX_1, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("LPI_MI2S_TX_2", LPI_MI2S_TX_2, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("LPI_MI2S_TX_3", LPI_MI2S_TX_3, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("LPI_MI2S_TX_4", LPI_MI2S_TX_4, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("LPI_MI2S_TX_5", LPI_MI2S_TX_5, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer), \
+ SOC_SINGLE_EXT("LPI_MI2S_TX_6", LPI_MI2S_TX_6, \
+ id, 1, 0, msm_routing_get_audio_mixer, \
+ msm_routing_put_audio_mixer),
+
+struct session_data {
+ int state;
+ int port_id;
+ int path_type;
+ int app_type;
+ int acdb_id;
+ int sample_rate;
+ int bits_per_sample;
+ int channels;
+ int perf_mode;
+ int numcopps;
+ int fedai_id;
+ unsigned long copp_map;
+ struct q6copp *copps[MAX_COPPS_PER_PORT];
+};
+
+struct msm_routing_data {
+ struct session_data sessions[MAX_SESSIONS];
+ struct session_data port_data[AFE_MAX_PORTS];
+ struct device *dev;
+ struct mutex lock;
+};
+
+static struct msm_routing_data *routing_data;
+
+/**
+ * q6routing_stream_open() - Register a new stream for route setup
+ *
+ * @fedai_id: Frontend dai id.
+ * @perf_mode: Performance mode.
+ * @stream_id: ASM stream id to map.
+ * @stream_type: Direction of stream
+ *
+ * Return: Will be an negative on error or a zero on success.
+ */
+int q6routing_stream_open(int fedai_id, int perf_mode,
+ int stream_id, int stream_type)
+{
+ int j, topology, num_copps = 0;
+ struct route_payload payload;
+ struct q6copp *copp;
+ int copp_idx;
+ struct session_data *session, *pdata;
+
+ if (!routing_data) {
+ pr_err("Routing driver not yet ready\n");
+ return -EINVAL;
+ }
+
+ session = &routing_data->sessions[stream_id - 1];
+ if (session->port_id < 0) {
+ dev_err(routing_data->dev, "Routing not setup for MultiMedia%d Session\n",
+ session->fedai_id);
+ return -EINVAL;
+ }
+
+ pdata = &routing_data->port_data[session->port_id];
+
+ mutex_lock(&routing_data->lock);
+ session->fedai_id = fedai_id;
+
+ session->path_type = pdata->path_type;
+ session->sample_rate = pdata->sample_rate;
+ session->channels = pdata->channels;
+ session->bits_per_sample = pdata->bits_per_sample;
+
+ payload.num_copps = 0; /* only RX needs to use payload */
+ topology = NULL_COPP_TOPOLOGY;
+ copp = q6adm_open(routing_data->dev, session->port_id,
+ session->path_type, session->sample_rate,
+ session->channels, topology, perf_mode,
+ session->bits_per_sample, 0, 0);
+
+ if (IS_ERR_OR_NULL(copp)) {
+ mutex_unlock(&routing_data->lock);
+ return -EINVAL;
+ }
+
+ copp_idx = q6adm_get_copp_id(copp);
+ set_bit(copp_idx, &session->copp_map);
+ session->copps[copp_idx] = copp;
+
+ for_each_set_bit(j, &session->copp_map, MAX_COPPS_PER_PORT) {
+ payload.port_id[num_copps] = session->port_id;
+ payload.copp_idx[num_copps] = j;
+ num_copps++;
+ }
+
+ if (num_copps) {
+ payload.num_copps = num_copps;
+ payload.session_id = stream_id;
+ q6adm_matrix_map(routing_data->dev, session->path_type,
+ payload, perf_mode);
+ }
+ mutex_unlock(&routing_data->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(q6routing_stream_open);
+
+static struct session_data *get_session_from_id(struct msm_routing_data *data,
+ int fedai_id)
+{
+ int i;
+
+ for (i = 0; i < MAX_SESSIONS; i++) {
+ if (fedai_id == data->sessions[i].fedai_id)
+ return &data->sessions[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * q6routing_stream_close() - Deregister a stream
+ *
+ * @fedai_id: Frontend dai id.
+ * @stream_type: Direction of stream
+ *
+ * Return: Will be an negative on error or a zero on success.
+ */
+void q6routing_stream_close(int fedai_id, int stream_type)
+{
+ struct session_data *session;
+ int idx;
+
+ session = get_session_from_id(routing_data, fedai_id);
+ if (!session)
+ return;
+
+ for_each_set_bit(idx, &session->copp_map, MAX_COPPS_PER_PORT) {
+ if (session->copps[idx]) {
+ q6adm_close(routing_data->dev, session->copps[idx]);
+ session->copps[idx] = NULL;
+ }
+ }
+
+ session->fedai_id = -1;
+ session->copp_map = 0;
+}
+EXPORT_SYMBOL_GPL(q6routing_stream_close);
+
+static int msm_routing_get_audio_mixer(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int session_id = mc->shift;
+ struct snd_soc_component *c = snd_soc_dapm_to_component(dapm);
+ struct msm_routing_data *priv = dev_get_drvdata(c->dev);
+ struct session_data *session = &priv->sessions[session_id];
+
+ if (session->port_id == mc->reg)
+ ucontrol->value.integer.value[0] = 1;
+ else
+ ucontrol->value.integer.value[0] = 0;
+
+ return 0;
+}
+
+static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
+ struct snd_soc_component *c = snd_soc_dapm_to_component(dapm);
+ struct msm_routing_data *data = dev_get_drvdata(c->dev);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_dapm_update *update = NULL;
+ int be_id = mc->reg;
+ int session_id = mc->shift;
+ struct session_data *session = &data->sessions[session_id];
+
+ if (ucontrol->value.integer.value[0]) {
+ if (session->port_id == be_id)
+ return 0;
+
+ session->port_id = be_id;
+ snd_soc_dapm_mixer_update_power(dapm, kcontrol, 1, update);
+ } else {
+ if (session->port_id == -1 || session->port_id != be_id)
+ return 0;
+
+ session->port_id = -1;
+ snd_soc_dapm_mixer_update_power(dapm, kcontrol, 0, update);
+ }
+
+ return 1;
+}
+
+static const struct snd_kcontrol_new usb_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(USB_RX) };
+
+static const struct snd_kcontrol_new hdmi_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(HDMI_RX) };
+
+static const struct snd_kcontrol_new display_port_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(DISPLAY_PORT_RX) };
+
+static const struct snd_kcontrol_new primary_mi2s_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(PRIMARY_MI2S_RX) };
+
+static const struct snd_kcontrol_new secondary_mi2s_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SECONDARY_MI2S_RX) };
+
+static const struct snd_kcontrol_new quaternary_mi2s_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUATERNARY_MI2S_RX) };
+
+static const struct snd_kcontrol_new quinary_mi2s_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUINARY_MI2S_RX) };
+
+static const struct snd_kcontrol_new tertiary_mi2s_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(TERTIARY_MI2S_RX) };
+
+static const struct snd_kcontrol_new slimbus_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SLIMBUS_0_RX) };
+
+static const struct snd_kcontrol_new slimbus_1_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SLIMBUS_1_RX) };
+
+static const struct snd_kcontrol_new slimbus_2_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SLIMBUS_2_RX) };
+
+static const struct snd_kcontrol_new slimbus_3_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SLIMBUS_3_RX) };
+
+static const struct snd_kcontrol_new slimbus_4_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SLIMBUS_4_RX) };
+
+static const struct snd_kcontrol_new slimbus_5_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SLIMBUS_5_RX) };
+
+static const struct snd_kcontrol_new slimbus_6_rx_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SLIMBUS_6_RX) };
+
+static const struct snd_kcontrol_new pri_tdm_rx_0_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_0) };
+
+static const struct snd_kcontrol_new pri_tdm_rx_1_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_1) };
+
+static const struct snd_kcontrol_new pri_tdm_rx_2_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_2) };
+
+static const struct snd_kcontrol_new pri_tdm_rx_3_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_3) };
+
+static const struct snd_kcontrol_new pri_tdm_rx_4_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_4) };
+
+static const struct snd_kcontrol_new pri_tdm_rx_5_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_5) };
+
+static const struct snd_kcontrol_new pri_tdm_rx_6_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_6) };
+
+static const struct snd_kcontrol_new pri_tdm_rx_7_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_7) };
+
+static const struct snd_kcontrol_new sec_tdm_rx_0_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_0) };
+
+static const struct snd_kcontrol_new sec_tdm_rx_1_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_1) };
+
+static const struct snd_kcontrol_new sec_tdm_rx_2_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_2) };
+
+static const struct snd_kcontrol_new sec_tdm_rx_3_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_3) };
+
+static const struct snd_kcontrol_new sec_tdm_rx_4_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_4) };
+
+static const struct snd_kcontrol_new sec_tdm_rx_5_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_5) };
+
+static const struct snd_kcontrol_new sec_tdm_rx_6_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_6) };
+
+static const struct snd_kcontrol_new sec_tdm_rx_7_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_7) };
+
+static const struct snd_kcontrol_new tert_tdm_rx_0_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_0) };
+
+static const struct snd_kcontrol_new tert_tdm_rx_1_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_1) };
+
+static const struct snd_kcontrol_new tert_tdm_rx_2_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_2) };
+
+static const struct snd_kcontrol_new tert_tdm_rx_3_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_3) };
+
+static const struct snd_kcontrol_new tert_tdm_rx_4_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_4) };
+
+static const struct snd_kcontrol_new tert_tdm_rx_5_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_5) };
+
+static const struct snd_kcontrol_new tert_tdm_rx_6_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_6) };
+
+static const struct snd_kcontrol_new tert_tdm_rx_7_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_7) };
+
+static const struct snd_kcontrol_new quat_tdm_rx_0_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_0) };
+
+static const struct snd_kcontrol_new quat_tdm_rx_1_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_1) };
+
+static const struct snd_kcontrol_new quat_tdm_rx_2_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_2) };
+
+static const struct snd_kcontrol_new quat_tdm_rx_3_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_3) };
+
+static const struct snd_kcontrol_new quat_tdm_rx_4_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_4) };
+
+static const struct snd_kcontrol_new quat_tdm_rx_5_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_5) };
+
+static const struct snd_kcontrol_new quat_tdm_rx_6_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_6) };
+
+static const struct snd_kcontrol_new quat_tdm_rx_7_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_7) };
+
+static const struct snd_kcontrol_new quin_tdm_rx_0_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_0) };
+
+static const struct snd_kcontrol_new quin_tdm_rx_1_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_1) };
+
+static const struct snd_kcontrol_new quin_tdm_rx_2_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_2) };
+
+static const struct snd_kcontrol_new quin_tdm_rx_3_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_3) };
+
+static const struct snd_kcontrol_new quin_tdm_rx_4_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_4) };
+
+static const struct snd_kcontrol_new quin_tdm_rx_5_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_5) };
+
+static const struct snd_kcontrol_new quin_tdm_rx_6_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_6) };
+
+static const struct snd_kcontrol_new quin_tdm_rx_7_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_7) };
+
+static const struct snd_kcontrol_new wsa_codec_dma_rx_0_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(WSA_CODEC_DMA_RX_0) };
+
+static const struct snd_kcontrol_new wsa_codec_dma_rx_1_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(WSA_CODEC_DMA_RX_1) };
+
+static const struct snd_kcontrol_new rx_codec_dma_rx_0_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_0) };
+
+static const struct snd_kcontrol_new rx_codec_dma_rx_1_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_1) };
+
+static const struct snd_kcontrol_new rx_codec_dma_rx_2_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_2) };
+
+static const struct snd_kcontrol_new rx_codec_dma_rx_3_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_3) };
+
+static const struct snd_kcontrol_new rx_codec_dma_rx_4_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_4) };
+
+static const struct snd_kcontrol_new rx_codec_dma_rx_5_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_5) };
+
+static const struct snd_kcontrol_new rxcodec_dma_rx_6_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_6) };
+
+static const struct snd_kcontrol_new rx_codec_dma_rx_7_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_7) };
+
+static const struct snd_kcontrol_new lpi_mi2s_rx_0_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_0) };
+
+static const struct snd_kcontrol_new lpi_mi2s_rx_1_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_1) };
+
+static const struct snd_kcontrol_new lpi_mi2s_rx_2_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_2) };
+
+static const struct snd_kcontrol_new lpi_mi2s_rx_3_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_3) };
+
+static const struct snd_kcontrol_new lpi_mi2s_rx_4_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_4) };
+
+static const struct snd_kcontrol_new lpi_mi2s_rx_5_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_5) };
+
+static const struct snd_kcontrol_new lpi_mi2s_rx_6_mixer_controls[] = {
+ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_6) };
+
+static const struct snd_kcontrol_new mmul1_mixer_controls[] = {
+ Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA1) };
+
+static const struct snd_kcontrol_new mmul2_mixer_controls[] = {
+ Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA2) };
+
+static const struct snd_kcontrol_new mmul3_mixer_controls[] = {
+ Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA3) };
+
+static const struct snd_kcontrol_new mmul4_mixer_controls[] = {
+ Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA4) };
+
+static const struct snd_kcontrol_new mmul5_mixer_controls[] = {
+ Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA5) };
+
+static const struct snd_kcontrol_new mmul6_mixer_controls[] = {
+ Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA6) };
+
+static const struct snd_kcontrol_new mmul7_mixer_controls[] = {
+ Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA7) };
+
+static const struct snd_kcontrol_new mmul8_mixer_controls[] = {
+ Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA8) };
+
+static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = {
+ /* Mixer definitions */
+ SND_SOC_DAPM_MIXER("HDMI Mixer", SND_SOC_NOPM, 0, 0,
+ hdmi_mixer_controls,
+ ARRAY_SIZE(hdmi_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("DISPLAY_PORT_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ display_port_mixer_controls,
+ ARRAY_SIZE(display_port_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("SLIMBUS_0_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ slimbus_rx_mixer_controls,
+ ARRAY_SIZE(slimbus_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ slimbus_1_rx_mixer_controls,
+ ARRAY_SIZE(slimbus_1_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SLIMBUS_2_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ slimbus_2_rx_mixer_controls,
+ ARRAY_SIZE(slimbus_2_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SLIMBUS_3_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ slimbus_3_rx_mixer_controls,
+ ARRAY_SIZE(slimbus_3_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SLIMBUS_4_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ slimbus_4_rx_mixer_controls,
+ ARRAY_SIZE(slimbus_4_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SLIMBUS_5_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ slimbus_5_rx_mixer_controls,
+ ARRAY_SIZE(slimbus_5_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SLIMBUS_6_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ slimbus_6_rx_mixer_controls,
+ ARRAY_SIZE(slimbus_6_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("PRI_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ primary_mi2s_rx_mixer_controls,
+ ARRAY_SIZE(primary_mi2s_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SEC_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ secondary_mi2s_rx_mixer_controls,
+ ARRAY_SIZE(secondary_mi2s_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUAT_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quaternary_mi2s_rx_mixer_controls,
+ ARRAY_SIZE(quaternary_mi2s_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUIN_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quinary_mi2s_rx_mixer_controls,
+ ARRAY_SIZE(quinary_mi2s_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("TERT_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ tertiary_mi2s_rx_mixer_controls,
+ ARRAY_SIZE(tertiary_mi2s_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ pri_tdm_rx_0_mixer_controls,
+ ARRAY_SIZE(pri_tdm_rx_0_mixer_controls)),
+ SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ pri_tdm_rx_1_mixer_controls,
+ ARRAY_SIZE(pri_tdm_rx_1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ pri_tdm_rx_2_mixer_controls,
+ ARRAY_SIZE(pri_tdm_rx_2_mixer_controls)),
+ SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ pri_tdm_rx_3_mixer_controls,
+ ARRAY_SIZE(pri_tdm_rx_3_mixer_controls)),
+ SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ pri_tdm_rx_4_mixer_controls,
+ ARRAY_SIZE(pri_tdm_rx_4_mixer_controls)),
+ SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ pri_tdm_rx_5_mixer_controls,
+ ARRAY_SIZE(pri_tdm_rx_5_mixer_controls)),
+ SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ pri_tdm_rx_6_mixer_controls,
+ ARRAY_SIZE(pri_tdm_rx_6_mixer_controls)),
+ SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ pri_tdm_rx_7_mixer_controls,
+ ARRAY_SIZE(pri_tdm_rx_7_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("SEC_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ sec_tdm_rx_0_mixer_controls,
+ ARRAY_SIZE(sec_tdm_rx_0_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SEC_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ sec_tdm_rx_1_mixer_controls,
+ ARRAY_SIZE(sec_tdm_rx_1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SEC_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ sec_tdm_rx_2_mixer_controls,
+ ARRAY_SIZE(sec_tdm_rx_2_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SEC_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ sec_tdm_rx_3_mixer_controls,
+ ARRAY_SIZE(sec_tdm_rx_3_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SEC_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ sec_tdm_rx_4_mixer_controls,
+ ARRAY_SIZE(sec_tdm_rx_4_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SEC_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ sec_tdm_rx_5_mixer_controls,
+ ARRAY_SIZE(sec_tdm_rx_5_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SEC_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ sec_tdm_rx_6_mixer_controls,
+ ARRAY_SIZE(sec_tdm_rx_6_mixer_controls)),
+ SND_SOC_DAPM_MIXER("SEC_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ sec_tdm_rx_7_mixer_controls,
+ ARRAY_SIZE(sec_tdm_rx_7_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("TERT_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ tert_tdm_rx_0_mixer_controls,
+ ARRAY_SIZE(tert_tdm_rx_0_mixer_controls)),
+ SND_SOC_DAPM_MIXER("TERT_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ tert_tdm_rx_1_mixer_controls,
+ ARRAY_SIZE(tert_tdm_rx_1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("TERT_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ tert_tdm_rx_2_mixer_controls,
+ ARRAY_SIZE(tert_tdm_rx_2_mixer_controls)),
+ SND_SOC_DAPM_MIXER("TERT_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ tert_tdm_rx_3_mixer_controls,
+ ARRAY_SIZE(tert_tdm_rx_3_mixer_controls)),
+ SND_SOC_DAPM_MIXER("TERT_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ tert_tdm_rx_4_mixer_controls,
+ ARRAY_SIZE(tert_tdm_rx_4_mixer_controls)),
+ SND_SOC_DAPM_MIXER("TERT_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ tert_tdm_rx_5_mixer_controls,
+ ARRAY_SIZE(tert_tdm_rx_5_mixer_controls)),
+ SND_SOC_DAPM_MIXER("TERT_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ tert_tdm_rx_6_mixer_controls,
+ ARRAY_SIZE(tert_tdm_rx_6_mixer_controls)),
+ SND_SOC_DAPM_MIXER("TERT_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ tert_tdm_rx_7_mixer_controls,
+ ARRAY_SIZE(tert_tdm_rx_7_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("QUAT_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quat_tdm_rx_0_mixer_controls,
+ ARRAY_SIZE(quat_tdm_rx_0_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUAT_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quat_tdm_rx_1_mixer_controls,
+ ARRAY_SIZE(quat_tdm_rx_1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUAT_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quat_tdm_rx_2_mixer_controls,
+ ARRAY_SIZE(quat_tdm_rx_2_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUAT_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quat_tdm_rx_3_mixer_controls,
+ ARRAY_SIZE(quat_tdm_rx_3_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUAT_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quat_tdm_rx_4_mixer_controls,
+ ARRAY_SIZE(quat_tdm_rx_4_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUAT_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quat_tdm_rx_5_mixer_controls,
+ ARRAY_SIZE(quat_tdm_rx_5_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUAT_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quat_tdm_rx_6_mixer_controls,
+ ARRAY_SIZE(quat_tdm_rx_6_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUAT_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quat_tdm_rx_7_mixer_controls,
+ ARRAY_SIZE(quat_tdm_rx_7_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("QUIN_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quin_tdm_rx_0_mixer_controls,
+ ARRAY_SIZE(quin_tdm_rx_0_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUIN_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quin_tdm_rx_1_mixer_controls,
+ ARRAY_SIZE(quin_tdm_rx_1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUIN_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quin_tdm_rx_2_mixer_controls,
+ ARRAY_SIZE(quin_tdm_rx_2_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUIN_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quin_tdm_rx_3_mixer_controls,
+ ARRAY_SIZE(quin_tdm_rx_3_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUIN_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quin_tdm_rx_4_mixer_controls,
+ ARRAY_SIZE(quin_tdm_rx_4_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUIN_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quin_tdm_rx_5_mixer_controls,
+ ARRAY_SIZE(quin_tdm_rx_5_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUIN_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quin_tdm_rx_6_mixer_controls,
+ ARRAY_SIZE(quin_tdm_rx_6_mixer_controls)),
+ SND_SOC_DAPM_MIXER("QUIN_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ quin_tdm_rx_7_mixer_controls,
+ ARRAY_SIZE(quin_tdm_rx_7_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("WSA_CODEC_DMA_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ wsa_codec_dma_rx_0_mixer_controls,
+ ARRAY_SIZE(wsa_codec_dma_rx_0_mixer_controls)),
+ SND_SOC_DAPM_MIXER("WSA_CODEC_DMA_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ wsa_codec_dma_rx_1_mixer_controls,
+ ARRAY_SIZE(wsa_codec_dma_rx_1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ rx_codec_dma_rx_0_mixer_controls,
+ ARRAY_SIZE(rx_codec_dma_rx_0_mixer_controls)),
+ SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ rx_codec_dma_rx_1_mixer_controls,
+ ARRAY_SIZE(rx_codec_dma_rx_1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ rx_codec_dma_rx_2_mixer_controls,
+ ARRAY_SIZE(rx_codec_dma_rx_2_mixer_controls)),
+ SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ rx_codec_dma_rx_3_mixer_controls,
+ ARRAY_SIZE(rx_codec_dma_rx_3_mixer_controls)),
+ SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ rx_codec_dma_rx_4_mixer_controls,
+ ARRAY_SIZE(rx_codec_dma_rx_4_mixer_controls)),
+ SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ rx_codec_dma_rx_5_mixer_controls,
+ ARRAY_SIZE(rx_codec_dma_rx_5_mixer_controls)),
+ SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ rxcodec_dma_rx_6_mixer_controls,
+ ARRAY_SIZE(rxcodec_dma_rx_6_mixer_controls)),
+ SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ rx_codec_dma_rx_7_mixer_controls,
+ ARRAY_SIZE(rx_codec_dma_rx_7_mixer_controls)),
+ SND_SOC_DAPM_MIXER("USB_RX Audio Mixer", SND_SOC_NOPM, 0, 0,
+ usb_rx_mixer_controls,
+ ARRAY_SIZE(usb_rx_mixer_controls)),
+ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ lpi_mi2s_rx_0_mixer_controls,
+ ARRAY_SIZE(lpi_mi2s_rx_0_mixer_controls)),
+ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ lpi_mi2s_rx_1_mixer_controls,
+ ARRAY_SIZE(lpi_mi2s_rx_1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ lpi_mi2s_rx_2_mixer_controls,
+ ARRAY_SIZE(lpi_mi2s_rx_2_mixer_controls)),
+ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ lpi_mi2s_rx_3_mixer_controls,
+ ARRAY_SIZE(lpi_mi2s_rx_3_mixer_controls)),
+ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ lpi_mi2s_rx_4_mixer_controls,
+ ARRAY_SIZE(lpi_mi2s_rx_4_mixer_controls)),
+ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ lpi_mi2s_rx_5_mixer_controls,
+ ARRAY_SIZE(lpi_mi2s_rx_5_mixer_controls)),
+ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0,
+ lpi_mi2s_rx_6_mixer_controls,
+ ARRAY_SIZE(lpi_mi2s_rx_6_mixer_controls)),
+ SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0,
+ mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0,
+ mmul2_mixer_controls, ARRAY_SIZE(mmul2_mixer_controls)),
+ SND_SOC_DAPM_MIXER("MultiMedia3 Mixer", SND_SOC_NOPM, 0, 0,
+ mmul3_mixer_controls, ARRAY_SIZE(mmul3_mixer_controls)),
+ SND_SOC_DAPM_MIXER("MultiMedia4 Mixer", SND_SOC_NOPM, 0, 0,
+ mmul4_mixer_controls, ARRAY_SIZE(mmul4_mixer_controls)),
+ SND_SOC_DAPM_MIXER("MultiMedia5 Mixer", SND_SOC_NOPM, 0, 0,
+ mmul5_mixer_controls, ARRAY_SIZE(mmul5_mixer_controls)),
+ SND_SOC_DAPM_MIXER("MultiMedia6 Mixer", SND_SOC_NOPM, 0, 0,
+ mmul6_mixer_controls, ARRAY_SIZE(mmul6_mixer_controls)),
+ SND_SOC_DAPM_MIXER("MultiMedia7 Mixer", SND_SOC_NOPM, 0, 0,
+ mmul7_mixer_controls, ARRAY_SIZE(mmul7_mixer_controls)),
+ SND_SOC_DAPM_MIXER("MultiMedia8 Mixer", SND_SOC_NOPM, 0, 0,
+ mmul8_mixer_controls, ARRAY_SIZE(mmul8_mixer_controls)),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ Q6ROUTING_RX_DAPM_ROUTE("HDMI Mixer", "HDMI_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("DISPLAY_PORT_RX Audio Mixer",
+ "DISPLAY_PORT_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_0_RX Audio Mixer", "SLIMBUS_0_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_1_RX Audio Mixer", "SLIMBUS_1_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_2_RX Audio Mixer", "SLIMBUS_2_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_3_RX Audio Mixer", "SLIMBUS_3_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_4_RX Audio Mixer", "SLIMBUS_4_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_5_RX Audio Mixer", "SLIMBUS_5_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_6_RX Audio Mixer", "SLIMBUS_6_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUAT_MI2S_RX Audio Mixer", "QUAT_MI2S_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUIN_MI2S_RX Audio Mixer", "QUIN_MI2S_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("TERT_MI2S_RX Audio Mixer", "TERT_MI2S_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("SEC_MI2S_RX Audio Mixer", "SEC_MI2S_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("PRI_MI2S_RX Audio Mixer", "PRI_MI2S_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_0 Audio Mixer",
+ "PRIMARY_TDM_RX_0"),
+ Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_1 Audio Mixer",
+ "PRIMARY_TDM_RX_1"),
+ Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_2 Audio Mixer",
+ "PRIMARY_TDM_RX_2"),
+ Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_3 Audio Mixer",
+ "PRIMARY_TDM_RX_3"),
+ Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_4 Audio Mixer",
+ "PRIMARY_TDM_RX_4"),
+ Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_5 Audio Mixer",
+ "PRIMARY_TDM_RX_5"),
+ Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_6 Audio Mixer",
+ "PRIMARY_TDM_RX_6"),
+ Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_7 Audio Mixer",
+ "PRIMARY_TDM_RX_7"),
+ Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_0 Audio Mixer", "SEC_TDM_RX_0"),
+ Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_1 Audio Mixer", "SEC_TDM_RX_1"),
+ Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_2 Audio Mixer", "SEC_TDM_RX_2"),
+ Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_3 Audio Mixer", "SEC_TDM_RX_3"),
+ Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_4 Audio Mixer", "SEC_TDM_RX_4"),
+ Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_5 Audio Mixer", "SEC_TDM_RX_5"),
+ Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_6 Audio Mixer", "SEC_TDM_RX_6"),
+ Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_7 Audio Mixer", "SEC_TDM_RX_7"),
+ Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_0 Audio Mixer", "TERT_TDM_RX_0"),
+ Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_1 Audio Mixer", "TERT_TDM_RX_1"),
+ Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_2 Audio Mixer", "TERT_TDM_RX_2"),
+ Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_3 Audio Mixer", "TERT_TDM_RX_3"),
+ Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_4 Audio Mixer", "TERT_TDM_RX_4"),
+ Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_5 Audio Mixer", "TERT_TDM_RX_5"),
+ Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_6 Audio Mixer", "TERT_TDM_RX_6"),
+ Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_7 Audio Mixer", "TERT_TDM_RX_7"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_0 Audio Mixer", "QUAT_TDM_RX_0"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_1 Audio Mixer", "QUAT_TDM_RX_1"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_2 Audio Mixer", "QUAT_TDM_RX_2"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_3 Audio Mixer", "QUAT_TDM_RX_3"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_4 Audio Mixer", "QUAT_TDM_RX_4"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_5 Audio Mixer", "QUAT_TDM_RX_5"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_6 Audio Mixer", "QUAT_TDM_RX_6"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_7 Audio Mixer", "QUAT_TDM_RX_7"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_0 Audio Mixer", "QUIN_TDM_RX_0"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_1 Audio Mixer", "QUIN_TDM_RX_1"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_2 Audio Mixer", "QUIN_TDM_RX_2"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_3 Audio Mixer", "QUIN_TDM_RX_3"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_4 Audio Mixer", "QUIN_TDM_RX_4"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_5 Audio Mixer", "QUIN_TDM_RX_5"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_6 Audio Mixer", "QUIN_TDM_RX_6"),
+ Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_7 Audio Mixer", "QUIN_TDM_RX_7"),
+ Q6ROUTING_RX_DAPM_ROUTE("WSA_CODEC_DMA_RX_0 Audio Mixer", "WSA_CODEC_DMA_RX_0"),
+ Q6ROUTING_RX_DAPM_ROUTE("WSA_CODEC_DMA_RX_1 Audio Mixer", "WSA_CODEC_DMA_RX_1"),
+ Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_0 Audio Mixer", "RX_CODEC_DMA_RX_0"),
+ Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_1 Audio Mixer", "RX_CODEC_DMA_RX_1"),
+ Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_2 Audio Mixer", "RX_CODEC_DMA_RX_2"),
+ Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_3 Audio Mixer", "RX_CODEC_DMA_RX_3"),
+ Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_4 Audio Mixer", "RX_CODEC_DMA_RX_4"),
+ Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_5 Audio Mixer", "RX_CODEC_DMA_RX_5"),
+ Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_6 Audio Mixer", "RX_CODEC_DMA_RX_6"),
+ Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_7 Audio Mixer", "RX_CODEC_DMA_RX_7"),
+ Q6ROUTING_RX_DAPM_ROUTE("USB_RX Audio Mixer", "USB_RX"),
+ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_0 Audio Mixer", "LPI_MI2S_RX_0"),
+ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_1 Audio Mixer", "LPI_MI2S_RX_1"),
+ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_2 Audio Mixer", "LPI_MI2S_RX_2"),
+ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_3 Audio Mixer", "LPI_MI2S_RX_3"),
+ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_4 Audio Mixer", "LPI_MI2S_RX_4"),
+ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_5 Audio Mixer", "LPI_MI2S_RX_5"),
+ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_6 Audio Mixer", "LPI_MI2S_RX_6"),
+ Q6ROUTING_TX_DAPM_ROUTE("MultiMedia1 Mixer"),
+ Q6ROUTING_TX_DAPM_ROUTE("MultiMedia2 Mixer"),
+ Q6ROUTING_TX_DAPM_ROUTE("MultiMedia3 Mixer"),
+ Q6ROUTING_TX_DAPM_ROUTE("MultiMedia4 Mixer"),
+ Q6ROUTING_TX_DAPM_ROUTE("MultiMedia5 Mixer"),
+ Q6ROUTING_TX_DAPM_ROUTE("MultiMedia6 Mixer"),
+ Q6ROUTING_TX_DAPM_ROUTE("MultiMedia7 Mixer"),
+ Q6ROUTING_TX_DAPM_ROUTE("MultiMedia8 Mixer"),
+
+ {"MM_UL1", NULL, "MultiMedia1 Mixer"},
+ {"MM_UL2", NULL, "MultiMedia2 Mixer"},
+ {"MM_UL3", NULL, "MultiMedia3 Mixer"},
+ {"MM_UL4", NULL, "MultiMedia4 Mixer"},
+ {"MM_UL5", NULL, "MultiMedia5 Mixer"},
+ {"MM_UL6", NULL, "MultiMedia6 Mixer"},
+ {"MM_UL7", NULL, "MultiMedia7 Mixer"},
+ {"MM_UL8", NULL, "MultiMedia8 Mixer"},
+};
+
+static int routing_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct msm_routing_data *data = dev_get_drvdata(component->dev);
+ unsigned int be_id = snd_soc_rtd_to_cpu(rtd, 0)->id;
+ struct session_data *session;
+ int path_type;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ path_type = ADM_PATH_PLAYBACK;
+ else
+ path_type = ADM_PATH_LIVE_REC;
+
+ if (be_id >= AFE_MAX_PORTS)
+ return -EINVAL;
+
+ session = &data->port_data[be_id];
+
+ mutex_lock(&data->lock);
+
+ session->path_type = path_type;
+ session->sample_rate = params_rate(params);
+ session->channels = params_channels(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ session->bits_per_sample = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ session->bits_per_sample = 24;
+ break;
+ default:
+ break;
+ }
+
+ mutex_unlock(&data->lock);
+ return 0;
+}
+
+static int msm_routing_probe(struct snd_soc_component *c)
+{
+ int i;
+
+ for (i = 0; i < MAX_SESSIONS; i++) {
+ routing_data->sessions[i].port_id = -1;
+ routing_data->sessions[i].fedai_id = -1;
+ }
+
+ return 0;
+}
+
+static unsigned int q6routing_reg_read(struct snd_soc_component *component,
+ unsigned int reg)
+{
+ /* default value */
+ return 0;
+}
+
+static int q6routing_reg_write(struct snd_soc_component *component,
+ unsigned int reg, unsigned int val)
+{
+ /* dummy */
+ return 0;
+}
+
+static const struct snd_soc_component_driver msm_soc_routing_component = {
+ .probe = msm_routing_probe,
+ .name = DRV_NAME,
+ .hw_params = routing_hw_params,
+ .dapm_widgets = msm_qdsp6_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(msm_qdsp6_widgets),
+ .dapm_routes = intercon,
+ .num_dapm_routes = ARRAY_SIZE(intercon),
+ .read = q6routing_reg_read,
+ .write = q6routing_reg_write,
+};
+
+static int q6pcm_routing_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ routing_data = kzalloc_obj(*routing_data);
+ if (!routing_data)
+ return -ENOMEM;
+
+ routing_data->dev = dev;
+
+ mutex_init(&routing_data->lock);
+ dev_set_drvdata(dev, routing_data);
+
+ return devm_snd_soc_register_component(dev, &msm_soc_routing_component,
+ NULL, 0);
+}
+
+static void q6pcm_routing_remove(struct platform_device *pdev)
+{
+ kfree(routing_data);
+ routing_data = NULL;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id q6pcm_routing_device_id[] = {
+ { .compatible = "qcom,q6adm-routing" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6pcm_routing_device_id);
+#endif
+
+static struct platform_driver q6pcm_routing_platform_driver = {
+ .driver = {
+ .name = "q6routing",
+ .of_match_table = of_match_ptr(q6pcm_routing_device_id),
+ },
+ .probe = q6pcm_routing_probe,
+ .remove = q6pcm_routing_remove,
+};
+module_platform_driver(q6pcm_routing_platform_driver);
+
+MODULE_DESCRIPTION("Q6 Routing platform");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/qdsp6/q6routing.h b/sound/soc/qcom/qdsp6/q6routing.h
new file mode 100644
index 000000000..35514e651
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6routing.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _Q6_PCM_ROUTING_H
+#define _Q6_PCM_ROUTING_H
+
+int q6routing_stream_open(int fedai_id, int perf_mode,
+ int stream_id, int stream_type);
+void q6routing_stream_close(int fedai_id, int stream_type);
+
+#endif /*_Q6_PCM_ROUTING_H */
diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c
new file mode 100644
index 000000000..6381b289c
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6usb.c
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/dma-map-ops.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/iommu.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/asound.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/q6usboffload.h>
+#include <sound/soc.h>
+#include <sound/soc-usb.h>
+
+#include <dt-bindings/sound/qcom,q6afe.h>
+
+#include "q6afe.h"
+#include "q6dsp-lpass-ports.h"
+
+#define Q6_USB_SID_MASK 0xF
+
+struct q6usb_port_data {
+ struct auxiliary_device uauxdev;
+ struct q6afe_usb_cfg usb_cfg;
+ struct snd_soc_usb *usb;
+ struct snd_soc_jack *hs_jack;
+ struct q6usb_offload priv;
+
+ /* Protects against operations between SOC USB and ASoC */
+ struct mutex mutex;
+ struct list_head devices;
+};
+
+static const struct snd_soc_dapm_widget q6usb_dai_widgets[] = {
+ SND_SOC_DAPM_HP("USB_RX_BE", NULL),
+};
+
+static const struct snd_soc_dapm_route q6usb_dapm_routes[] = {
+ {"USB Playback", NULL, "USB_RX_BE"},
+};
+
+static int q6usb_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct q6usb_port_data *data = dev_get_drvdata(dai->dev);
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ int direction = substream->stream;
+ struct q6afe_port *q6usb_afe;
+ struct snd_soc_usb_device *sdev;
+ int ret = -EINVAL;
+
+ mutex_lock(&data->mutex);
+
+ /* No active chip index */
+ if (list_empty(&data->devices))
+ goto out;
+
+ sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list);
+
+ ret = snd_soc_usb_find_supported_format(sdev->chip_idx, params, direction);
+ if (ret < 0)
+ goto out;
+
+ q6usb_afe = q6afe_port_get_from_id(cpu_dai->dev, USB_RX);
+ if (IS_ERR(q6usb_afe)) {
+ ret = PTR_ERR(q6usb_afe);
+ goto out;
+ }
+
+ /* Notify audio DSP about the devices being offloaded */
+ ret = afe_port_send_usb_dev_param(q6usb_afe, sdev->card_idx,
+ sdev->ppcm_idx[sdev->num_playback - 1]);
+
+out:
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static const struct snd_soc_dai_ops q6usb_ops = {
+ .hw_params = q6usb_hw_params,
+};
+
+static struct snd_soc_dai_driver q6usb_be_dais[] = {
+ {
+ .playback = {
+ .stream_name = "USB BE RX",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |
+ SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE |
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |
+ SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_max = 192000,
+ .rate_min = 8000,
+ },
+ .id = USB_RX,
+ .name = "USB_RX_BE",
+ .ops = &q6usb_ops,
+ },
+};
+
+static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *component,
+ const struct of_phandle_args *args,
+ const char **dai_name)
+{
+ int id = args->args[0];
+ int ret = -EINVAL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(q6usb_be_dais); i++) {
+ if (q6usb_be_dais[i].id == id) {
+ *dai_name = q6usb_be_dais[i].name;
+ ret = 0;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int q6usb_get_pcm_id_from_widget(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm);
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *dai;
+
+ for_each_card_rtds(card, rtd) {
+ dai = snd_soc_rtd_to_cpu(rtd, 0);
+ /*
+ * Only look for playback widget. RTD number carries the assigned
+ * PCM index.
+ */
+ if (dai->stream[0].widget == w)
+ return rtd->id;
+ }
+
+ return -1;
+}
+
+static int q6usb_usb_mixer_enabled(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dapm_path *p;
+
+ /* Checks to ensure USB path is enabled/connected */
+ snd_soc_dapm_widget_for_each_sink_path(w, p)
+ if (!strcmp(p->sink->name, "USB Mixer") && p->connect)
+ return 1;
+
+ return 0;
+}
+
+static int q6usb_get_pcm_id(struct snd_soc_component *component)
+{
+ struct snd_soc_dapm_widget *w;
+ struct snd_soc_dapm_path *p;
+ int pidx;
+
+ /*
+ * Traverse widgets to find corresponding FE widget. The DAI links are
+ * built like the following:
+ * MultiMedia* <-> MM_DL* <-> USB Mixer*
+ */
+ for_each_card_widgets(component->card, w) {
+ if (!strncmp(w->name, "MultiMedia", 10)) {
+ /*
+ * Look up all paths associated with the FE widget to see if
+ * the USB BE is enabled. The sink widget is responsible to
+ * link with the USB mixers.
+ */
+ snd_soc_dapm_widget_for_each_sink_path(w, p) {
+ if (q6usb_usb_mixer_enabled(p->sink)) {
+ pidx = q6usb_get_pcm_id_from_widget(w);
+ return pidx;
+ }
+ }
+ }
+ }
+
+ return -1;
+}
+
+static int q6usb_update_offload_route(struct snd_soc_component *component, int card,
+ int pcm, int direction, enum snd_soc_usb_kctl path,
+ long *route)
+{
+ struct q6usb_port_data *data = dev_get_drvdata(component->dev);
+ struct snd_soc_usb_device *sdev;
+ int ret = 0;
+ int idx = -1;
+
+ mutex_lock(&data->mutex);
+
+ if (list_empty(&data->devices) ||
+ direction == SNDRV_PCM_STREAM_CAPTURE) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list);
+
+ /*
+ * Will always look for last PCM device discovered/probed as the
+ * active offload index.
+ */
+ if (card == sdev->card_idx &&
+ pcm == sdev->ppcm_idx[sdev->num_playback - 1]) {
+ idx = path == SND_SOC_USB_KCTL_CARD_ROUTE ?
+ component->card->snd_card->number :
+ q6usb_get_pcm_id(component);
+ }
+
+out:
+ route[0] = idx;
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb,
+ struct snd_soc_usb_device *sdev, bool connected)
+{
+ struct q6usb_port_data *data;
+
+ if (!usb->component)
+ return -ENODEV;
+
+ data = dev_get_drvdata(usb->component->dev);
+
+ mutex_lock(&data->mutex);
+ if (connected) {
+ if (data->hs_jack)
+ snd_jack_report(data->hs_jack->jack, SND_JACK_USB);
+
+ /* Selects the latest USB headset plugged in for offloading */
+ list_add_tail(&sdev->list, &data->devices);
+ } else {
+ list_del(&sdev->list);
+
+ if (data->hs_jack)
+ snd_jack_report(data->hs_jack->jack, 0);
+ }
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+static void q6usb_component_disable_jack(struct q6usb_port_data *data)
+{
+ /* Offload jack has already been disabled */
+ if (!data->hs_jack)
+ return;
+
+ snd_jack_report(data->hs_jack->jack, 0);
+ data->hs_jack = NULL;
+}
+
+static void q6usb_component_enable_jack(struct q6usb_port_data *data,
+ struct snd_soc_jack *jack)
+{
+ snd_jack_report(jack->jack, !list_empty(&data->devices) ? SND_JACK_USB : 0);
+ data->hs_jack = jack;
+}
+
+static int q6usb_component_set_jack(struct snd_soc_component *component,
+ struct snd_soc_jack *jack, void *priv)
+{
+ struct q6usb_port_data *data = dev_get_drvdata(component->dev);
+
+ mutex_lock(&data->mutex);
+ if (jack)
+ q6usb_component_enable_jack(data, jack);
+ else
+ q6usb_component_disable_jack(data);
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+static void q6usb_dai_aux_release(struct device *dev) {}
+
+static int q6usb_dai_add_aux_device(struct q6usb_port_data *data,
+ struct auxiliary_device *auxdev)
+{
+ int ret;
+
+ auxdev->dev.parent = data->priv.dev;
+ auxdev->dev.release = q6usb_dai_aux_release;
+ auxdev->name = "qc-usb-audio-offload";
+
+ ret = auxiliary_device_init(auxdev);
+ if (ret)
+ return ret;
+
+ ret = auxiliary_device_add(auxdev);
+ if (ret)
+ auxiliary_device_uninit(auxdev);
+
+ return ret;
+}
+
+static int q6usb_component_probe(struct snd_soc_component *component)
+{
+ struct q6usb_port_data *data = dev_get_drvdata(component->dev);
+ struct snd_soc_usb *usb;
+ int ret;
+
+ /* Add the QC USB SND aux device */
+ ret = q6usb_dai_add_aux_device(data, &data->uauxdev);
+ if (ret < 0)
+ return ret;
+
+ usb = snd_soc_usb_allocate_port(component, &data->priv);
+ if (IS_ERR(usb))
+ return -ENOMEM;
+
+ usb->connection_status_cb = q6usb_alsa_connection_cb;
+ usb->update_offload_route_info = q6usb_update_offload_route;
+
+ snd_soc_usb_add_port(usb);
+ data->usb = usb;
+
+ return 0;
+}
+
+static void q6usb_component_remove(struct snd_soc_component *component)
+{
+ struct q6usb_port_data *data = dev_get_drvdata(component->dev);
+
+ snd_soc_usb_remove_port(data->usb);
+ auxiliary_device_delete(&data->uauxdev);
+ auxiliary_device_uninit(&data->uauxdev);
+ snd_soc_usb_free_port(data->usb);
+}
+
+static const struct snd_soc_component_driver q6usb_dai_component = {
+ .probe = q6usb_component_probe,
+ .set_jack = q6usb_component_set_jack,
+ .remove = q6usb_component_remove,
+ .name = "q6usb-dai-component",
+ .dapm_widgets = q6usb_dai_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(q6usb_dai_widgets),
+ .dapm_routes = q6usb_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(q6usb_dapm_routes),
+ .of_xlate_dai_name = q6usb_audio_ports_of_xlate_dai_name,
+};
+
+static int q6usb_dai_dev_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct q6usb_port_data *data;
+ struct device *dev = &pdev->dev;
+ struct of_phandle_args args;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ret = of_property_read_u16(node, "qcom,usb-audio-intr-idx",
+ &data->priv.intr_num);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to read intr idx.\n");
+ return ret;
+ }
+
+ ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
+ if (!ret)
+ data->priv.sid = args.args[0] & Q6_USB_SID_MASK;
+
+ ret = devm_mutex_init(dev, &data->mutex);
+ if (ret < 0)
+ return ret;
+
+ data->priv.domain = iommu_get_domain_for_dev(&pdev->dev);
+
+ data->priv.dev = dev;
+ INIT_LIST_HEAD(&data->devices);
+ dev_set_drvdata(dev, data);
+
+ return devm_snd_soc_register_component(dev, &q6usb_dai_component,
+ q6usb_be_dais, ARRAY_SIZE(q6usb_be_dais));
+}
+
+static const struct of_device_id q6usb_dai_device_id[] = {
+ { .compatible = "qcom,q6usb" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6usb_dai_device_id);
+
+static struct platform_driver q6usb_dai_platform_driver = {
+ .driver = {
+ .name = "q6usb-dai",
+ .of_match_table = q6usb_dai_device_id,
+ },
+ .probe = q6usb_dai_dev_probe,
+ /*
+ * Remove not required as resources are cleaned up as part of
+ * component removal. Others are device managed resources.
+ */
+};
+module_platform_driver(q6usb_dai_platform_driver);
+
+MODULE_DESCRIPTION("Q6 USB backend dai driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/qdsp6/topology.c b/sound/soc/qcom/qdsp6/topology.c
new file mode 100644
index 000000000..faafbc5c0
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/topology.c
@@ -0,0 +1,1443 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020, Linaro Limited
+
+#include <linux/cleanup.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/asound.h>
+#include <linux/firmware.h>
+#include <sound/soc-topology.h>
+#include <sound/soc-dpcm.h>
+#include <uapi/sound/snd_ar_tokens.h>
+#include <linux/kernel.h>
+#include <linux/wait.h>
+#include "q6apm.h"
+#include "audioreach.h"
+
+struct snd_ar_control {
+ u32 graph_id; /* Graph ID */
+ u32 sgid; /* Sub Graph ID */
+ u32 module_instance_id; /* Connected Module Instance ID */
+ struct snd_soc_dapm_widget *w;
+ struct list_head node;
+ struct snd_soc_component *scomp;
+};
+
+static struct audioreach_graph_info *audioreach_tplg_alloc_graph_info(struct q6apm *apm,
+ uint32_t graph_id,
+ bool *found)
+{
+ struct audioreach_graph_info *info;
+ int ret;
+
+ mutex_lock(&apm->lock);
+ info = idr_find(&apm->graph_info_idr, graph_id);
+ mutex_unlock(&apm->lock);
+
+ if (info) {
+ *found = true;
+ return info;
+ }
+
+ *found = false;
+ info = kzalloc_obj(*info);
+ if (!info)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&info->sg_list);
+
+ mutex_lock(&apm->lock);
+ ret = idr_alloc_u32(&apm->graph_info_idr, info, &graph_id, graph_id, GFP_KERNEL);
+ mutex_unlock(&apm->lock);
+
+ if (ret < 0) {
+ dev_err(apm->dev, "Failed to allocate Graph ID (%x)\n", graph_id);
+ kfree(info);
+ return ERR_PTR(ret);
+ }
+
+ info->id = graph_id;
+
+ return info;
+}
+
+static void audioreach_tplg_add_sub_graph(struct audioreach_sub_graph *sg,
+ struct audioreach_graph_info *info)
+{
+ list_add_tail(&sg->node, &info->sg_list);
+ sg->info = info;
+ info->num_sub_graphs++;
+}
+
+static struct audioreach_sub_graph *audioreach_tplg_alloc_sub_graph(struct q6apm *apm,
+ uint32_t sub_graph_id,
+ bool *found)
+{
+ struct audioreach_sub_graph *sg;
+ int ret;
+
+ if (!sub_graph_id)
+ return ERR_PTR(-EINVAL);
+
+ /* Find if there is already a matching sub-graph */
+ mutex_lock(&apm->lock);
+ sg = idr_find(&apm->sub_graphs_idr, sub_graph_id);
+ mutex_unlock(&apm->lock);
+
+ if (sg) {
+ *found = true;
+ return sg;
+ }
+
+ *found = false;
+ sg = kzalloc_obj(*sg);
+ if (!sg)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&sg->container_list);
+
+ mutex_lock(&apm->lock);
+ ret = idr_alloc_u32(&apm->sub_graphs_idr, sg, &sub_graph_id, sub_graph_id, GFP_KERNEL);
+ mutex_unlock(&apm->lock);
+
+ if (ret < 0) {
+ dev_err(apm->dev, "Failed to allocate Sub-Graph Instance ID (%x)\n", sub_graph_id);
+ kfree(sg);
+ return ERR_PTR(ret);
+ }
+
+ sg->sub_graph_id = sub_graph_id;
+
+ return sg;
+}
+
+static struct audioreach_container *audioreach_tplg_alloc_container(struct q6apm *apm,
+ struct audioreach_sub_graph *sg,
+ uint32_t container_id,
+ bool *found)
+{
+ struct audioreach_container *cont;
+ int ret;
+
+ if (!container_id)
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&apm->lock);
+ cont = idr_find(&apm->containers_idr, container_id);
+ mutex_unlock(&apm->lock);
+
+ if (cont) {
+ *found = true;
+ return cont;
+ }
+ *found = false;
+
+ cont = kzalloc_obj(*cont);
+ if (!cont)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&cont->modules_list);
+
+ mutex_lock(&apm->lock);
+ ret = idr_alloc_u32(&apm->containers_idr, cont, &container_id, container_id, GFP_KERNEL);
+ mutex_unlock(&apm->lock);
+
+ if (ret < 0) {
+ dev_err(apm->dev, "Failed to allocate Container Instance ID (%x)\n", container_id);
+ kfree(cont);
+ return ERR_PTR(ret);
+ }
+
+ cont->container_id = container_id;
+ cont->sub_graph = sg;
+ /* add to container list */
+ list_add_tail(&cont->node, &sg->container_list);
+ sg->num_containers++;
+
+ return cont;
+}
+
+static struct audioreach_module *audioreach_tplg_alloc_module(struct q6apm *apm,
+ struct audioreach_container *cont,
+ struct snd_soc_dapm_widget *w,
+ uint32_t module_id, bool *found)
+{
+ struct audioreach_module *mod;
+ int ret;
+
+ mutex_lock(&apm->lock);
+ mod = idr_find(&apm->modules_idr, module_id);
+ mutex_unlock(&apm->lock);
+
+ if (mod) {
+ *found = true;
+ return mod;
+ }
+ *found = false;
+ mod = kzalloc_obj(*mod);
+ if (!mod)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_lock(&apm->lock);
+ if (!module_id) { /* alloc module id dynamically */
+ ret = idr_alloc_cyclic(&apm->modules_idr, mod,
+ AR_MODULE_DYNAMIC_INSTANCE_ID_START,
+ AR_MODULE_DYNAMIC_INSTANCE_ID_END, GFP_KERNEL);
+ } else {
+ ret = idr_alloc_u32(&apm->modules_idr, mod, &module_id, module_id, GFP_KERNEL);
+ }
+ mutex_unlock(&apm->lock);
+
+ if (ret < 0) {
+ dev_err(apm->dev, "Failed to allocate Module Instance ID (%x)\n", module_id);
+ kfree(mod);
+ return ERR_PTR(ret);
+ }
+
+ mod->instance_id = module_id;
+ /* add to module list */
+ list_add_tail(&mod->node, &cont->modules_list);
+ mod->container = cont;
+ mod->widget = w;
+ cont->num_modules++;
+
+ return mod;
+}
+
+static const struct snd_soc_tplg_vendor_array *
+audioreach_get_sg_array(const struct snd_soc_tplg_private *private)
+{
+ const struct snd_soc_tplg_vendor_array *sg_array = NULL;
+ bool found = false;
+ int sz;
+
+ for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) {
+ const struct snd_soc_tplg_vendor_value_elem *sg_elem;
+ int tkn_count = 0;
+
+ sg_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz);
+ sg_elem = sg_array->value;
+ sz = sz + le32_to_cpu(sg_array->size);
+ while (!found && tkn_count <= (le32_to_cpu(sg_array->num_elems) - 1)) {
+ switch (le32_to_cpu(sg_elem->token)) {
+ case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:
+ found = true;
+ break;
+ default:
+ break;
+ }
+ tkn_count++;
+ sg_elem++;
+ }
+ }
+
+ if (found)
+ return sg_array;
+
+ return NULL;
+}
+
+static const struct snd_soc_tplg_vendor_array *
+audioreach_get_cont_array(const struct snd_soc_tplg_private *private)
+{
+ const struct snd_soc_tplg_vendor_array *cont_array = NULL;
+ bool found = false;
+ int sz;
+
+ for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) {
+ const struct snd_soc_tplg_vendor_value_elem *cont_elem;
+ int tkn_count = 0;
+
+ cont_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz);
+ cont_elem = cont_array->value;
+ sz = sz + le32_to_cpu(cont_array->size);
+ while (!found && tkn_count <= (le32_to_cpu(cont_array->num_elems) - 1)) {
+ switch (le32_to_cpu(cont_elem->token)) {
+ case AR_TKN_U32_CONTAINER_INSTANCE_ID:
+ found = true;
+ break;
+ default:
+ break;
+ }
+ tkn_count++;
+ cont_elem++;
+ }
+ }
+
+ if (found)
+ return cont_array;
+
+ return NULL;
+}
+
+static const struct snd_soc_tplg_vendor_array *
+audioreach_get_module_array(const struct snd_soc_tplg_private *private)
+{
+ const struct snd_soc_tplg_vendor_array *mod_array = NULL;
+ bool found = false;
+ int sz = 0;
+
+ for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) {
+ const struct snd_soc_tplg_vendor_value_elem *mod_elem;
+ int tkn_count = 0;
+
+ mod_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz);
+ mod_elem = mod_array->value;
+ sz = sz + le32_to_cpu(mod_array->size);
+ while (!found && tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) {
+ switch (le32_to_cpu(mod_elem->token)) {
+ case AR_TKN_U32_MODULE_INSTANCE_ID:
+ found = true;
+ break;
+ default:
+ break;
+ }
+ tkn_count++;
+ mod_elem++;
+ }
+ }
+
+ if (found)
+ return mod_array;
+
+ return NULL;
+}
+
+static struct audioreach_module_priv_data *
+audioreach_get_module_priv_data(const struct snd_soc_tplg_private *private)
+{
+ int sz;
+
+ for (sz = 0; sz < le32_to_cpu(private->size); ) {
+ const struct snd_soc_tplg_vendor_array *mod_array;
+
+ mod_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz);
+ if (le32_to_cpu(mod_array->type) == SND_SOC_AR_TPLG_MODULE_CFG_TYPE) {
+ struct audioreach_module_priv_data *pdata;
+
+ pdata = kzalloc_flex(*pdata, data,
+ le32_to_cpu(mod_array->size));
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(pdata, ((u8 *)private->data + sz), struct_size(pdata, data,
+ le32_to_cpu(mod_array->size)));
+ return pdata;
+ }
+
+ sz = sz + le32_to_cpu(mod_array->size);
+ }
+
+ return NULL;
+}
+
+static struct audioreach_sub_graph *audioreach_parse_sg_tokens(struct q6apm *apm,
+ const struct snd_soc_tplg_private *private)
+{
+ const struct snd_soc_tplg_vendor_value_elem *sg_elem;
+ const struct snd_soc_tplg_vendor_array *sg_array;
+ struct audioreach_graph_info *info = NULL;
+ int graph_id, sub_graph_id, tkn_count = 0;
+ struct audioreach_sub_graph *sg;
+ bool found;
+
+ sg_array = audioreach_get_sg_array(private);
+ sg_elem = sg_array->value;
+
+ while (tkn_count <= (le32_to_cpu(sg_array->num_elems) - 1)) {
+ switch (le32_to_cpu(sg_elem->token)) {
+ case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:
+ sub_graph_id = le32_to_cpu(sg_elem->value);
+ sg = audioreach_tplg_alloc_sub_graph(apm, sub_graph_id, &found);
+ if (IS_ERR(sg)) {
+ return sg;
+ } else if (found) {
+ /* Already parsed data for this sub-graph */
+ return sg;
+ }
+ break;
+ case AR_TKN_DAI_INDEX:
+ /* Sub graph is associated with predefined graph */
+ graph_id = le32_to_cpu(sg_elem->value);
+ info = audioreach_tplg_alloc_graph_info(apm, graph_id, &found);
+ if (IS_ERR(info))
+ return ERR_CAST(info);
+ break;
+ case AR_TKN_U32_SUB_GRAPH_PERF_MODE:
+ sg->perf_mode = le32_to_cpu(sg_elem->value);
+ break;
+ case AR_TKN_U32_SUB_GRAPH_DIRECTION:
+ sg->direction = le32_to_cpu(sg_elem->value);
+ break;
+ case AR_TKN_U32_SUB_GRAPH_SCENARIO_ID:
+ sg->scenario_id = le32_to_cpu(sg_elem->value);
+ break;
+ default:
+ dev_err(apm->dev, "Not a valid token %d for graph\n", sg_elem->token);
+ break;
+
+ }
+ tkn_count++;
+ sg_elem++;
+ }
+
+ /* Sub graph is associated with predefined graph */
+ if (info)
+ audioreach_tplg_add_sub_graph(sg, info);
+
+ return sg;
+}
+
+static struct audioreach_container *audioreach_parse_cont_tokens(struct q6apm *apm,
+ struct audioreach_sub_graph *sg,
+ const struct snd_soc_tplg_private *private)
+{
+ const struct snd_soc_tplg_vendor_value_elem *cont_elem;
+ const struct snd_soc_tplg_vendor_array *cont_array;
+ struct audioreach_container *cont;
+ int container_id, tkn_count = 0;
+ bool found = false;
+
+ cont_array = audioreach_get_cont_array(private);
+ cont_elem = cont_array->value;
+
+ while (tkn_count <= (le32_to_cpu(cont_array->num_elems) - 1)) {
+ switch (le32_to_cpu(cont_elem->token)) {
+ case AR_TKN_U32_CONTAINER_INSTANCE_ID:
+ container_id = le32_to_cpu(cont_elem->value);
+ cont = audioreach_tplg_alloc_container(apm, sg, container_id, &found);
+ if (IS_ERR(cont) || found)/* Error or Already parsed container data */
+ return cont;
+ break;
+ case AR_TKN_U32_CONTAINER_CAPABILITY_ID:
+ cont->capability_id = le32_to_cpu(cont_elem->value);
+ break;
+ case AR_TKN_U32_CONTAINER_STACK_SIZE:
+ cont->stack_size = le32_to_cpu(cont_elem->value);
+ break;
+ case AR_TKN_U32_CONTAINER_GRAPH_POS:
+ cont->graph_pos = le32_to_cpu(cont_elem->value);
+ break;
+ case AR_TKN_U32_CONTAINER_PROC_DOMAIN:
+ cont->proc_domain = le32_to_cpu(cont_elem->value);
+ break;
+ default:
+ dev_err(apm->dev, "Not a valid token %d for graph\n", cont_elem->token);
+ break;
+
+ }
+ tkn_count++;
+ cont_elem++;
+ }
+
+ return cont;
+}
+
+static struct audioreach_module *audioreach_parse_common_tokens(struct q6apm *apm,
+ struct audioreach_container *cont,
+ const struct snd_soc_tplg_private *private,
+ struct snd_soc_dapm_widget *w)
+{
+ uint32_t max_ip_port = 0, max_op_port = 0;
+ uint32_t src_mod_op_port_id[AR_MAX_MOD_LINKS] = { 0, };
+ uint32_t dst_mod_inst_id[AR_MAX_MOD_LINKS] = { 0, };
+ uint32_t dst_mod_ip_port_id[AR_MAX_MOD_LINKS] = { 0, };
+ uint32_t src_mod_inst_id = 0;
+
+ int module_id = 0, instance_id = 0, tkn_count = 0;
+ const struct snd_soc_tplg_vendor_value_elem *mod_elem;
+ const struct snd_soc_tplg_vendor_array *mod_array;
+ struct audioreach_module *mod = NULL;
+ uint32_t token;
+ bool found;
+ int max_tokens;
+
+ mod_array = audioreach_get_module_array(private);
+ mod_elem = mod_array->value;
+ max_tokens = le32_to_cpu(mod_array->num_elems);
+ while (tkn_count <= (max_tokens - 1)) {
+ token = le32_to_cpu(mod_elem->token);
+ switch (token) {
+ /* common module info */
+ case AR_TKN_U32_MODULE_ID:
+ module_id = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_INSTANCE_ID:
+ instance_id = le32_to_cpu(mod_elem->value);
+ mod = audioreach_tplg_alloc_module(apm, cont, w,
+ instance_id, &found);
+ if (IS_ERR(mod)) {
+ return mod;
+ } else if (found) {
+ dev_err(apm->dev, "Duplicate Module Instance ID 0x%08x found\n",
+ instance_id);
+ return ERR_PTR(-EINVAL);
+ }
+
+ break;
+ case AR_TKN_U32_MODULE_MAX_IP_PORTS:
+ max_ip_port = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_MAX_OP_PORTS:
+ max_op_port = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_SRC_INSTANCE_ID:
+ src_mod_inst_id = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_SRC_OP_PORT_ID:
+ src_mod_op_port_id[0] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_SRC_OP_PORT_ID1:
+ src_mod_op_port_id[1] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_SRC_OP_PORT_ID2:
+ src_mod_op_port_id[2] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_SRC_OP_PORT_ID3:
+ src_mod_op_port_id[3] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_SRC_OP_PORT_ID4:
+ src_mod_op_port_id[4] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_SRC_OP_PORT_ID5:
+ src_mod_op_port_id[5] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_SRC_OP_PORT_ID6:
+ src_mod_op_port_id[6] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_SRC_OP_PORT_ID7:
+ src_mod_op_port_id[7] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_INSTANCE_ID:
+ dst_mod_inst_id[0] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_INSTANCE_ID1:
+ dst_mod_inst_id[1] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_INSTANCE_ID2:
+ dst_mod_inst_id[2] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_INSTANCE_ID3:
+ dst_mod_inst_id[3] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_INSTANCE_ID4:
+ dst_mod_inst_id[4] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_INSTANCE_ID5:
+ dst_mod_inst_id[5] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_INSTANCE_ID6:
+ dst_mod_inst_id[6] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_INSTANCE_ID7:
+ dst_mod_inst_id[7] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_IN_PORT_ID:
+ dst_mod_ip_port_id[0] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_IN_PORT_ID1:
+ dst_mod_ip_port_id[1] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_IN_PORT_ID2:
+ dst_mod_ip_port_id[2] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_IN_PORT_ID3:
+ dst_mod_ip_port_id[3] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_IN_PORT_ID4:
+ dst_mod_ip_port_id[4] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_IN_PORT_ID5:
+ dst_mod_ip_port_id[5] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_IN_PORT_ID6:
+ dst_mod_ip_port_id[6] = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_DST_IN_PORT_ID7:
+ dst_mod_ip_port_id[7] = le32_to_cpu(mod_elem->value);
+ break;
+ default:
+ break;
+
+ }
+ tkn_count++;
+ mod_elem++;
+ }
+
+ if (mod) {
+ int pn, id = 0;
+
+ mod->module_id = module_id;
+ mod->max_ip_port = max_ip_port;
+ mod->max_op_port = max_op_port;
+ mod->src_mod_inst_id = src_mod_inst_id;
+ for (pn = 0; pn < mod->max_op_port; pn++) {
+ if (src_mod_op_port_id[pn] && dst_mod_inst_id[pn] &&
+ dst_mod_ip_port_id[pn]) {
+ mod->src_mod_op_port_id[id] = src_mod_op_port_id[pn];
+ mod->dst_mod_inst_id[id] = dst_mod_inst_id[pn];
+ mod->dst_mod_ip_port_id[id] = dst_mod_ip_port_id[pn];
+ id++;
+ mod->num_connections = id;
+ }
+ }
+ }
+
+ return mod;
+}
+
+static int audioreach_widget_load_module_common(struct snd_soc_component *component,
+ int index, struct snd_soc_dapm_widget *w,
+ const struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+ struct q6apm *apm = dev_get_drvdata(component->dev);
+ struct audioreach_container *cont;
+ struct audioreach_sub_graph *sg;
+ struct audioreach_module *mod;
+ struct snd_soc_dobj *dobj;
+
+ sg = audioreach_parse_sg_tokens(apm, &tplg_w->priv);
+ if (IS_ERR(sg))
+ return PTR_ERR(sg);
+
+ cont = audioreach_parse_cont_tokens(apm, sg, &tplg_w->priv);
+ if (IS_ERR(cont))
+ return PTR_ERR(cont);
+
+ mod = audioreach_parse_common_tokens(apm, cont, &tplg_w->priv, w);
+ if (IS_ERR_OR_NULL(mod))
+ return mod ? PTR_ERR(mod) : -ENODEV;
+
+ mod->data = audioreach_get_module_priv_data(&tplg_w->priv);
+
+ dobj = &w->dobj;
+ dobj->private = mod;
+
+ return 0;
+}
+
+static int audioreach_widget_load_enc_dec_cnv(struct snd_soc_component *component,
+ int index, struct snd_soc_dapm_widget *w,
+ const struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+ const struct snd_soc_tplg_vendor_value_elem *mod_elem;
+ const struct snd_soc_tplg_vendor_array *mod_array;
+ struct audioreach_module *mod;
+ struct snd_soc_dobj *dobj;
+ int tkn_count = 0;
+ int ret;
+
+ ret = audioreach_widget_load_module_common(component, index, w, tplg_w);
+ if (ret)
+ return ret;
+
+ dobj = &w->dobj;
+ mod = dobj->private;
+ mod_array = audioreach_get_module_array(&tplg_w->priv);
+ mod_elem = mod_array->value;
+
+ while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) {
+ switch (le32_to_cpu(mod_elem->token)) {
+ case AR_TKN_U32_MODULE_FMT_INTERLEAVE:
+ mod->interleave_type = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_FMT_SAMPLE_RATE:
+ mod->rate = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_FMT_BIT_DEPTH:
+ mod->bit_depth = le32_to_cpu(mod_elem->value);
+ break;
+ default:
+ break;
+ }
+ tkn_count++;
+ mod_elem++;
+ }
+
+ return 0;
+}
+
+static int audioreach_widget_log_module_load(struct audioreach_module *mod,
+ const struct snd_soc_tplg_vendor_array *mod_array)
+{
+ const struct snd_soc_tplg_vendor_value_elem *mod_elem;
+ int tkn_count = 0;
+
+ mod_elem = mod_array->value;
+
+ while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) {
+ switch (le32_to_cpu(mod_elem->token)) {
+
+ case AR_TKN_U32_MODULE_LOG_CODE:
+ mod->log_code = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_LOG_TAP_POINT_ID:
+ mod->log_tap_point_id = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_LOG_MODE:
+ mod->log_mode = le32_to_cpu(mod_elem->value);
+ break;
+ default:
+ break;
+ }
+ tkn_count++;
+ mod_elem++;
+ }
+
+ return 0;
+}
+
+static int audioreach_widget_dma_module_load(struct audioreach_module *mod,
+ const struct snd_soc_tplg_vendor_array *mod_array)
+{
+ const struct snd_soc_tplg_vendor_value_elem *mod_elem;
+ int tkn_count = 0;
+
+ mod_elem = mod_array->value;
+
+ while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) {
+ switch (le32_to_cpu(mod_elem->token)) {
+ case AR_TKN_U32_MODULE_HW_IF_IDX:
+ mod->hw_interface_idx = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_FMT_DATA:
+ mod->data_format = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_HW_IF_TYPE:
+ mod->hw_interface_type = le32_to_cpu(mod_elem->value);
+ break;
+ default:
+ break;
+ }
+ tkn_count++;
+ mod_elem++;
+ }
+
+ return 0;
+}
+
+static int audioreach_widget_i2s_module_load(struct audioreach_module *mod,
+ const struct snd_soc_tplg_vendor_array *mod_array)
+{
+ const struct snd_soc_tplg_vendor_value_elem *mod_elem;
+ int tkn_count = 0;
+
+ mod_elem = mod_array->value;
+
+ while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) {
+ switch (le32_to_cpu(mod_elem->token)) {
+ case AR_TKN_U32_MODULE_HW_IF_IDX:
+ mod->hw_interface_idx = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_FMT_DATA:
+ mod->data_format = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_HW_IF_TYPE:
+ mod->hw_interface_type = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_SD_LINE_IDX:
+ mod->sd_line_idx = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_WS_SRC:
+ mod->ws_src = le32_to_cpu(mod_elem->value);
+ break;
+ default:
+ break;
+ }
+ tkn_count++;
+ mod_elem++;
+ }
+
+ return 0;
+}
+
+static int audioreach_widget_audio_if_module_load(struct audioreach_module *mod,
+ const struct snd_soc_tplg_vendor_array *mod_array)
+{
+ const struct snd_soc_tplg_vendor_value_elem *mod_elem;
+ int tkn_count = 0;
+ u32 val;
+
+ mod_elem = mod_array->value;
+
+ while (tkn_count < le32_to_cpu(mod_array->num_elems)) {
+ val = le32_to_cpu(mod_elem->value);
+ switch (le32_to_cpu(mod_elem->token)) {
+ case AR_TKN_U32_MODULE_HW_IF_IDX:
+ mod->hw_interface_idx = val;
+ break;
+ case AR_TKN_U32_MODULE_FMT_DATA:
+ mod->data_format = val;
+ break;
+ case AR_TKN_U16_MODULE_SYNC_SRC:
+ if (val > U16_MAX)
+ return -EINVAL;
+ mod->sync_src = (u16)val;
+ break;
+ case AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE:
+ if (val > U16_MAX)
+ return -EINVAL;
+ mod->ctrl_data_out_enable = (u16)val;
+ break;
+ case AR_TKN_U32_MODULE_SLOT_MASK:
+ mod->slot_mask = val;
+ break;
+ case AR_TKN_U16_MODULE_NSLOTS_PER_FRAME:
+ if (val > U16_MAX)
+ return -EINVAL;
+ mod->nslots_per_frame = (u16)val;
+ break;
+ case AR_TKN_U16_MODULE_SLOT_WIDTH:
+ if (val > U16_MAX)
+ return -EINVAL;
+ mod->slot_width = (u16)val;
+ break;
+ case AR_TKN_U16_MODULE_INTF_MODE:
+ if (val > U16_MAX)
+ return -EINVAL;
+ mod->intf_mode = (u16)val;
+ break;
+ case AR_TKN_U16_MODULE_SYNC_MODE:
+ if (val > U16_MAX)
+ return -EINVAL;
+ mod->sync_mode = (u16)val;
+ break;
+ case AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE:
+ if (val > U16_MAX)
+ return -EINVAL;
+ mod->ctrl_invert_sync_pulse = (u16)val;
+ break;
+ case AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY:
+ if (val > U16_MAX)
+ return -EINVAL;
+ mod->ctrl_sync_data_delay = (u16)val;
+ break;
+ case AR_TKN_U16_MODULE_QAIF_TYPE:
+ if (val > U16_MAX)
+ return -EINVAL;
+ mod->qaif_type = (u16)val;
+ break;
+ case AR_TKN_U32_MODULE_ACTIVE_LANE_MASK:
+ mod->active_lane_mask = val;
+ break;
+ case AR_TKN_U32_MODULE_FRAME_SYNC_RATE:
+ mod->frame_sync_rate = val;
+ break;
+ case AR_TKN_U16_MODULE_BIT_CLK_TYPE:
+ if (val > U16_MAX)
+ return -EINVAL;
+ mod->bit_clk_type = (u16)val;
+ break;
+ case AR_TKN_U8_MODULE_INV_INT_BIT_CLK:
+ if (val > U8_MAX)
+ return -EINVAL;
+ mod->inv_int_bit_clk = (u8)val;
+ break;
+ case AR_TKN_U8_MODULE_INV_EXT_BIT_CLK:
+ if (val > U8_MAX)
+ return -EINVAL;
+ mod->inv_ext_bit_clk = (u8)val;
+ break;
+ default:
+ break;
+ }
+ tkn_count++;
+ mod_elem++;
+ }
+
+ return 0;
+}
+
+static int audioreach_widget_dp_module_load(struct audioreach_module *mod,
+ const struct snd_soc_tplg_vendor_array *mod_array)
+{
+ const struct snd_soc_tplg_vendor_value_elem *mod_elem;
+ int tkn_count = 0;
+
+ mod_elem = mod_array->value;
+
+ while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) {
+ switch (le32_to_cpu(mod_elem->token)) {
+ case AR_TKN_U32_MODULE_FMT_DATA:
+ mod->data_format = le32_to_cpu(mod_elem->value);
+ break;
+ default:
+ break;
+ }
+ tkn_count++;
+ mod_elem++;
+ }
+
+ return 0;
+}
+
+static int audioreach_widget_load_buffer(struct snd_soc_component *component,
+ int index, struct snd_soc_dapm_widget *w,
+ const struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+ const struct snd_soc_tplg_vendor_array *mod_array;
+ struct audioreach_module *mod;
+ struct snd_soc_dobj *dobj;
+ int ret;
+
+ ret = audioreach_widget_load_module_common(component, index, w, tplg_w);
+ if (ret)
+ return ret;
+
+ dobj = &w->dobj;
+ mod = dobj->private;
+
+ mod_array = audioreach_get_module_array(&tplg_w->priv);
+
+ switch (mod->module_id) {
+ case MODULE_ID_CODEC_DMA_SINK:
+ case MODULE_ID_CODEC_DMA_SOURCE:
+ audioreach_widget_dma_module_load(mod, mod_array);
+ break;
+ case MODULE_ID_DATA_LOGGING:
+ audioreach_widget_log_module_load(mod, mod_array);
+ break;
+ case MODULE_ID_I2S_SINK:
+ case MODULE_ID_I2S_SOURCE:
+ audioreach_widget_i2s_module_load(mod, mod_array);
+ break;
+ case MODULE_ID_AUDIO_IF_SINK:
+ case MODULE_ID_AUDIO_IF_SOURCE:
+ ret = audioreach_widget_audio_if_module_load(mod, mod_array);
+ if (ret)
+ return ret;
+ break;
+ case MODULE_ID_DISPLAY_PORT_SINK:
+ audioreach_widget_dp_module_load(mod, mod_array);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int audioreach_widget_load_mixer(struct snd_soc_component *component,
+ int index, struct snd_soc_dapm_widget *w,
+ const struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+ const struct snd_soc_tplg_vendor_value_elem *w_elem;
+ const struct snd_soc_tplg_vendor_array *w_array;
+ struct snd_ar_control *scontrol;
+ struct q6apm *data = dev_get_drvdata(component->dev);
+ struct snd_soc_dobj *dobj;
+ int tkn_count = 0;
+
+ w_array = &tplg_w->priv.array[0];
+
+ scontrol = kzalloc_obj(*scontrol);
+ if (!scontrol)
+ return -ENOMEM;
+
+ scontrol->scomp = component;
+ dobj = &w->dobj;
+ dobj->private = scontrol;
+
+ w_elem = w_array->value;
+ while (tkn_count <= (le32_to_cpu(w_array->num_elems) - 1)) {
+ switch (le32_to_cpu(w_elem->token)) {
+ case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:
+ scontrol->sgid = le32_to_cpu(w_elem->value);
+ break;
+ case AR_TKN_DAI_INDEX:
+ scontrol->graph_id = le32_to_cpu(w_elem->value);
+ break;
+ default: /* ignore other tokens */
+ break;
+ }
+ tkn_count++;
+ w_elem++;
+ }
+
+ scontrol->w = w;
+ list_add_tail(&scontrol->node, &data->widget_list);
+
+ return 0;
+}
+
+static int audioreach_pga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_component *c = snd_soc_dapm_to_component(dapm);
+ struct audioreach_module *mod = w->dobj.private;
+ struct q6apm *apm = dev_get_drvdata(c->dev);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ /* apply gain after power up of widget */
+ audioreach_gain_set_vol_ctrl(apm, mod, mod->gain);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_tplg_widget_events audioreach_widget_ops[] = {
+ { AR_PGA_DAPM_EVENT, audioreach_pga_event },
+};
+
+static int audioreach_widget_load_pga(struct snd_soc_component *component,
+ int index, struct snd_soc_dapm_widget *w,
+ const struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+ struct audioreach_module *mod;
+ struct snd_soc_dobj *dobj;
+ int ret;
+
+ ret = audioreach_widget_load_module_common(component, index, w, tplg_w);
+ if (ret)
+ return ret;
+
+ dobj = &w->dobj;
+ mod = dobj->private;
+ mod->gain = VOL_CTRL_DEFAULT_GAIN;
+
+ ret = snd_soc_tplg_widget_bind_event(w, audioreach_widget_ops,
+ ARRAY_SIZE(audioreach_widget_ops),
+ le16_to_cpu(tplg_w->event_type));
+ if (ret) {
+ dev_err(component->dev, "matching event handlers NOT found for %d\n",
+ le16_to_cpu(tplg_w->event_type));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int audioreach_widget_ready(struct snd_soc_component *component,
+ int index, struct snd_soc_dapm_widget *w,
+ struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+ switch (w->id) {
+ case snd_soc_dapm_aif_in:
+ case snd_soc_dapm_aif_out:
+ audioreach_widget_load_buffer(component, index, w, tplg_w);
+ break;
+ case snd_soc_dapm_decoder:
+ case snd_soc_dapm_encoder:
+ case snd_soc_dapm_src:
+ audioreach_widget_load_enc_dec_cnv(component, index, w, tplg_w);
+ break;
+ case snd_soc_dapm_buffer:
+ audioreach_widget_load_buffer(component, index, w, tplg_w);
+ break;
+ case snd_soc_dapm_mixer:
+ return audioreach_widget_load_mixer(component, index, w, tplg_w);
+ case snd_soc_dapm_pga:
+ return audioreach_widget_load_pga(component, index, w, tplg_w);
+ case snd_soc_dapm_dai_link:
+ case snd_soc_dapm_scheduler:
+ case snd_soc_dapm_out_drv:
+ default:
+ dev_err(component->dev, "Widget type (0x%x) not yet supported\n", w->id);
+ break;
+ }
+
+ return 0;
+}
+
+static int audioreach_widget_unload(struct snd_soc_component *scomp,
+ struct snd_soc_dobj *dobj)
+{
+ const struct snd_soc_dapm_widget *w = container_of(dobj, struct snd_soc_dapm_widget, dobj);
+ struct q6apm *apm = dev_get_drvdata(scomp->dev);
+ struct audioreach_container *cont;
+ struct audioreach_module *mod;
+
+ if (w->id == snd_soc_dapm_mixer) {
+ /* virtual widget */
+ struct snd_ar_control *scontrol = dobj->private;
+
+ list_del(&scontrol->node);
+ kfree(scontrol);
+ return 0;
+ }
+ mod = dobj->private;
+ if (!mod)
+ return 0;
+
+ cont = mod->container;
+
+ mutex_lock(&apm->lock);
+ idr_remove(&apm->modules_idr, mod->instance_id);
+ cont->num_modules--;
+
+ list_del(&mod->node);
+ kfree(mod->data);
+ kfree(mod);
+ /* Graph Info has N sub-graphs, sub-graph has N containers, Container has N Modules */
+ if (list_empty(&cont->modules_list)) { /* if no modules in the container then remove it */
+ struct audioreach_sub_graph *sg = cont->sub_graph;
+
+ idr_remove(&apm->containers_idr, cont->container_id);
+ list_del(&cont->node);
+ sg->num_containers--;
+ kfree(cont);
+ /* check if there are no more containers in the sub graph and remove it */
+ if (list_empty(&sg->container_list)) {
+ struct audioreach_graph_info *info = sg->info;
+
+ idr_remove(&apm->sub_graphs_idr, sg->sub_graph_id);
+ list_del(&sg->node);
+ info->num_sub_graphs--;
+ kfree(sg);
+ /* Check if there are no more sub-graphs left then remove graph info */
+ if (list_empty(&info->sg_list)) {
+ idr_remove(&apm->graph_info_idr, info->id);
+ kfree(info);
+ }
+ }
+ }
+
+ mutex_unlock(&apm->lock);
+
+ return 0;
+}
+
+static struct snd_ar_control *audioreach_find_widget(struct snd_soc_component *comp,
+ const char *name)
+{
+ struct q6apm *apm = dev_get_drvdata(comp->dev);
+ struct snd_ar_control *control;
+
+ list_for_each_entry(control, &apm->widget_list, node) {
+ if (control->w && !strcmp(name, control->w->name))
+ return control;
+ }
+
+ return NULL;
+}
+
+static struct audioreach_module *audioreach_find_module(struct snd_soc_component *comp,
+ const char *name)
+{
+ struct q6apm *apm = dev_get_drvdata(comp->dev);
+ struct audioreach_module *module;
+ int id;
+
+ idr_for_each_entry(&apm->modules_idr, module, id) {
+ if (!strcmp(name, module->widget->name))
+ return module;
+ }
+
+ return NULL;
+}
+
+static int audioreach_route_load(struct snd_soc_component *scomp, int index,
+ struct snd_soc_dapm_route *route)
+{
+ const struct audioreach_module *src_module, *sink_module;
+ struct snd_ar_control *control;
+ struct snd_soc_dapm_widget *w;
+ int i;
+
+ /* check if these are actual modules */
+ src_module = audioreach_find_module(scomp, route->source);
+ sink_module = audioreach_find_module(scomp, route->sink);
+
+ if (sink_module && !src_module) {
+ control = audioreach_find_widget(scomp, route->source);
+ if (control)
+ control->module_instance_id = sink_module->instance_id;
+
+ } else if (!sink_module && src_module && route->control) {
+ /* check if this is a virtual mixer */
+ control = audioreach_find_widget(scomp, route->sink);
+ if (!control || !control->w)
+ return 0;
+
+ w = control->w;
+
+ for (i = 0; i < w->num_kcontrols; i++) {
+ if (!strcmp(route->control, w->kcontrol_news[i].name)) {
+ struct soc_mixer_control *sm;
+ struct snd_soc_dobj *dobj;
+ struct snd_ar_control *scontrol;
+
+ sm = (struct soc_mixer_control *)w->kcontrol_news[i].private_value;
+ dobj = &sm->dobj;
+ scontrol = dobj->private;
+ scontrol->module_instance_id = src_module->instance_id;
+ }
+ }
+
+ }
+
+ return 0;
+}
+
+static int audioreach_route_unload(struct snd_soc_component *scomp,
+ struct snd_soc_dobj *dobj)
+{
+ return 0;
+}
+
+static int audioreach_tplg_complete(struct snd_soc_component *component)
+{
+ /* TBD */
+ return 0;
+}
+
+/* DAI link - used for any driver specific init */
+static int audioreach_link_load(struct snd_soc_component *component, int index,
+ struct snd_soc_dai_link *link,
+ struct snd_soc_tplg_link_config *cfg)
+{
+ link->nonatomic = true;
+ link->dynamic = true;
+ link->platforms->name = NULL;
+ link->platforms->of_node = of_get_compatible_child(component->dev->of_node,
+ "qcom,q6apm-dais");
+ return 0;
+}
+
+static void audioreach_connect_sub_graphs(struct q6apm *apm,
+ const struct snd_ar_control *m1,
+ const struct snd_ar_control *m2,
+ bool connect)
+{
+ struct audioreach_graph_info *info;
+
+ mutex_lock(&apm->lock);
+ info = idr_find(&apm->graph_info_idr, m2->graph_id);
+ mutex_unlock(&apm->lock);
+
+ if (connect) {
+ info->src_mod_inst_id = m1->module_instance_id;
+ info->src_mod_op_port_id = 1;
+ info->dst_mod_inst_id = m2->module_instance_id;
+ info->dst_mod_ip_port_id = 2;
+
+ } else {
+ info->src_mod_inst_id = 0;
+ info->src_mod_op_port_id = 0;
+ info->dst_mod_inst_id = 0;
+ info->dst_mod_ip_port_id = 0;
+ }
+}
+
+static bool audioreach_is_vmixer_connected(struct q6apm *apm,
+ const struct snd_ar_control *m1,
+ const struct snd_ar_control *m2)
+{
+ const struct audioreach_graph_info *info;
+
+ mutex_lock(&apm->lock);
+ info = idr_find(&apm->graph_info_idr, m2->graph_id);
+ mutex_unlock(&apm->lock);
+
+ if (info->dst_mod_inst_id == m2->module_instance_id &&
+ info->src_mod_inst_id == m1->module_instance_id)
+ return true;
+
+ return false;
+}
+
+static int audioreach_get_audio_mixer(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
+ const struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_to_widget(kcontrol);
+ struct snd_soc_component *c = snd_soc_dapm_to_component(dapm);
+ const struct snd_ar_control *dapm_scontrol = dw->dobj.private;
+ const struct snd_ar_control *scontrol = mc->dobj.private;
+ struct q6apm *data = dev_get_drvdata(c->dev);
+ bool connected;
+
+ connected = audioreach_is_vmixer_connected(data, scontrol, dapm_scontrol);
+ if (connected)
+ ucontrol->value.integer.value[0] = 1;
+ else
+ ucontrol->value.integer.value[0] = 0;
+
+ return 0;
+}
+
+static int audioreach_put_audio_mixer(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
+ struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_to_widget(kcontrol);
+ struct snd_soc_component *c = snd_soc_dapm_to_component(dapm);
+ const struct snd_ar_control *dapm_scontrol = dw->dobj.private;
+ const struct snd_ar_control *scontrol = mc->dobj.private;
+ struct q6apm *data = dev_get_drvdata(c->dev);
+
+ if (ucontrol->value.integer.value[0]) {
+ audioreach_connect_sub_graphs(data, scontrol, dapm_scontrol, true);
+ snd_soc_dapm_mixer_update_power(dapm, kcontrol, 1, NULL);
+ } else {
+ audioreach_connect_sub_graphs(data, scontrol, dapm_scontrol, false);
+ snd_soc_dapm_mixer_update_power(dapm, kcontrol, 0, NULL);
+ }
+ return 0;
+}
+
+static int audioreach_get_vol_ctrl_audio_mixer(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_to_widget(kcontrol);
+ struct audioreach_module *mod = dw->dobj.private;
+
+ ucontrol->value.integer.value[0] = mod->gain;
+
+ return 0;
+}
+
+static int audioreach_put_vol_ctrl_audio_mixer(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_to_widget(kcontrol);
+ struct audioreach_module *mod = dw->dobj.private;
+
+ mod->gain = ucontrol->value.integer.value[0];
+
+ return 1;
+}
+
+static int audioreach_control_load_mix(struct snd_soc_component *scomp,
+ struct snd_ar_control *scontrol,
+ struct snd_kcontrol_new *kc,
+ const struct snd_soc_tplg_ctl_hdr *hdr)
+{
+ const struct snd_soc_tplg_vendor_value_elem *c_elem;
+ const struct snd_soc_tplg_vendor_array *c_array;
+ const struct snd_soc_tplg_mixer_control *mc;
+ int tkn_count = 0;
+
+ mc = container_of_const(hdr, struct snd_soc_tplg_mixer_control, hdr);
+ c_array = (struct snd_soc_tplg_vendor_array *)mc->priv.data;
+
+ c_elem = c_array->value;
+
+ while (tkn_count <= (le32_to_cpu(c_array->num_elems) - 1)) {
+ switch (le32_to_cpu(c_elem->token)) {
+ case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID:
+ scontrol->sgid = le32_to_cpu(c_elem->value);
+ break;
+ case AR_TKN_DAI_INDEX:
+ scontrol->graph_id = le32_to_cpu(c_elem->value);
+ break;
+ default:
+ /* Ignore other tokens */
+ break;
+ }
+ c_elem++;
+ tkn_count++;
+ }
+
+ return 0;
+}
+
+static int audioreach_control_load(struct snd_soc_component *scomp, int index,
+ struct snd_kcontrol_new *kc,
+ struct snd_soc_tplg_ctl_hdr *hdr)
+{
+ struct snd_ar_control *scontrol;
+ struct soc_mixer_control *sm;
+ struct snd_soc_dobj *dobj;
+ int ret = 0;
+
+ scontrol = kzalloc_obj(*scontrol);
+ if (!scontrol)
+ return -ENOMEM;
+
+ scontrol->scomp = scomp;
+
+ switch (le32_to_cpu(hdr->ops.get)) {
+ case SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX:
+ sm = (struct soc_mixer_control *)kc->private_value;
+ dobj = &sm->dobj;
+ ret = audioreach_control_load_mix(scomp, scontrol, kc, hdr);
+ break;
+ case SND_SOC_AR_TPLG_VOL_CTL:
+ sm = (struct soc_mixer_control *)kc->private_value;
+ dobj = &sm->dobj;
+ break;
+ default:
+ dev_warn(scomp->dev, "control type not supported %d:%d:%d\n",
+ hdr->ops.get, hdr->ops.put, hdr->ops.info);
+ kfree(scontrol);
+ return -EINVAL;
+ }
+
+ dobj->private = scontrol;
+ return ret;
+}
+
+static int audioreach_control_unload(struct snd_soc_component *scomp,
+ struct snd_soc_dobj *dobj)
+{
+ struct snd_ar_control *scontrol = dobj->private;
+
+ kfree(scontrol);
+
+ return 0;
+}
+
+static const struct snd_soc_tplg_kcontrol_ops audioreach_io_ops[] = {
+ {SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX, audioreach_get_audio_mixer,
+ audioreach_put_audio_mixer, snd_soc_info_volsw},
+ {SND_SOC_AR_TPLG_VOL_CTL, audioreach_get_vol_ctrl_audio_mixer,
+ audioreach_put_vol_ctrl_audio_mixer, snd_soc_info_volsw},
+};
+
+static const struct snd_soc_tplg_ops audioreach_tplg_ops = {
+ .io_ops = audioreach_io_ops,
+ .io_ops_count = ARRAY_SIZE(audioreach_io_ops),
+
+ .control_load = audioreach_control_load,
+ .control_unload = audioreach_control_unload,
+
+ .widget_ready = audioreach_widget_ready,
+ .widget_unload = audioreach_widget_unload,
+
+ .complete = audioreach_tplg_complete,
+ .link_load = audioreach_link_load,
+
+ .dapm_route_load = audioreach_route_load,
+ .dapm_route_unload = audioreach_route_unload,
+};
+
+int audioreach_tplg_init(struct snd_soc_component *component)
+{
+ struct snd_soc_card *card = component->card;
+ struct device *dev = component->dev;
+ int ret;
+
+ /* Inline with Qualcomm UCM configs and linux-firmware path */
+ char *tplg_fw_name __free(kfree) = kasprintf(GFP_KERNEL, "qcom/%s/%s-tplg.bin",
+ card->driver_name,
+ card->name);
+ if (!tplg_fw_name)
+ return -ENOMEM;
+
+ const struct firmware *fw __free(firmware) = NULL;
+ ret = request_firmware(&fw, tplg_fw_name, dev);
+ if (ret < 0) {
+ dev_err(dev, "tplg firmware loading %s failed %d\n", tplg_fw_name, ret);
+ return ret;
+ }
+
+ ret = snd_soc_tplg_component_load(component, &audioreach_tplg_ops, fw);
+ if (ret < 0) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "tplg component load failed: %d\n", ret);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(audioreach_tplg_init);
diff --git a/sound/soc/qcom/sc7180.c b/sound/soc/qcom/sc7180.c
new file mode 100644
index 000000000..bc5a18883
--- /dev/null
+++ b/sound/soc/qcom/sc7180.c
@@ -0,0 +1,582 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright (c) 2020, The Linux Foundation. All rights reserved.
+//
+// sc7180.c -- ALSA SoC Machine driver for SC7180
+
+#include <dt-bindings/sound/sc7180-lpass.h>
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <uapi/linux/input-event-codes.h>
+
+#include "../codecs/rt5682.h"
+#include "../codecs/rt5682s.h"
+#include "common.h"
+#include "qdsp6/q6afe.h"
+
+#define DEFAULT_MCLK_RATE 19200000
+#define MI2S_BCLK_RATE 1536000
+#define RT5682_PLL1_FREQ (48000 * 512)
+
+#define DRIVER_NAME "SC7180"
+
+struct sc7180_snd_data {
+ struct snd_soc_card card;
+ u32 pri_mi2s_clk_count;
+ struct snd_soc_jack hs_jack;
+ struct snd_soc_jack hdmi_jack;
+ struct gpio_desc *dmic_sel;
+ int dmic_switch;
+};
+
+static void sc7180_jack_free(struct snd_jack *jack)
+{
+ struct snd_soc_component *component = jack->private_data;
+
+ snd_soc_component_set_jack(component, NULL, NULL);
+}
+
+static struct snd_soc_jack_pin sc7180_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int sc7180_headset_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct sc7180_snd_data *pdata = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ struct snd_jack *jack;
+ int rval;
+
+ rval = snd_soc_card_jack_new_pins(card, "Headset Jack",
+ SND_JACK_HEADSET |
+ SND_JACK_HEADPHONE |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3,
+ &pdata->hs_jack,
+ sc7180_jack_pins,
+ ARRAY_SIZE(sc7180_jack_pins));
+
+ if (rval < 0) {
+ dev_err(card->dev, "Unable to add Headset Jack\n");
+ return rval;
+ }
+
+ jack = pdata->hs_jack.jack;
+
+ snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+ jack->private_data = component;
+ jack->private_free = sc7180_jack_free;
+
+ return snd_soc_component_set_jack(component, &pdata->hs_jack, NULL);
+}
+
+static int sc7180_hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct sc7180_snd_data *pdata = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ struct snd_jack *jack;
+ int rval;
+
+ rval = snd_soc_card_jack_new(
+ card, "HDMI Jack",
+ SND_JACK_LINEOUT,
+ &pdata->hdmi_jack);
+
+ if (rval < 0) {
+ dev_err(card->dev, "Unable to add HDMI Jack\n");
+ return rval;
+ }
+
+ jack = pdata->hdmi_jack.jack;
+ jack->private_data = component;
+ jack->private_free = sc7180_jack_free;
+
+ return snd_soc_component_set_jack(component, &pdata->hdmi_jack, NULL);
+}
+
+static int sc7180_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ switch (cpu_dai->id) {
+ case MI2S_PRIMARY:
+ return sc7180_headset_init(rtd);
+ case MI2S_SECONDARY:
+ return 0;
+ case LPASS_DP_RX:
+ return sc7180_hdmi_init(rtd);
+ default:
+ dev_err(rtd->dev, "%s: invalid dai id 0x%x\n", __func__,
+ cpu_dai->id);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sc7180_qdsp_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ switch (cpu_dai->id) {
+ case PRIMARY_MI2S_RX:
+ return sc7180_headset_init(rtd);
+ case PRIMARY_MI2S_TX:
+ case TERTIARY_MI2S_RX:
+ return 0;
+ case DISPLAY_PORT_RX:
+ return sc7180_hdmi_init(rtd);
+ default:
+ dev_err(rtd->dev, "%s: invalid dai id 0x%x\n", __func__,
+ cpu_dai->id);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sc7180_startup_realtek_codec(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ int pll_id, pll_source, pll_in, pll_out, clk_id, ret;
+
+ if (!strcmp(codec_dai->name, "rt5682-aif1")) {
+ pll_source = RT5682_PLL1_S_MCLK;
+ pll_id = 0;
+ clk_id = RT5682_SCLK_S_PLL1;
+ pll_out = RT5682_PLL1_FREQ;
+ pll_in = DEFAULT_MCLK_RATE;
+ } else if (!strcmp(codec_dai->name, "rt5682s-aif1")) {
+ pll_source = RT5682S_PLL_S_MCLK;
+ pll_id = RT5682S_PLL2;
+ clk_id = RT5682S_SCLK_S_PLL2;
+ pll_out = RT5682_PLL1_FREQ;
+ pll_in = DEFAULT_MCLK_RATE;
+ } else {
+ return 0;
+ }
+ snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_BC_FC |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_I2S);
+
+ /* Configure PLL1 for codec */
+ ret = snd_soc_dai_set_pll(codec_dai, pll_id, pll_source,
+ pll_in, pll_out);
+ if (ret) {
+ dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
+ return ret;
+ }
+
+ /* Configure sysclk for codec */
+ ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, pll_out,
+ SND_SOC_CLOCK_IN);
+ if (ret)
+ dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n",
+ ret);
+
+ return ret;
+}
+
+static int sc7180_snd_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_card *card = rtd->card;
+ struct sc7180_snd_data *data = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ int ret;
+
+ switch (cpu_dai->id) {
+ case MI2S_PRIMARY:
+ if (++data->pri_mi2s_clk_count == 1) {
+ snd_soc_dai_set_sysclk(cpu_dai,
+ LPASS_MCLK0,
+ DEFAULT_MCLK_RATE,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ }
+
+ ret = sc7180_startup_realtek_codec(rtd);
+ if (ret)
+ return ret;
+
+ break;
+ case MI2S_SECONDARY:
+ break;
+ case LPASS_DP_RX:
+ break;
+ default:
+ dev_err(rtd->dev, "%s: invalid dai id 0x%x\n", __func__,
+ cpu_dai->id);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sc7180_qdsp_snd_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_card *card = rtd->card;
+ struct sc7180_snd_data *data = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ switch (cpu_dai->id) {
+ case PRIMARY_MI2S_RX:
+ case PRIMARY_MI2S_TX:
+ if (++data->pri_mi2s_clk_count == 1) {
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_MCLK_1,
+ DEFAULT_MCLK_RATE,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT,
+ MI2S_BCLK_RATE,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ }
+
+ snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP);
+
+ ret = sc7180_startup_realtek_codec(rtd);
+ if (ret)
+ return ret;
+
+ break;
+ case TERTIARY_MI2S_RX:
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_TER_MI2S_IBIT,
+ MI2S_BCLK_RATE,
+ SNDRV_PCM_STREAM_PLAYBACK);
+
+ snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_BC_FC |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_I2S);
+ snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP);
+ break;
+ case DISPLAY_PORT_RX:
+ break;
+ default:
+ dev_err(rtd->dev, "%s: invalid dai id 0x%x\n", __func__,
+ cpu_dai->id);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int dmic_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
+ struct snd_soc_card *card = snd_soc_dapm_to_card(dapm);
+ struct sc7180_snd_data *data = snd_soc_card_get_drvdata(card);
+
+ ucontrol->value.integer.value[0] = data->dmic_switch;
+ return 0;
+}
+
+static int dmic_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
+ struct snd_soc_card *card = snd_soc_dapm_to_card(dapm);
+ struct sc7180_snd_data *data = snd_soc_card_get_drvdata(card);
+
+ data->dmic_switch = ucontrol->value.integer.value[0];
+ gpiod_set_value(data->dmic_sel, data->dmic_switch);
+ return 0;
+}
+
+static void sc7180_snd_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_card *card = rtd->card;
+ struct sc7180_snd_data *data = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ switch (cpu_dai->id) {
+ case MI2S_PRIMARY:
+ if (--data->pri_mi2s_clk_count == 0) {
+ snd_soc_dai_set_sysclk(cpu_dai,
+ LPASS_MCLK0,
+ 0,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ }
+ break;
+ case MI2S_SECONDARY:
+ break;
+ case LPASS_DP_RX:
+ break;
+ default:
+ dev_err(rtd->dev, "%s: invalid dai id 0x%x\n", __func__,
+ cpu_dai->id);
+ break;
+ }
+}
+
+static void sc7180_qdsp_snd_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_card *card = rtd->card;
+ struct sc7180_snd_data *data = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ switch (cpu_dai->id) {
+ case PRIMARY_MI2S_RX:
+ case PRIMARY_MI2S_TX:
+ if (--data->pri_mi2s_clk_count == 0) {
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_MCLK_1,
+ 0,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT,
+ 0,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ }
+ break;
+ case TERTIARY_MI2S_RX:
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_TER_MI2S_IBIT,
+ 0,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ break;
+ case DISPLAY_PORT_RX:
+ break;
+ default:
+ dev_err(rtd->dev, "%s: invalid dai id 0x%x\n", __func__,
+ cpu_dai->id);
+ break;
+ }
+}
+
+static int sc7180_adau7002_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ switch (cpu_dai->id) {
+ case MI2S_PRIMARY:
+ return 0;
+ case MI2S_SECONDARY:
+ return 0;
+ case LPASS_DP_RX:
+ return sc7180_hdmi_init(rtd);
+ default:
+ dev_err(rtd->dev, "%s: invalid dai id 0x%x\n", __func__,
+ cpu_dai->id);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sc7180_adau7002_snd_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ switch (cpu_dai->id) {
+ case MI2S_PRIMARY:
+ snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_CBC_CFC |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_I2S);
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
+ snd_pcm_hw_constraint_msbits(runtime, 0, 32, 32);
+
+ break;
+ case MI2S_SECONDARY:
+ break;
+ case LPASS_DP_RX:
+ break;
+ default:
+ dev_err(rtd->dev, "%s: invalid dai id 0x%x\n", __func__,
+ cpu_dai->id);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sc7180_qdsp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ return 0;
+}
+
+static const struct snd_soc_ops sc7180_ops = {
+ .startup = sc7180_snd_startup,
+ .shutdown = sc7180_snd_shutdown,
+};
+
+static const struct snd_soc_ops sc7180_qdsp_ops = {
+ .startup = sc7180_qdsp_snd_startup,
+ .shutdown = sc7180_qdsp_snd_shutdown,
+};
+
+static const struct snd_soc_ops sc7180_adau7002_ops = {
+ .startup = sc7180_adau7002_snd_startup,
+};
+
+static const struct snd_soc_dapm_widget sc7180_snd_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+};
+
+static const struct snd_kcontrol_new sc7180_snd_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static const struct snd_soc_dapm_widget sc7180_adau7002_snd_widgets[] = {
+ SND_SOC_DAPM_MIC("DMIC", NULL),
+};
+
+static const char * const dmic_mux_text[] = {
+ "Front Mic",
+ "Rear Mic",
+};
+
+static SOC_ENUM_SINGLE_DECL(sc7180_dmic_enum,
+ SND_SOC_NOPM, 0, dmic_mux_text);
+
+static const struct snd_kcontrol_new sc7180_dmic_mux_control =
+ SOC_DAPM_ENUM_EXT("DMIC Select Mux", sc7180_dmic_enum,
+ dmic_get, dmic_set);
+
+static const struct snd_soc_dapm_widget sc7180_snd_dual_mic_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("DMIC", NULL),
+ SND_SOC_DAPM_MUX("Dmic Mux", SND_SOC_NOPM, 0, 0, &sc7180_dmic_mux_control),
+};
+
+static const struct snd_kcontrol_new sc7180_snd_dual_mic_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static const struct snd_soc_dapm_route sc7180_snd_dual_mic_audio_route[] = {
+ {"Dmic Mux", "Front Mic", "DMIC"},
+ {"Dmic Mux", "Rear Mic", "DMIC"},
+};
+
+static int sc7180_snd_platform_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ struct sc7180_snd_data *data;
+ struct device *dev = &pdev->dev;
+ struct snd_soc_dai_link *link;
+ int ret;
+ int i;
+ bool qdsp = false, no_headphone = false;
+
+ /* Allocate the private data */
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ card = &data->card;
+ snd_soc_card_set_drvdata(card, data);
+
+ card->owner = THIS_MODULE;
+ card->driver_name = DRIVER_NAME;
+ card->dev = dev;
+ card->dapm_widgets = sc7180_snd_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(sc7180_snd_widgets);
+ card->controls = sc7180_snd_controls;
+ card->num_controls = ARRAY_SIZE(sc7180_snd_controls);
+
+ if (of_property_present(dev->of_node, "dmic-gpios")) {
+ card->dapm_widgets = sc7180_snd_dual_mic_widgets,
+ card->num_dapm_widgets = ARRAY_SIZE(sc7180_snd_dual_mic_widgets),
+ card->controls = sc7180_snd_dual_mic_controls,
+ card->num_controls = ARRAY_SIZE(sc7180_snd_dual_mic_controls),
+ card->dapm_routes = sc7180_snd_dual_mic_audio_route,
+ card->num_dapm_routes = ARRAY_SIZE(sc7180_snd_dual_mic_audio_route),
+ data->dmic_sel = devm_gpiod_get(&pdev->dev, "dmic", GPIOD_OUT_LOW);
+ if (IS_ERR(data->dmic_sel)) {
+ dev_err(&pdev->dev, "DMIC gpio failed err=%ld\n", PTR_ERR(data->dmic_sel));
+ return PTR_ERR(data->dmic_sel);
+ }
+ }
+
+ if (of_device_is_compatible(dev->of_node, "google,sc7180-coachz")) {
+ no_headphone = true;
+ card->dapm_widgets = sc7180_adau7002_snd_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(sc7180_adau7002_snd_widgets);
+ } else if (of_device_is_compatible(dev->of_node, "qcom,sc7180-qdsp6-sndcard")) {
+ qdsp = true;
+ }
+
+ ret = qcom_snd_parse_of(card);
+ if (ret)
+ return ret;
+
+ for_each_card_prelinks(card, i, link) {
+ if (no_headphone) {
+ link->ops = &sc7180_adau7002_ops;
+ link->init = sc7180_adau7002_init;
+ } else if (qdsp) {
+ if (link->no_pcm == 1) {
+ link->ops = &sc7180_qdsp_ops;
+ link->be_hw_params_fixup = sc7180_qdsp_be_hw_params_fixup;
+ link->init = sc7180_qdsp_init;
+ }
+ } else {
+ link->ops = &sc7180_ops;
+ link->init = sc7180_init;
+ }
+ }
+
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static const struct of_device_id sc7180_snd_device_id[] = {
+ {.compatible = "google,sc7180-trogdor"},
+ {.compatible = "google,sc7180-coachz"},
+ {.compatible = "qcom,sc7180-qdsp6-sndcard"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, sc7180_snd_device_id);
+
+static struct platform_driver sc7180_snd_driver = {
+ .probe = sc7180_snd_platform_probe,
+ .driver = {
+ .name = "msm-snd-sc7180",
+ .of_match_table = sc7180_snd_device_id,
+ .pm = &snd_soc_pm_ops,
+ },
+};
+module_platform_driver(sc7180_snd_driver);
+
+MODULE_DESCRIPTION("sc7180 ASoC Machine Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/sc7280.c b/sound/soc/qcom/sc7280.c
new file mode 100644
index 000000000..d3d8a6e83
--- /dev/null
+++ b/sound/soc/qcom/sc7280.c
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
+//
+// ALSA SoC Machine driver for sc7280
+
+#include <dt-bindings/sound/qcom,lpass.h>
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/rt5682s.h>
+#include <linux/soundwire/sdw.h>
+#include <sound/pcm_params.h>
+
+#include "../codecs/rt5682.h"
+#include "../codecs/rt5682s.h"
+#include "common.h"
+#include "lpass.h"
+#include "qdsp6/q6afe.h"
+#include "sdw.h"
+
+#define DEFAULT_MCLK_RATE 19200000
+#define RT5682_PLL_FREQ (48000 * 512)
+#define MI2S_BCLK_RATE 1536000
+
+struct sc7280_snd_data {
+ struct snd_soc_card card;
+ u32 pri_mi2s_clk_count;
+ struct snd_soc_jack hs_jack;
+ struct snd_soc_jack hdmi_jack;
+ bool jack_setup;
+ bool stream_prepared[LPASS_MAX_PORTS];
+};
+
+static void sc7280_jack_free(struct snd_jack *jack)
+{
+ struct snd_soc_component *component = jack->private_data;
+
+ snd_soc_component_set_jack(component, NULL, NULL);
+}
+
+static struct snd_soc_jack_pin sc7280_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int sc7280_headset_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct sc7280_snd_data *pdata = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ struct snd_jack *jack;
+ int rval, i;
+
+ if (!pdata->jack_setup) {
+ rval = snd_soc_card_jack_new_pins(card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_LINEOUT |
+ SND_JACK_MECHANICAL |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3 |
+ SND_JACK_BTN_4 | SND_JACK_BTN_5,
+ &pdata->hs_jack,
+ sc7280_jack_pins,
+ ARRAY_SIZE(sc7280_jack_pins));
+
+ if (rval < 0) {
+ dev_err(card->dev, "Unable to add Headset Jack\n");
+ return rval;
+ }
+
+ jack = pdata->hs_jack.jack;
+
+ snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+ jack->private_data = component;
+ jack->private_free = sc7280_jack_free;
+ pdata->jack_setup = true;
+ }
+ switch (cpu_dai->id) {
+ case MI2S_PRIMARY:
+ case LPASS_CDC_DMA_RX0:
+ case LPASS_CDC_DMA_TX3:
+ case TX_CODEC_DMA_TX_3:
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ rval = snd_soc_component_set_jack(component, &pdata->hs_jack, NULL);
+ if (rval != 0 && rval != -ENOTSUPP) {
+ dev_err(card->dev, "Failed to set jack: %d\n", rval);
+ return rval;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int sc7280_hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct sc7280_snd_data *pdata = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ struct snd_jack *jack;
+ int rval;
+
+ rval = snd_soc_card_jack_new(card, "HDMI Jack", SND_JACK_LINEOUT,
+ &pdata->hdmi_jack);
+
+ if (rval < 0) {
+ dev_err(card->dev, "Unable to add HDMI Jack\n");
+ return rval;
+ }
+
+ jack = pdata->hdmi_jack.jack;
+ jack->private_data = component;
+ jack->private_free = sc7280_jack_free;
+
+ return snd_soc_component_set_jack(component, &pdata->hdmi_jack, NULL);
+}
+
+static int sc7280_rt5682_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_card *card = rtd->card;
+ struct sc7280_snd_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ if (++data->pri_mi2s_clk_count == 1) {
+ snd_soc_dai_set_sysclk(cpu_dai,
+ LPASS_MCLK0,
+ DEFAULT_MCLK_RATE,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ }
+ snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_CBC_CFC |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_I2S);
+
+ ret = snd_soc_dai_set_pll(codec_dai, RT5682S_PLL2, RT5682S_PLL_S_MCLK,
+ DEFAULT_MCLK_RATE, RT5682_PLL_FREQ);
+ if (ret) {
+ dev_err(rtd->dev, "can't 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) {
+ dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sc7280_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ switch (cpu_dai->id) {
+ case MI2S_PRIMARY:
+ case LPASS_CDC_DMA_TX3:
+ case TX_CODEC_DMA_TX_3:
+ return sc7280_headset_init(rtd);
+ case LPASS_CDC_DMA_RX0:
+ case LPASS_CDC_DMA_VA_TX0:
+ case MI2S_SECONDARY:
+ case RX_CODEC_DMA_RX_0:
+ case SECONDARY_MI2S_RX:
+ case VA_CODEC_DMA_TX_0:
+ return 0;
+ case LPASS_DP_RX:
+ return sc7280_hdmi_init(rtd);
+ default:
+ dev_err(rtd->dev, "%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
+ }
+
+ return -EINVAL;
+}
+
+static int sc7280_snd_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+
+ if (!rtd->dai_link->no_pcm) {
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2);
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, 48000, 48000);
+ }
+
+ return 0;
+}
+
+static int sc7280_snd_swr_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ const struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct sc7280_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+
+ return qcom_snd_sdw_prepare(substream, &data->stream_prepared[cpu_dai->id]);
+}
+
+static int sc7280_snd_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ const struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ switch (cpu_dai->id) {
+ case LPASS_CDC_DMA_RX0:
+ case LPASS_CDC_DMA_TX3:
+ case RX_CODEC_DMA_RX_0:
+ case TX_CODEC_DMA_TX_3:
+ case VA_CODEC_DMA_TX_0:
+ return sc7280_snd_swr_prepare(substream);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int sc7280_snd_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sc7280_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ const struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ return qcom_snd_sdw_hw_free(substream, &data->stream_prepared[cpu_dai->id]);
+}
+
+static void sc7280_snd_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_card *card = rtd->card;
+ struct sc7280_snd_data *data = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ switch (cpu_dai->id) {
+ case MI2S_PRIMARY:
+ if (--data->pri_mi2s_clk_count == 0) {
+ snd_soc_dai_set_sysclk(cpu_dai,
+ LPASS_MCLK0,
+ 0,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ }
+ break;
+ case SECONDARY_MI2S_RX:
+ snd_soc_dai_set_sysclk(cpu_dai, Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT,
+ 0, SNDRV_PCM_STREAM_PLAYBACK);
+ break;
+ default:
+ break;
+ }
+
+ qcom_snd_sdw_shutdown(substream);
+}
+
+static int sc7280_snd_startup(struct snd_pcm_substream *substream)
+{
+ unsigned int fmt = SND_SOC_DAIFMT_CBC_CFC;
+ unsigned int codec_dai_fmt = SND_SOC_DAIFMT_CBC_CFC;
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ int ret = 0;
+
+ switch (cpu_dai->id) {
+ case MI2S_PRIMARY:
+ ret = sc7280_rt5682_init(rtd);
+ if (ret)
+ return ret;
+ break;
+ case SECONDARY_MI2S_RX:
+ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S;
+
+ snd_soc_dai_set_sysclk(cpu_dai, Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT,
+ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+
+ snd_soc_dai_set_fmt(cpu_dai, fmt);
+ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
+ break;
+ default:
+ break;
+ }
+
+ return qcom_snd_sdw_startup(substream);
+}
+
+static const struct snd_soc_ops sc7280_ops = {
+ .startup = sc7280_snd_startup,
+ .hw_params = sc7280_snd_hw_params,
+ .hw_free = sc7280_snd_hw_free,
+ .prepare = sc7280_snd_prepare,
+ .shutdown = sc7280_snd_shutdown,
+};
+
+static const struct snd_soc_dapm_widget sc7280_snd_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+};
+
+static const struct snd_kcontrol_new sc7280_snd_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static int sc7280_snd_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+
+ return 0;
+}
+
+static int sc7280_snd_platform_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ struct sc7280_snd_data *data;
+ struct device *dev = &pdev->dev;
+ struct snd_soc_dai_link *link;
+ int ret, i;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ card = &data->card;
+ snd_soc_card_set_drvdata(card, data);
+
+ card->owner = THIS_MODULE;
+ card->driver_name = "SC7280";
+ card->dev = dev;
+
+ card->dapm_widgets = sc7280_snd_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(sc7280_snd_widgets);
+ card->controls = sc7280_snd_controls;
+ card->num_controls = ARRAY_SIZE(sc7280_snd_controls);
+
+ ret = qcom_snd_parse_of(card);
+ if (ret)
+ return ret;
+
+ for_each_card_prelinks(card, i, link) {
+ link->init = sc7280_init;
+ link->ops = &sc7280_ops;
+ if (link->no_pcm == 1)
+ link->be_hw_params_fixup = sc7280_snd_be_hw_params_fixup;
+ }
+
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static const struct of_device_id sc7280_snd_device_id[] = {
+ { .compatible = "google,sc7280-herobrine" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sc7280_snd_device_id);
+
+static struct platform_driver sc7280_snd_driver = {
+ .probe = sc7280_snd_platform_probe,
+ .driver = {
+ .name = "msm-snd-sc7280",
+ .of_match_table = sc7280_snd_device_id,
+ .pm = &snd_soc_pm_ops,
+ },
+};
+module_platform_driver(sc7280_snd_driver);
+
+MODULE_DESCRIPTION("sc7280 ASoC Machine Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c
new file mode 100644
index 000000000..4d48e1012
--- /dev/null
+++ b/sound/soc/qcom/sc8280xp.c
@@ -0,0 +1,594 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2022, Linaro Limited
+
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <linux/soundwire/sdw.h>
+#include <sound/jack.h>
+#include <linux/input-event-codes.h>
+#include "qdsp6/q6afe.h"
+#include "qdsp6/q6apm.h"
+#include "qdsp6/q6prm.h"
+#include "qdsp6/q6dsp-common.h"
+#include "common.h"
+#include "sdw.h"
+
+#define I2S_MCLKFS 256
+
+#define I2S_MCLK_RATE(rate) \
+ ((rate) * (I2S_MCLKFS))
+#define I2S_BIT_RATE(rate, channels, format) \
+ ((rate) * (channels) * (format))
+
+static struct snd_soc_dapm_widget sc8280xp_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_SPK("DP0 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP1 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP2 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP3 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP4 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP5 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP6 Jack", NULL),
+ SND_SOC_DAPM_SPK("DP7 Jack", NULL),
+};
+
+static const struct snd_kcontrol_new max98090_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headset Mic12"),
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic56"),
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("Receiver"),
+ SOC_DAPM_PIN_SWITCH("Int Mic"),
+};
+
+static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic12", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic56", NULL),
+ SND_SOC_DAPM_MIC("Int Mic", NULL),
+ SND_SOC_DAPM_SPK("Receiver", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+struct qcom_snd_soc_common {
+ const char *driver_name;
+ const struct snd_soc_dapm_widget *dapm_widgets;
+ int num_dapm_widgets;
+ const struct snd_soc_dapm_route *dapm_routes;
+ int num_dapm_routes;
+ const struct snd_kcontrol_new *controls;
+ int num_controls;
+ unsigned int codec_dai_fmt;
+ bool codec_sysclk_set;
+ bool mi2s_mclk_enable;
+ bool mi2s_bclk_enable;
+ bool wcd_jack;
+ int (*snd_prepare)(struct snd_pcm_substream *substream);
+};
+
+struct sc8280xp_snd_data {
+ bool stream_prepared[AFE_PORT_MAX];
+ struct snd_soc_card *card;
+ struct snd_soc_jack jack;
+ struct snd_soc_jack dp_jack[8];
+ const struct qcom_snd_soc_common *priv;
+ bool jack_setup;
+};
+
+static inline int sc8280xp_get_mclk_freq(struct snd_pcm_hw_params *params)
+{
+ int rate = params_rate(params);
+
+ switch (rate) {
+ case 11025:
+ case 44100:
+ case 88200:
+ return I2S_MCLK_RATE(44100);
+ default:
+ break;
+ }
+
+ return I2S_MCLK_RATE(rate);
+}
+
+static inline int sc8280xp_get_bclk_freq(struct snd_pcm_hw_params *params)
+{
+ return I2S_BIT_RATE(params_rate(params),
+ params_channels(params),
+ snd_pcm_format_width(params_format(params)));
+}
+
+static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai;
+ struct qcom_snd_tdm_slot_cfg cpu_cfg;
+ struct qcom_snd_tdm_slot_cfg codec_cfg;
+ int bclk_freq;
+ int ret;
+ int i;
+
+ ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg);
+ if (ret)
+ return ret == -ENOENT ? 0 : ret;
+
+ if (!cpu_cfg.slots)
+ return 0;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP);
+ if (ret && ret != -ENOTSUPP)
+ return ret;
+
+ if (data->priv->codec_dai_fmt) {
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ data->priv->codec_dai_fmt);
+ if (ret && ret != -ENOTSUPP)
+ return ret;
+ }
+ }
+
+ ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg);
+ if (ret)
+ return ret;
+
+ bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1);
+ if (bclk_freq <= 0)
+ return -EINVAL;
+
+ if (data->priv->mi2s_bclk_enable) {
+ ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq,
+ SND_SOC_CLOCK_IN);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n",
+ __func__, ret);
+ return ret;
+ }
+ }
+
+ if (data->priv->codec_sysclk_set) {
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq,
+ SND_SOC_CLOCK_IN);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n",
+ __func__, codec_dai->name, ret);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_jack *dp_jack = NULL;
+ int dp_pcm_id = 0;
+
+ switch (cpu_dai->id) {
+ case WSA_CODEC_DMA_RX_0:
+ case WSA_CODEC_DMA_RX_1:
+ /*
+ * Set limit of -3 dB on Digital Volume and 0 dB on PA Volume
+ * to reduce the risk of speaker damage until we have active
+ * speaker protection in place.
+ */
+ snd_soc_limit_volume(card, "WSA_RX0 Digital Volume", 81);
+ snd_soc_limit_volume(card, "WSA_RX1 Digital Volume", 81);
+ snd_soc_limit_volume(card, "SpkrLeft PA Volume", 17);
+ snd_soc_limit_volume(card, "SpkrRight PA Volume", 17);
+ break;
+ case DISPLAY_PORT_RX_0:
+ /* DISPLAY_PORT dai ids are not contiguous */
+ dp_pcm_id = 0;
+ dp_jack = &data->dp_jack[dp_pcm_id];
+ break;
+ case DISPLAY_PORT_RX_1 ... DISPLAY_PORT_RX_7:
+ dp_pcm_id = cpu_dai->id - DISPLAY_PORT_RX_1 + 1;
+ dp_jack = &data->dp_jack[dp_pcm_id];
+ break;
+ default:
+ break;
+ }
+
+ if (dp_jack)
+ return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id);
+
+ if (data->priv->wcd_jack)
+ return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup);
+
+ return 0;
+}
+
+static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ rate->min = rate->max = 48000;
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+ channels->min = 2;
+ channels->max = 2;
+ switch (cpu_dai->id) {
+ case TX_CODEC_DMA_TX_0:
+ case TX_CODEC_DMA_TX_1:
+ case TX_CODEC_DMA_TX_2:
+ case TX_CODEC_DMA_TX_3:
+ channels->min = 1;
+ break;
+ default:
+ break;
+ }
+
+
+ return 0;
+}
+
+static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ int mclk_freq = sc8280xp_get_mclk_freq(params);
+ int bclk_freq = sc8280xp_get_bclk_freq(params);
+ int ret;
+
+ switch (cpu_dai->id) {
+ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX:
+ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX:
+ case SENARY_MI2S_RX ... SENARY_MI2S_TX:
+ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4:
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP);
+ if (ret && ret != -ENOTSUPP)
+ return ret;
+
+ if (data->priv->codec_dai_fmt) {
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ data->priv->codec_dai_fmt);
+ if (ret && ret != -ENOTSUPP)
+ return ret;
+ }
+
+ if (data->priv->mi2s_mclk_enable) {
+ ret = snd_soc_dai_set_sysclk(cpu_dai,
+ LPAIF_MI2S_MCLK, mclk_freq,
+ SND_SOC_CLOCK_OUT);
+ if (ret)
+ return ret;
+ }
+
+ if (data->priv->mi2s_bclk_enable) {
+ ret = snd_soc_dai_set_sysclk(cpu_dai,
+ LPAIF_MI2S_BCLK, bclk_freq,
+ SND_SOC_CLOCK_OUT);
+ if (ret)
+ return ret;
+ }
+
+ if (data->priv->codec_sysclk_set) {
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ 0, mclk_freq,
+ SND_SOC_CLOCK_IN);
+ if (ret && ret != -ENOTSUPP)
+ return ret;
+ }
+ break;
+ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7:
+ return sc8280xp_tdm_hw_params(substream, params);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * WSA and WSA2 are handled as a single interface with the
+ * following channels mask:
+ * __________________________________________________
+ * | Bits | 3 | 2 | 1 | 0 |
+ * ---------------------------------------------------
+ * | Line | WSA2 Ch2 | WSA2 Ch1 | WSA Ch2 | WSA Ch1 |
+ * ---------------------------------------------------
+ *
+ * The Ayaneo Pocket S2 speakers are connected only to
+ * the WSA2 interface and the WSA interface is not enabled.
+ *
+ * Set the channel mapping on the WSA2 channels only.
+ */
+static const unsigned int ayaneo_ps2_channels_mapping[] = {
+ 0, /* WSA Ch1 */
+ 0, /* WSA Ch2 */
+ PCM_CHANNEL_FL, /* WSA2 Ch1 */
+ PCM_CHANNEL_FR /* WSA2 Ch2 */
+};
+
+static int ayaneo_ps2_snd_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ unsigned int channels = substream->runtime->channels;
+
+ if (cpu_dai->id != WSA_CODEC_DMA_RX_0)
+ return 0;
+
+ if (channels != 2)
+ return -EINVAL;
+
+ return snd_soc_dai_set_channel_map(cpu_dai, 0, NULL,
+ ARRAY_SIZE(ayaneo_ps2_channels_mapping),
+ ayaneo_ps2_channels_mapping);
+}
+
+static int sc8280xp_snd_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+
+ if (data->priv->snd_prepare) {
+ int ret;
+
+ ret = data->priv->snd_prepare(substream);
+ if (ret)
+ return ret;
+ }
+
+ return qcom_snd_sdw_prepare(substream, &data->stream_prepared[cpu_dai->id]);
+}
+
+static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ return qcom_snd_sdw_hw_free(substream, &data->stream_prepared[cpu_dai->id]);
+}
+
+static const struct snd_soc_ops sc8280xp_be_ops = {
+ .startup = qcom_snd_sdw_startup,
+ .shutdown = qcom_snd_sdw_shutdown,
+ .hw_params = sc8280xp_snd_hw_params,
+ .hw_free = sc8280xp_snd_hw_free,
+ .prepare = sc8280xp_snd_prepare,
+};
+
+static void sc8280xp_add_be_ops(struct snd_soc_card *card)
+{
+ struct snd_soc_dai_link *link;
+ int i;
+
+ for_each_card_prelinks(card, i, link) {
+ if (link->no_pcm == 1) {
+ link->init = sc8280xp_snd_init;
+ link->be_hw_params_fixup = sc8280xp_be_hw_params_fixup;
+ link->ops = &sc8280xp_be_ops;
+ }
+ }
+}
+
+static int sc8280xp_platform_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ struct sc8280xp_snd_data *data;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+
+ /* Allocate the private data */
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->priv = of_device_get_match_data(dev);
+ if (!data->priv)
+ return -ENODEV;
+
+ card->owner = THIS_MODULE;
+ card->dev = dev;
+ dev_set_drvdata(dev, card);
+ snd_soc_card_set_drvdata(card, data);
+ card->dapm_widgets = data->priv->dapm_widgets;
+ card->num_dapm_widgets = data->priv->num_dapm_widgets;
+ card->dapm_routes = data->priv->dapm_routes;
+ card->num_dapm_routes = data->priv->num_dapm_routes;
+ card->controls = data->priv->controls;
+ card->num_controls = data->priv->num_controls;
+
+ ret = qcom_snd_parse_of(card);
+ if (ret)
+ return ret;
+
+ card->driver_name = data->priv->driver_name;
+ sc8280xp_add_be_ops(card);
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static struct qcom_snd_soc_common ayaneo_ps2_priv_data = {
+ .driver_name = "ayaneo-ps2",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .snd_prepare = ayaneo_ps2_snd_prepare,
+ .wcd_jack = true,
+};
+
+static const struct qcom_snd_soc_common eliza_priv_data = {
+ .driver_name = "eliza",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .wcd_jack = true,
+};
+
+static const struct qcom_snd_soc_common hawi_priv_data = {
+ .driver_name = "hawi",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .codec_sysclk_set = true,
+ .mi2s_bclk_enable = true,
+ .wcd_jack = true,
+};
+
+static const struct qcom_snd_soc_common kaanapali_priv_data = {
+ .driver_name = "kaanapali",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .wcd_jack = true,
+};
+
+static const struct qcom_snd_soc_common qcs9100_priv_data = {
+ .driver_name = "sa8775p",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+};
+
+static const struct qcom_snd_soc_common qcs615_priv_data = {
+ .driver_name = "qcs615",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .codec_sysclk_set = true,
+};
+
+static const struct qcom_snd_soc_common qcm6490_priv_data = {
+ .driver_name = "qcm6490",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .wcd_jack = true,
+};
+
+static const struct qcom_snd_soc_common qcs6490_priv_data = {
+ .driver_name = "qcs6490",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .wcd_jack = true,
+};
+
+static const struct qcom_snd_soc_common qcs8275_priv_data = {
+ .driver_name = "qcs8300",
+ .dapm_widgets = max98090_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(max98090_dapm_widgets),
+ .controls = max98090_controls,
+ .num_controls = ARRAY_SIZE(max98090_controls),
+ .codec_sysclk_set = true,
+ .codec_dai_fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_BC_FC,
+};
+
+static const struct qcom_snd_soc_common sc8280xp_priv_data = {
+ .driver_name = "sc8280xp",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .wcd_jack = true,
+};
+
+static const struct qcom_snd_soc_common sm8450_priv_data = {
+ .driver_name = "sm8450",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .wcd_jack = true,
+ /* I2S Connected to HDMI */
+ .mi2s_mclk_enable = true,
+ .mi2s_bclk_enable = true,
+ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_I2S,
+};
+
+static const struct qcom_snd_soc_common sm8475_priv_data = {
+ .driver_name = "sm8475",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .wcd_jack = true,
+};
+
+static const struct qcom_snd_soc_common sm8550_priv_data = {
+ .driver_name = "sm8550",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .wcd_jack = true,
+ /* I2S Connected to HDMI */
+ .mi2s_mclk_enable = true,
+ .mi2s_bclk_enable = true,
+ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_I2S,
+};
+
+static const struct qcom_snd_soc_common sm8650_priv_data = {
+ .driver_name = "sm8650",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .wcd_jack = true,
+ /* I2S Connected to HDMI */
+ .mi2s_mclk_enable = true,
+ .mi2s_bclk_enable = true,
+ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_I2S,
+};
+
+static const struct qcom_snd_soc_common sm8750_priv_data = {
+ .driver_name = "sm8750",
+ .dapm_widgets = sc8280xp_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
+ .wcd_jack = true,
+};
+
+static const struct of_device_id snd_sc8280xp_dt_match[] = {
+ { .compatible = "ayaneo,pocket-s2-sndcard", .data = &ayaneo_ps2_priv_data },
+ { .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data },
+ { .compatible = "qcom,hawi-sndcard", .data = &hawi_priv_data },
+ { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data },
+ { .compatible = "qcom,maili-sndcard", .data = &hawi_priv_data },
+ { .compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data },
+ { .compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data },
+ { .compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data },
+ { .compatible = "qcom,qcs8275-sndcard", .data = &qcs8275_priv_data },
+ { .compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data },
+ { .compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data },
+ { .compatible = "qcom,sc8280xp-sndcard", .data = &sc8280xp_priv_data },
+ { .compatible = "qcom,sm8450-sndcard", .data = &sm8450_priv_data },
+ { .compatible = "qcom,sm8475-sndcard", .data = &sm8475_priv_data },
+ { .compatible = "qcom,sm8550-sndcard", .data = &sm8550_priv_data },
+ { .compatible = "qcom,sm8650-sndcard", .data = &sm8650_priv_data },
+ { .compatible = "qcom,sm8750-sndcard", .data = &sm8750_priv_data },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, snd_sc8280xp_dt_match);
+
+static struct platform_driver snd_sc8280xp_driver = {
+ .probe = sc8280xp_platform_probe,
+ .driver = {
+ .name = "snd-sc8280xp",
+ .of_match_table = snd_sc8280xp_dt_match,
+ },
+};
+module_platform_driver(snd_sc8280xp_driver);
+MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org");
+MODULE_DESCRIPTION("SC8280XP ASoC Machine Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c
new file mode 100644
index 000000000..6843ab8ba
--- /dev/null
+++ b/sound/soc/qcom/sdm845.c
@@ -0,0 +1,591 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ */
+
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <linux/soundwire/sdw.h>
+#include <uapi/linux/input-event-codes.h>
+#include "common.h"
+#include "qdsp6/q6afe.h"
+#include "sdw.h"
+#include "../codecs/rt5663.h"
+
+#define DRIVER_NAME "sdm845"
+#define DEFAULT_SAMPLE_RATE_48K 48000
+#define DEFAULT_MCLK_RATE 24576000
+#define TDM_BCLK_RATE 6144000
+#define MI2S_BCLK_RATE 1536000
+#define LEFT_SPK_TDM_TX_MASK 0x30
+#define RIGHT_SPK_TDM_TX_MASK 0xC0
+#define SPK_TDM_RX_MASK 0x03
+#define NUM_TDM_SLOTS 8
+#define SLIM_MAX_TX_PORTS 16
+#define SLIM_MAX_RX_PORTS 13
+#define WCD934X_DEFAULT_MCLK_RATE 9600000
+
+struct sdm845_snd_data {
+ struct snd_soc_jack jack;
+ bool jack_setup;
+ bool slim_port_setup;
+ bool stream_prepared[AFE_PORT_MAX];
+ uint32_t pri_mi2s_clk_count;
+ uint32_t sec_mi2s_clk_count;
+ uint32_t quat_tdm_clk_count;
+};
+
+static struct snd_soc_jack_pin sdm845_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static unsigned int tdm_slot_offset[8] = {0, 4, 8, 12, 16, 20, 24, 28};
+
+static int sdm845_slim_snd_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai;
+ u32 rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS];
+ u32 rx_ch_cnt = 0, tx_ch_cnt = 0;
+ int ret = 0, i;
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ ret = snd_soc_dai_get_channel_map(codec_dai,
+ &tx_ch_cnt, tx_ch, &rx_ch_cnt, rx_ch);
+
+ if (ret != 0 && ret != -ENOTSUPP) {
+ pr_err("failed to get codec chan map, err:%d\n", ret);
+ return ret;
+ } else if (ret == -ENOTSUPP) {
+ /* Ignore unsupported */
+ continue;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = snd_soc_dai_set_channel_map(cpu_dai, 0, NULL,
+ rx_ch_cnt, rx_ch);
+ else
+ ret = snd_soc_dai_set_channel_map(cpu_dai, tx_ch_cnt,
+ tx_ch, 0, NULL);
+ if (ret != 0 && ret != -ENOTSUPP) {
+ dev_err(rtd->dev, "failed to set cpu chan map, err:%d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai;
+ int ret = 0, j;
+ int channels, slot_width;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ slot_width = 16;
+ break;
+ default:
+ dev_err(rtd->dev, "%s: invalid param format 0x%x\n",
+ __func__, params_format(params));
+ return -EINVAL;
+ }
+
+ channels = params_channels(params);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0x3,
+ 8, slot_width);
+ if (ret < 0) {
+ dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n",
+ __func__, ret);
+ goto end;
+ }
+
+ ret = snd_soc_dai_set_channel_map(cpu_dai, 0, NULL,
+ channels, tdm_slot_offset);
+ if (ret < 0) {
+ dev_err(rtd->dev, "%s: failed to set channel map, err:%d\n",
+ __func__, ret);
+ goto end;
+ }
+ } else {
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0xf, 0,
+ 8, slot_width);
+ if (ret < 0) {
+ dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n",
+ __func__, ret);
+ goto end;
+ }
+
+ ret = snd_soc_dai_set_channel_map(cpu_dai, channels,
+ tdm_slot_offset, 0, NULL);
+ if (ret < 0) {
+ dev_err(rtd->dev, "%s: failed to set channel map, err:%d\n",
+ __func__, ret);
+ goto end;
+ }
+ }
+
+ for_each_rtd_codec_dais(rtd, j, codec_dai) {
+
+ if (!strcmp(codec_dai->component->name_prefix, "Left")) {
+ ret = snd_soc_dai_set_tdm_slot(
+ codec_dai, LEFT_SPK_TDM_TX_MASK,
+ SPK_TDM_RX_MASK, NUM_TDM_SLOTS,
+ slot_width);
+ if (ret < 0) {
+ dev_err(rtd->dev,
+ "DEV0 TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+
+ if (!strcmp(codec_dai->component->name_prefix, "Right")) {
+ ret = snd_soc_dai_set_tdm_slot(
+ codec_dai, RIGHT_SPK_TDM_TX_MASK,
+ SPK_TDM_RX_MASK, NUM_TDM_SLOTS,
+ slot_width);
+ if (ret < 0) {
+ dev_err(rtd->dev,
+ "DEV1 TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+ }
+
+end:
+ return ret;
+}
+
+static int sdm845_snd_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ int ret = 0;
+
+ switch (cpu_dai->id) {
+ case PRIMARY_MI2S_RX:
+ case PRIMARY_MI2S_TX:
+ /*
+ * Use ASRC for internal clocks, as PLL rate isn't multiple
+ * of BCLK.
+ */
+ rt5663_sel_asrc_clk_src(
+ codec_dai->component,
+ RT5663_DA_STEREO_FILTER | RT5663_AD_STEREO_FILTER,
+ RT5663_CLK_SEL_I2S1_ASRC);
+ ret = snd_soc_dai_set_sysclk(
+ codec_dai, RT5663_SCLK_S_MCLK, DEFAULT_MCLK_RATE,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(rtd->dev,
+ "snd_soc_dai_set_sysclk err = %d\n", ret);
+ break;
+ case QUATERNARY_TDM_RX_0:
+ case QUATERNARY_TDM_TX_0:
+ ret = sdm845_tdm_snd_hw_params(substream, params);
+ break;
+ case SLIMBUS_0_RX...SLIMBUS_6_TX:
+ ret = sdm845_slim_snd_hw_params(substream, params);
+ break;
+ case QUATERNARY_MI2S_RX:
+ case SECONDARY_MI2S_RX:
+ break;
+ default:
+ pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
+ break;
+ }
+ return ret;
+}
+
+static void sdm845_jack_free(struct snd_jack *jack)
+{
+ struct snd_soc_component *component = jack->private_data;
+
+ snd_soc_component_set_jack(component, NULL, NULL);
+}
+
+static int sdm845_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *component;
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct sdm845_snd_data *pdata = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai_link *link = rtd->dai_link;
+ struct snd_jack *jack;
+ /*
+ * Codec SLIMBUS configuration
+ * RX1, RX2, RX3, RX4, RX5, RX6, RX7, RX8, RX9, RX10, RX11, RX12, RX13
+ * TX1, TX2, TX3, TX4, TX5, TX6, TX7, TX8, TX9, TX10, TX11, TX12, TX13
+ * TX14, TX15, TX16
+ */
+ unsigned int rx_ch[SLIM_MAX_RX_PORTS] = {144, 145, 146, 147, 148, 149,
+ 150, 151, 152, 153, 154, 155, 156};
+ unsigned int tx_ch[SLIM_MAX_TX_PORTS] = {128, 129, 130, 131, 132, 133,
+ 134, 135, 136, 137, 138, 139,
+ 140, 141, 142, 143};
+ int rval, i;
+
+
+ if (!pdata->jack_setup) {
+ rval = snd_soc_card_jack_new_pins(card, "Headset Jack",
+ SND_JACK_HEADSET |
+ SND_JACK_HEADPHONE |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3,
+ &pdata->jack,
+ sdm845_jack_pins,
+ ARRAY_SIZE(sdm845_jack_pins));
+
+ if (rval < 0) {
+ dev_err(card->dev, "Unable to add Headphone Jack\n");
+ return rval;
+ }
+
+ jack = pdata->jack.jack;
+
+ snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+ pdata->jack_setup = true;
+ }
+
+ switch (cpu_dai->id) {
+ case PRIMARY_MI2S_RX:
+ jack = pdata->jack.jack;
+ component = codec_dai->component;
+
+ jack->private_data = component;
+ jack->private_free = sdm845_jack_free;
+ rval = snd_soc_component_set_jack(component,
+ &pdata->jack, NULL);
+ if (rval != 0 && rval != -ENOTSUPP) {
+ dev_warn(card->dev, "Failed to set jack: %d\n", rval);
+ return rval;
+ }
+ break;
+ case SLIMBUS_0_RX...SLIMBUS_6_TX:
+ /* setting up wcd multiple times for slim port is redundant */
+ if (pdata->slim_port_setup || !link->no_pcm)
+ return 0;
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ rval = snd_soc_dai_set_channel_map(codec_dai,
+ ARRAY_SIZE(tx_ch),
+ tx_ch,
+ ARRAY_SIZE(rx_ch),
+ rx_ch);
+ if (rval != 0 && rval != -ENOTSUPP)
+ return rval;
+
+ snd_soc_dai_set_sysclk(codec_dai, 0,
+ WCD934X_DEFAULT_MCLK_RATE,
+ SNDRV_PCM_STREAM_PLAYBACK);
+
+ rval = snd_soc_component_set_jack(codec_dai->component,
+ &pdata->jack, NULL);
+ if (rval != 0 && rval != -ENOTSUPP) {
+ dev_warn(card->dev, "Failed to set jack: %d\n", rval);
+ return rval;
+ }
+ }
+
+ pdata->slim_port_setup = true;
+
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+
+static int sdm845_snd_startup(struct snd_pcm_substream *substream)
+{
+ unsigned int fmt = SND_SOC_DAIFMT_BP_FP;
+ unsigned int codec_dai_fmt = SND_SOC_DAIFMT_BC_FC;
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_card *card = rtd->card;
+ struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ int j;
+ int ret;
+
+ switch (cpu_dai->id) {
+ case PRIMARY_MI2S_RX:
+ case PRIMARY_MI2S_TX:
+ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF;
+ if (++(data->pri_mi2s_clk_count) == 1) {
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_MCLK_1,
+ DEFAULT_MCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT,
+ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+ }
+ snd_soc_dai_set_fmt(cpu_dai, fmt);
+ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
+ break;
+
+ case SECONDARY_MI2S_RX:
+ case SECONDARY_MI2S_TX:
+ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S;
+ if (++(data->sec_mi2s_clk_count) == 1) {
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT,
+ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_CAPTURE);
+ }
+ snd_soc_dai_set_fmt(cpu_dai, fmt);
+ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
+ break;
+ case QUATERNARY_MI2S_RX:
+ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S;
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_QUAD_MI2S_IBIT,
+ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+ snd_soc_dai_set_fmt(cpu_dai, fmt);
+ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
+ break;
+
+ case QUATERNARY_TDM_RX_0:
+ case QUATERNARY_TDM_TX_0:
+ if (++(data->quat_tdm_clk_count) == 1) {
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT,
+ TDM_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+ }
+
+ codec_dai_fmt |= SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_DSP_B;
+
+ for_each_rtd_codec_dais(rtd, j, codec_dai) {
+
+ if (!strcmp(codec_dai->component->name_prefix,
+ "Left")) {
+ ret = snd_soc_dai_set_fmt(
+ codec_dai, codec_dai_fmt);
+ if (ret < 0) {
+ dev_err(rtd->dev,
+ "Left TDM fmt err:%d\n", ret);
+ return ret;
+ }
+ }
+
+ if (!strcmp(codec_dai->component->name_prefix,
+ "Right")) {
+ ret = snd_soc_dai_set_fmt(
+ codec_dai, codec_dai_fmt);
+ if (ret < 0) {
+ dev_err(rtd->dev,
+ "Right TDM slot err:%d\n", ret);
+ return ret;
+ }
+ }
+ }
+ break;
+ case SLIMBUS_0_RX...SLIMBUS_6_TX:
+ break;
+
+ default:
+ pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
+ break;
+ }
+ return qcom_snd_sdw_startup(substream);
+}
+
+static void sdm845_snd_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_card *card = rtd->card;
+ struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ switch (cpu_dai->id) {
+ case PRIMARY_MI2S_RX:
+ case PRIMARY_MI2S_TX:
+ if (--(data->pri_mi2s_clk_count) == 0) {
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_MCLK_1,
+ 0, SNDRV_PCM_STREAM_PLAYBACK);
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT,
+ 0, SNDRV_PCM_STREAM_PLAYBACK);
+ }
+ break;
+
+ case SECONDARY_MI2S_RX:
+ case SECONDARY_MI2S_TX:
+ if (--(data->sec_mi2s_clk_count) == 0) {
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT,
+ 0, SNDRV_PCM_STREAM_CAPTURE);
+ }
+ break;
+
+ case QUATERNARY_TDM_RX_0:
+ case QUATERNARY_TDM_TX_0:
+ if (--(data->quat_tdm_clk_count) == 0) {
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT,
+ 0, SNDRV_PCM_STREAM_PLAYBACK);
+ }
+ break;
+ case SLIMBUS_0_RX...SLIMBUS_6_TX:
+ case QUATERNARY_MI2S_RX:
+ break;
+
+ default:
+ pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
+ break;
+ }
+
+ qcom_snd_sdw_shutdown(substream);
+}
+
+static int sdm845_snd_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sdm845_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ return qcom_snd_sdw_prepare(substream, &data->stream_prepared[cpu_dai->id]);
+}
+
+static int sdm845_snd_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sdm845_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ return qcom_snd_sdw_hw_free(substream, &data->stream_prepared[cpu_dai->id]);
+}
+
+static const struct snd_soc_ops sdm845_be_ops = {
+ .hw_params = sdm845_snd_hw_params,
+ .hw_free = sdm845_snd_hw_free,
+ .prepare = sdm845_snd_prepare,
+ .startup = sdm845_snd_startup,
+ .shutdown = sdm845_snd_shutdown,
+};
+
+static int sdm845_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ rate->min = rate->max = DEFAULT_SAMPLE_RATE_48K;
+ channels->min = channels->max = 2;
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget sdm845_snd_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_SPK("Left Spk", NULL),
+ SND_SOC_DAPM_SPK("Right Spk", NULL),
+ SND_SOC_DAPM_MIC("Int Mic", NULL),
+};
+
+static const struct snd_kcontrol_new sdm845_snd_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static void sdm845_add_ops(struct snd_soc_card *card)
+{
+ struct snd_soc_dai_link *link;
+ int i;
+
+ for_each_card_prelinks(card, i, link) {
+ if (link->no_pcm == 1) {
+ link->ops = &sdm845_be_ops;
+ link->be_hw_params_fixup = sdm845_be_hw_params_fixup;
+ }
+ link->init = sdm845_dai_init;
+ }
+}
+
+static int sdm845_snd_platform_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ struct sdm845_snd_data *data;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+
+ /* Allocate the private data */
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ card->driver_name = DRIVER_NAME;
+ card->dapm_widgets = sdm845_snd_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(sdm845_snd_widgets);
+ card->controls = sdm845_snd_controls;
+ card->num_controls = ARRAY_SIZE(sdm845_snd_controls);
+ card->dev = dev;
+ card->owner = THIS_MODULE;
+ dev_set_drvdata(dev, card);
+ ret = qcom_snd_parse_of(card);
+ if (ret)
+ return ret;
+
+ snd_soc_card_set_drvdata(card, data);
+
+ sdm845_add_ops(card);
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static const struct of_device_id sdm845_snd_device_id[] = {
+ { .compatible = "qcom,sdm845-sndcard" },
+ /* Do not grow the list for compatible devices */
+ { .compatible = "qcom,db845c-sndcard" },
+ { .compatible = "lenovo,yoga-c630-sndcard" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sdm845_snd_device_id);
+
+static struct platform_driver sdm845_snd_driver = {
+ .probe = sdm845_snd_platform_probe,
+ .driver = {
+ .name = "msm-snd-sdm845",
+ .of_match_table = sdm845_snd_device_id,
+ },
+};
+module_platform_driver(sdm845_snd_driver);
+
+MODULE_DESCRIPTION("sdm845 ASoC Machine Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/sdw.c b/sound/soc/qcom/sdw.c
new file mode 100644
index 000000000..6576b47a4
--- /dev/null
+++ b/sound/soc/qcom/sdw.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018-2023, Linaro Limited.
+// Copyright (c) 2018, The Linux Foundation. All rights reserved.
+
+#include <dt-bindings/sound/qcom,lpass.h>
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/module.h>
+#include <sound/soc.h>
+#include "sdw.h"
+
+static bool qcom_snd_is_sdw_dai(int id)
+{
+ switch (id) {
+ case WSA_CODEC_DMA_RX_0:
+ case WSA_CODEC_DMA_TX_0:
+ case WSA_CODEC_DMA_RX_1:
+ case WSA_CODEC_DMA_TX_1:
+ case WSA_CODEC_DMA_TX_2:
+ case RX_CODEC_DMA_RX_0:
+ case TX_CODEC_DMA_TX_0:
+ case RX_CODEC_DMA_RX_1:
+ case TX_CODEC_DMA_TX_1:
+ case RX_CODEC_DMA_RX_2:
+ case TX_CODEC_DMA_TX_2:
+ case RX_CODEC_DMA_RX_3:
+ case TX_CODEC_DMA_TX_3:
+ case RX_CODEC_DMA_RX_4:
+ case TX_CODEC_DMA_TX_4:
+ case RX_CODEC_DMA_RX_5:
+ case TX_CODEC_DMA_TX_5:
+ case RX_CODEC_DMA_RX_6:
+ case RX_CODEC_DMA_RX_7:
+ case SLIMBUS_0_RX...SLIMBUS_6_TX:
+ return true;
+ default:
+ break;
+ }
+
+ /* DSP Bypass usecase, cpu dai index overlaps with DSP dai ids,
+ * DO NOT MERGE into top switch case */
+ switch (id) {
+ case LPASS_CDC_DMA_TX3:
+ case LPASS_CDC_DMA_RX0:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * qcom_snd_sdw_startup() - Helper to start Soundwire stream for SoC audio card
+ * @substream: The PCM substream from audio, as passed to snd_soc_ops->startup()
+ *
+ * Helper for the SoC audio card (snd_soc_ops->startup()) to allocate and set
+ * Soundwire stream runtime to each codec DAI.
+ *
+ * The shutdown() callback should call sdw_release_stream() on the same
+ * sdw_stream_runtime.
+ *
+ * Return: 0 or errno
+ */
+int qcom_snd_sdw_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ u32 rx_ch[SDW_MAX_PORTS], tx_ch[SDW_MAX_PORTS];
+ struct sdw_stream_runtime *sruntime;
+ struct snd_soc_dai *codec_dai;
+ u32 rx_ch_cnt = 0, tx_ch_cnt = 0;
+ int ret, i, j;
+
+ if (!qcom_snd_is_sdw_dai(cpu_dai->id))
+ return 0;
+
+ sruntime = sdw_alloc_stream(cpu_dai->name, SDW_STREAM_PCM);
+ if (!sruntime)
+ return -ENOMEM;
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ ret = snd_soc_dai_set_stream(codec_dai, sruntime,
+ substream->stream);
+ if (ret < 0 && ret != -ENOTSUPP) {
+ dev_err(rtd->dev, "Failed to set sdw stream on %s\n", codec_dai->name);
+ goto err_set_stream;
+ } else if (ret == -ENOTSUPP) {
+ /* Ignore unsupported */
+ continue;
+ }
+
+ ret = snd_soc_dai_get_channel_map(codec_dai, &tx_ch_cnt, tx_ch,
+ &rx_ch_cnt, rx_ch);
+ if (ret != 0 && ret != -ENOTSUPP) {
+ dev_err(rtd->dev, "Failed to get codec chan map %s\n", codec_dai->name);
+ goto err_set_stream;
+ } else if (ret == -ENOTSUPP) {
+ /* Ignore unsupported */
+ continue;
+ }
+ }
+
+ switch (cpu_dai->id) {
+ case RX_CODEC_DMA_RX_0:
+ case TX_CODEC_DMA_TX_3:
+ if (tx_ch_cnt || rx_ch_cnt) {
+ for_each_rtd_codec_dais(rtd, j, codec_dai) {
+ ret = snd_soc_dai_set_channel_map(codec_dai,
+ tx_ch_cnt, tx_ch,
+ rx_ch_cnt, rx_ch);
+ if (ret != 0 && ret != -ENOTSUPP)
+ goto err_set_stream;
+ }
+ }
+ }
+
+ return 0;
+
+err_set_stream:
+ sdw_release_stream(sruntime);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(qcom_snd_sdw_startup);
+
+int qcom_snd_sdw_prepare(struct snd_pcm_substream *substream,
+ bool *stream_prepared)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct sdw_stream_runtime *sruntime;
+ int ret;
+
+
+ if (!qcom_snd_is_sdw_dai(cpu_dai->id))
+ return 0;
+
+ sruntime = qcom_snd_sdw_get_stream(substream);
+ if (!sruntime)
+ return 0;
+
+ if (*stream_prepared)
+ return 0;
+
+ ret = sdw_prepare_stream(sruntime);
+ if (ret)
+ return ret;
+
+ /**
+ * NOTE: there is a strict hw requirement about the ordering of port
+ * enables and actual WSA881x PA enable. PA enable should only happen
+ * after soundwire ports are enabled if not DC on the line is
+ * accumulated resulting in Click/Pop Noise
+ * PA enable/mute are handled as part of codec DAPM and digital mute.
+ */
+
+ ret = sdw_enable_stream(sruntime);
+ if (ret) {
+ sdw_deprepare_stream(sruntime);
+ return ret;
+ }
+ *stream_prepared = true;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(qcom_snd_sdw_prepare);
+
+struct sdw_stream_runtime *qcom_snd_sdw_get_stream(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai;
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct sdw_stream_runtime *sruntime;
+ int i;
+
+ if (!qcom_snd_is_sdw_dai(cpu_dai->id))
+ return NULL;
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ sruntime = snd_soc_dai_get_stream(codec_dai, substream->stream);
+ if (sruntime != ERR_PTR(-ENOTSUPP))
+ return sruntime;
+ }
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(qcom_snd_sdw_get_stream);
+
+void qcom_snd_sdw_shutdown(struct snd_pcm_substream *substream)
+{
+ struct sdw_stream_runtime *sruntime = qcom_snd_sdw_get_stream(substream);
+
+ sdw_release_stream(sruntime);
+}
+EXPORT_SYMBOL_GPL(qcom_snd_sdw_shutdown);
+
+int qcom_snd_sdw_hw_free(struct snd_pcm_substream *substream, bool *stream_prepared)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct sdw_stream_runtime *sruntime;
+
+ if (!qcom_snd_is_sdw_dai(cpu_dai->id))
+ return 0;
+
+ sruntime = qcom_snd_sdw_get_stream(substream);
+ if (sruntime && *stream_prepared) {
+ sdw_disable_stream(sruntime);
+ sdw_deprepare_stream(sruntime);
+ *stream_prepared = false;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(qcom_snd_sdw_hw_free);
+MODULE_DESCRIPTION("Qualcomm ASoC SoundWire helper functions");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/sdw.h b/sound/soc/qcom/sdw.h
new file mode 100644
index 000000000..061a63f1a
--- /dev/null
+++ b/sound/soc/qcom/sdw.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+// Copyright (c) 2018, The Linux Foundation. All rights reserved.
+
+#ifndef __QCOM_SND_SDW_H__
+#define __QCOM_SND_SDW_H__
+
+#include <linux/soundwire/sdw.h>
+
+int qcom_snd_sdw_startup(struct snd_pcm_substream *substream);
+void qcom_snd_sdw_shutdown(struct snd_pcm_substream *substream);
+int qcom_snd_sdw_prepare(struct snd_pcm_substream *substream,
+ bool *stream_prepared);
+struct sdw_stream_runtime *qcom_snd_sdw_get_stream(struct snd_pcm_substream *stream);
+int qcom_snd_sdw_hw_free(struct snd_pcm_substream *substream,
+ bool *stream_prepared);
+#endif
diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c
new file mode 100644
index 000000000..76dc3a3f4
--- /dev/null
+++ b/sound/soc/qcom/sm8250.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020, Linaro Limited
+
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <linux/soundwire/sdw.h>
+#include <sound/jack.h>
+#include <linux/input-event-codes.h>
+#include "qdsp6/q6afe.h"
+#include "common.h"
+#include "usb_offload_utils.h"
+#include "sdw.h"
+
+#define MI2S_BCLK_RATE 1536000
+
+struct sm8250_snd_data {
+ bool stream_prepared[AFE_PORT_MAX];
+ struct snd_soc_card *card;
+ struct snd_soc_jack jack;
+ struct snd_soc_jack usb_offload_jack;
+ bool usb_offload_jack_setup;
+ struct snd_soc_jack dp_jack;
+ bool jack_setup;
+};
+
+static int sm8250_snd_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ switch (cpu_dai->id) {
+ case DISPLAY_PORT_RX:
+ return qcom_snd_dp_jack_setup(rtd, &data->dp_jack, 0);
+ case USB_RX:
+ return qcom_snd_usb_offload_jack_setup(rtd, &data->usb_offload_jack,
+ &data->usb_offload_jack_setup);
+ default:
+ return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup);
+ }
+}
+
+static void sm8250_snd_exit(struct snd_soc_pcm_runtime *rtd)
+{
+ struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ if (cpu_dai->id == USB_RX)
+ qcom_snd_usb_offload_jack_remove(rtd,
+ &data->usb_offload_jack_setup);
+
+}
+
+static int sm8250_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+
+ return 0;
+}
+
+static int sm8250_snd_startup(struct snd_pcm_substream *substream)
+{
+ unsigned int fmt = SND_SOC_DAIFMT_BP_FP;
+ unsigned int codec_dai_fmt = SND_SOC_DAIFMT_BC_FC;
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+ switch (cpu_dai->id) {
+ case PRIMARY_MI2S_RX:
+ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S;
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT,
+ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+ snd_soc_dai_set_fmt(cpu_dai, fmt);
+ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
+ break;
+ case SECONDARY_MI2S_RX:
+ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S;
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT,
+ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+ snd_soc_dai_set_fmt(cpu_dai, fmt);
+ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
+ break;
+ case TERTIARY_MI2S_RX:
+ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S;
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_TER_MI2S_IBIT,
+ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+ snd_soc_dai_set_fmt(cpu_dai, fmt);
+ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
+ break;
+ case QUINARY_MI2S_RX:
+ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S;
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_QUI_MI2S_IBIT,
+ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+ snd_soc_dai_set_fmt(cpu_dai, fmt);
+ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
+ break;
+ case LPI_MI2S_RX_0:
+ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S;
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_INT0_MI2S_IBIT,
+ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+ snd_soc_dai_set_fmt(cpu_dai, fmt);
+ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
+ break;
+ case LPI_MI2S_TX_3:
+ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S;
+ snd_soc_dai_set_sysclk(cpu_dai,
+ Q6AFE_LPASS_CLK_ID_INT3_MI2S_IBIT,
+ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_CAPTURE);
+ snd_soc_dai_set_fmt(cpu_dai, fmt);
+ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
+ break;
+ default:
+ break;
+ }
+
+ return qcom_snd_sdw_startup(substream);
+}
+
+static int sm8250_snd_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+
+ return qcom_snd_sdw_prepare(substream, &data->stream_prepared[cpu_dai->id]);
+}
+
+static int sm8250_snd_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ return qcom_snd_sdw_hw_free(substream, &data->stream_prepared[cpu_dai->id]);
+}
+
+static const struct snd_soc_ops sm8250_be_ops = {
+ .startup = sm8250_snd_startup,
+ .shutdown = qcom_snd_sdw_shutdown,
+ .hw_free = sm8250_snd_hw_free,
+ .prepare = sm8250_snd_prepare,
+};
+
+static void sm8250_add_be_ops(struct snd_soc_card *card)
+{
+ struct snd_soc_dai_link *link;
+ int i;
+
+ for_each_card_prelinks(card, i, link) {
+ if (link->no_pcm == 1) {
+ link->init = sm8250_snd_init;
+ link->exit = sm8250_snd_exit;
+ link->be_hw_params_fixup = sm8250_be_hw_params_fixup;
+ link->ops = &sm8250_be_ops;
+ }
+ }
+}
+
+static int sm8250_platform_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ struct sm8250_snd_data *data;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+
+ card->owner = THIS_MODULE;
+ /* Allocate the private data */
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ card->dev = dev;
+ dev_set_drvdata(dev, card);
+ snd_soc_card_set_drvdata(card, data);
+ ret = qcom_snd_parse_of(card);
+ if (ret)
+ return ret;
+
+ card->driver_name = of_device_get_match_data(dev);
+ sm8250_add_be_ops(card);
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static const struct of_device_id snd_sm8250_dt_match[] = {
+ { .compatible = "fairphone,fp4-sndcard", .data = "sm7225" },
+ { .compatible = "fairphone,fp5-sndcard", .data = "qcm6490" },
+ { .compatible = "qcom,qrb2210-sndcard", .data = "qcm2290" },
+ { .compatible = "qcom,qrb4210-rb2-sndcard", .data = "sm4250" },
+ { .compatible = "qcom,qrb5165-rb5-sndcard", .data = "sm8250" },
+ { .compatible = "qcom,sdm660-sndcard", .data = "sdm660" },
+ { .compatible = "qcom,sm8250-sndcard", .data = "sm8250" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, snd_sm8250_dt_match);
+
+static struct platform_driver snd_sm8250_driver = {
+ .probe = sm8250_platform_probe,
+ .driver = {
+ .name = "snd-sm8250",
+ .of_match_table = snd_sm8250_dt_match,
+ },
+};
+module_platform_driver(snd_sm8250_driver);
+MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org");
+MODULE_DESCRIPTION("SM8250 ASoC Machine Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/storm.c b/sound/soc/qcom/storm.c
new file mode 100644
index 000000000..6b42fbfd2
--- /dev/null
+++ b/sound/soc/qcom/storm.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
+ *
+ * storm.c -- ALSA SoC machine driver for QTi ipq806x-based Storm board
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define STORM_SYSCLK_MULT 4
+
+static int storm_ops_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_card *card = soc_runtime->card;
+ snd_pcm_format_t format = params_format(params);
+ unsigned int rate = params_rate(params);
+ unsigned int sysclk_freq;
+ int bitwidth, ret;
+
+ bitwidth = snd_pcm_format_width(format);
+ if (bitwidth < 0) {
+ dev_err(card->dev, "invalid bit width given: %d\n", bitwidth);
+ return bitwidth;
+ }
+
+ /*
+ * as the CPU DAI is the I2S bus master and no system clock is needed by
+ * the MAX98357a DAC, simply set the system clock to be a constant
+ * multiple of the bit clock for the clock divider
+ */
+ sysclk_freq = rate * bitwidth * 2 * STORM_SYSCLK_MULT;
+
+ ret = snd_soc_dai_set_sysclk(snd_soc_rtd_to_cpu(soc_runtime, 0), 0, sysclk_freq, 0);
+ if (ret) {
+ dev_err(card->dev, "error setting sysclk to %u: %d\n",
+ sysclk_freq, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops storm_soc_ops = {
+ .hw_params = storm_ops_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "HiFi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link storm_dai_link = {
+ .name = "Primary",
+ .stream_name = "Primary",
+ .ops = &storm_soc_ops,
+ SND_SOC_DAILINK_REG(hifi),
+};
+
+static int storm_parse_of(struct device *dev)
+{
+ struct snd_soc_dai_link *dai_link = &storm_dai_link;
+ struct device_node *np = dev->of_node;
+
+ dai_link->cpus->of_node = of_parse_phandle(np, "cpu", 0);
+ if (!dai_link->cpus->of_node) {
+ dev_err(dev, "error getting cpu phandle\n");
+ return -EINVAL;
+ }
+ dai_link->platforms->of_node = dai_link->cpus->of_node;
+
+ dai_link->codecs->of_node = of_parse_phandle(np, "codec", 0);
+ if (!dai_link->codecs->of_node) {
+ dev_err(dev, "error getting codec phandle\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int storm_platform_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ int ret;
+
+ card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+
+ card->dev = &pdev->dev;
+ card->owner = THIS_MODULE;
+
+ ret = snd_soc_of_parse_card_name(card, "qcom,model");
+ if (ret) {
+ dev_err(&pdev->dev, "error parsing card name: %d\n", ret);
+ return ret;
+ }
+
+ card->dai_link = &storm_dai_link;
+ card->num_links = 1;
+
+ ret = storm_parse_of(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "error resolving dai links: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret)
+ dev_err(&pdev->dev, "error registering soundcard: %d\n", ret);
+
+ return ret;
+
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id storm_device_id[] = {
+ { .compatible = "google,storm-audio" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, storm_device_id);
+#endif
+
+static struct platform_driver storm_platform_driver = {
+ .driver = {
+ .name = "storm-audio",
+ .of_match_table =
+ of_match_ptr(storm_device_id),
+ },
+ .probe = storm_platform_probe,
+};
+module_platform_driver(storm_platform_driver);
+
+MODULE_DESCRIPTION("QTi IPQ806x-based Storm Machine Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/usb_offload_utils.c b/sound/soc/qcom/usb_offload_utils.c
new file mode 100644
index 000000000..0a24b278f
--- /dev/null
+++ b/sound/soc/qcom/usb_offload_utils.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/module.h>
+#include <sound/jack.h>
+#include <sound/soc-usb.h>
+
+#include "usb_offload_utils.h"
+
+int qcom_snd_usb_offload_jack_setup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_jack *jack, bool *jack_setup)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ int ret = 0;
+
+ if (cpu_dai->id != USB_RX)
+ return -EINVAL;
+
+ if (!*jack_setup) {
+ ret = snd_soc_usb_setup_offload_jack(codec_dai->component, jack);
+ if (ret)
+ return ret;
+ }
+
+ *jack_setup = true;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(qcom_snd_usb_offload_jack_setup);
+
+int qcom_snd_usb_offload_jack_remove(struct snd_soc_pcm_runtime *rtd,
+ bool *jack_setup)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ int ret = 0;
+
+ if (cpu_dai->id != USB_RX)
+ return -EINVAL;
+
+ if (*jack_setup) {
+ ret = snd_soc_component_set_jack(codec_dai->component, NULL, NULL);
+ if (ret)
+ return ret;
+ }
+
+ *jack_setup = false;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(qcom_snd_usb_offload_jack_remove);
+MODULE_DESCRIPTION("ASoC Q6 USB offload controls");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/usb_offload_utils.h b/sound/soc/qcom/usb_offload_utils.h
new file mode 100644
index 000000000..c3f411f56
--- /dev/null
+++ b/sound/soc/qcom/usb_offload_utils.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#ifndef __QCOM_SND_USB_OFFLOAD_UTILS_H__
+#define __QCOM_SND_USB_OFFLOAD_UTILS_H__
+
+#include <sound/soc.h>
+
+#if IS_ENABLED(CONFIG_SND_SOC_QCOM_OFFLOAD_UTILS)
+int qcom_snd_usb_offload_jack_setup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_jack *jack, bool *jack_setup);
+
+int qcom_snd_usb_offload_jack_remove(struct snd_soc_pcm_runtime *rtd,
+ bool *jack_setup);
+#else
+static inline int qcom_snd_usb_offload_jack_setup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_jack *jack,
+ bool *jack_setup)
+{
+ return -ENODEV;
+}
+
+static inline int qcom_snd_usb_offload_jack_remove(struct snd_soc_pcm_runtime *rtd,
+ bool *jack_setup)
+{
+ return -ENODEV;
+}
+#endif /* IS_ENABLED(CONFIG_SND_SOC_QCOM_OFFLOAD_UTILS) */
+#endif /* __QCOM_SND_USB_OFFLOAD_UTILS_H__ */
diff --git a/sound/soc/qcom/x1e80100.c b/sound/soc/qcom/x1e80100.c
new file mode 100644
index 000000000..c81df41ac
--- /dev/null
+++ b/sound/soc/qcom/x1e80100.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2023, Linaro Limited
+
+#include <dt-bindings/sound/qcom,q6afe.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/soundwire/sdw.h>
+#include <sound/pcm.h>
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "common.h"
+#include "qdsp6/q6afe.h"
+#include "qdsp6/q6dsp-common.h"
+#include "sdw.h"
+
+struct x1e80100_snd_data {
+ bool stream_prepared[AFE_PORT_MAX];
+ struct snd_soc_card *card;
+ struct snd_soc_jack jack;
+ struct snd_soc_jack dp_jack[8];
+ bool jack_setup;
+};
+
+static int x1e80100_snd_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_jack *dp_jack = NULL;
+ int dp_pcm_id = 0;
+
+ switch (cpu_dai->id) {
+ case WSA_CODEC_DMA_RX_0:
+ case WSA_CODEC_DMA_RX_1:
+ /*
+ * Set limit of -3 dB on Digital Volume and 0 dB on PA Volume
+ * to reduce the risk of speaker damage until we have active
+ * speaker protection in place.
+ */
+ snd_soc_limit_volume(card, "WSA WSA_RX0 Digital Volume", 81);
+ snd_soc_limit_volume(card, "WSA WSA_RX1 Digital Volume", 81);
+ snd_soc_limit_volume(card, "WSA2 WSA_RX0 Digital Volume", 81);
+ snd_soc_limit_volume(card, "WSA2 WSA_RX1 Digital Volume", 81);
+ snd_soc_limit_volume(card, "SpkrLeft PA Volume", 6);
+ snd_soc_limit_volume(card, "SpkrRight PA Volume", 6);
+ snd_soc_limit_volume(card, "WooferLeft PA Volume", 6);
+ snd_soc_limit_volume(card, "TweeterLeft PA Volume", 6);
+ snd_soc_limit_volume(card, "WooferRight PA Volume", 6);
+ snd_soc_limit_volume(card, "TweeterRight PA Volume", 6);
+ break;
+ case DISPLAY_PORT_RX_0:
+ dp_pcm_id = 0;
+ dp_jack = &data->dp_jack[dp_pcm_id];
+ break;
+ case DISPLAY_PORT_RX_1 ... DISPLAY_PORT_RX_7:
+ dp_pcm_id = cpu_dai->id - DISPLAY_PORT_RX_1 + 1;
+ dp_jack = &data->dp_jack[dp_pcm_id];
+ break;
+ default:
+ break;
+ }
+
+ if (dp_jack)
+ return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id);
+
+ return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup);
+}
+
+static int x1e80100_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ rate->min = rate->max = 48000;
+ switch (cpu_dai->id) {
+ case TX_CODEC_DMA_TX_0:
+ case TX_CODEC_DMA_TX_1:
+ case TX_CODEC_DMA_TX_2:
+ case TX_CODEC_DMA_TX_3:
+ channels->min = 1;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int x1e80100_snd_hw_map_channels(unsigned int *ch_map, int num)
+{
+ switch (num) {
+ case 1:
+ ch_map[0] = PCM_CHANNEL_FC;
+ break;
+ case 2:
+ ch_map[0] = PCM_CHANNEL_FL;
+ ch_map[1] = PCM_CHANNEL_FR;
+ break;
+ case 3:
+ ch_map[0] = PCM_CHANNEL_FL;
+ ch_map[1] = PCM_CHANNEL_FR;
+ ch_map[2] = PCM_CHANNEL_FC;
+ break;
+ case 4:
+ ch_map[0] = PCM_CHANNEL_FL;
+ ch_map[1] = PCM_CHANNEL_LB;
+ ch_map[2] = PCM_CHANNEL_FR;
+ ch_map[3] = PCM_CHANNEL_RB;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int x1e80100_snd_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ unsigned int channels = substream->runtime->channels;
+ unsigned int rx_slot[4];
+ int ret;
+
+ switch (cpu_dai->id) {
+ case WSA_CODEC_DMA_RX_0:
+ case WSA_CODEC_DMA_RX_1:
+ ret = x1e80100_snd_hw_map_channels(rx_slot, channels);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dai_set_channel_map(cpu_dai, 0, NULL,
+ channels, rx_slot);
+ if (ret)
+ return ret;
+ break;
+ default:
+ break;
+ }
+
+ return qcom_snd_sdw_prepare(substream, &data->stream_prepared[cpu_dai->id]);
+}
+
+static int x1e80100_snd_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ return qcom_snd_sdw_hw_free(substream, &data->stream_prepared[cpu_dai->id]);
+}
+
+static const struct snd_soc_ops x1e80100_be_ops = {
+ .startup = qcom_snd_sdw_startup,
+ .shutdown = qcom_snd_sdw_shutdown,
+ .hw_free = x1e80100_snd_hw_free,
+ .prepare = x1e80100_snd_prepare,
+};
+
+static void x1e80100_add_be_ops(struct snd_soc_card *card)
+{
+ struct snd_soc_dai_link *link;
+ int i;
+
+ for_each_card_prelinks(card, i, link) {
+ if (link->no_pcm == 1) {
+ link->init = x1e80100_snd_init;
+ link->be_hw_params_fixup = x1e80100_be_hw_params_fixup;
+ link->ops = &x1e80100_be_ops;
+ }
+ }
+}
+
+static int x1e80100_platform_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ struct x1e80100_snd_data *data;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+ /* Allocate the private data */
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ card->owner = THIS_MODULE;
+ card->dev = dev;
+ dev_set_drvdata(dev, card);
+ snd_soc_card_set_drvdata(card, data);
+
+ ret = qcom_snd_parse_of(card);
+ if (ret)
+ return ret;
+
+ card->driver_name = of_device_get_match_data(dev);
+ x1e80100_add_be_ops(card);
+
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static const struct of_device_id snd_x1e80100_dt_match[] = {
+ { .compatible = "qcom,x1e80100-sndcard", .data = "x1e80100" },
+ { .compatible = "qcom,glymur-sndcard", .data = "glymur" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, snd_x1e80100_dt_match);
+
+static struct platform_driver snd_x1e80100_driver = {
+ .probe = x1e80100_platform_probe,
+ .driver = {
+ .name = "snd-x1e80100",
+ .of_match_table = snd_x1e80100_dt_match,
+ },
+};
+module_platform_driver(snd_x1e80100_driver);
+MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org");
+MODULE_AUTHOR("Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>");
+MODULE_DESCRIPTION("Qualcomm X1E80100 ASoC Machine Driver");
+MODULE_LICENSE("GPL");