aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/sound/soc/qcom
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/qcom')
-rw-r--r--sound/soc/qcom/Kconfig148
-rw-r--r--sound/soc/qcom/Makefile40
-rw-r--r--sound/soc/qcom/apq8016_sbc.c200
-rw-r--r--sound/soc/qcom/apq8096.c15
-rw-r--r--sound/soc/qcom/common.c181
-rw-r--r--sound/soc/qcom/common.h5
-rw-r--r--sound/soc/qcom/lpass-apq8016.c107
-rw-r--r--sound/soc/qcom/lpass-cdc-dma.c302
-rw-r--r--sound/soc/qcom/lpass-cpu.c966
-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.c80
-rw-r--r--sound/soc/qcom/lpass-lpaif-reg.h327
-rw-r--r--sound/soc/qcom/lpass-platform.c1117
-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.h335
-rw-r--r--sound/soc/qcom/qdsp6/Makefile13
-rw-r--r--sound/soc/qcom/qdsp6/audioreach.c1386
-rw-r--r--sound/soc/qcom/qdsp6/audioreach.h804
-rw-r--r--sound/soc/qcom/qdsp6/q6adm.c26
-rw-r--r--sound/soc/qcom/qdsp6/q6afe-clocks.c120
-rw-r--r--sound/soc/qcom/qdsp6/q6afe-dai.c924
-rw-r--r--sound/soc/qcom/qdsp6/q6afe.c530
-rw-r--r--sound/soc/qcom/qdsp6/q6afe.h69
-rw-r--r--sound/soc/qcom/qdsp6/q6apm-dai.c888
-rw-r--r--sound/soc/qcom/qdsp6/q6apm-lpass-dais.c321
-rw-r--r--sound/soc/qcom/qdsp6/q6apm.c848
-rw-r--r--sound/soc/qcom/qdsp6/q6apm.h156
-rw-r--r--sound/soc/qcom/qdsp6/q6asm-dai.c599
-rw-r--r--sound/soc/qcom/qdsp6/q6asm.c190
-rw-r--r--sound/soc/qcom/qdsp6/q6asm.h74
-rw-r--r--sound/soc/qcom/qdsp6/q6core.c6
-rw-r--r--sound/soc/qcom/qdsp6/q6dsp-common.c37
-rw-r--r--sound/soc/qcom/qdsp6/q6dsp-common.h1
-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.c658
-rw-r--r--sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h23
-rw-r--r--sound/soc/qcom/qdsp6/q6prm-clocks.c94
-rw-r--r--sound/soc/qcom/qdsp6/q6prm.c255
-rw-r--r--sound/soc/qcom/qdsp6/q6prm.h97
-rw-r--r--sound/soc/qcom/qdsp6/q6routing.c186
-rw-r--r--sound/soc/qcom/qdsp6/q6usb.c421
-rw-r--r--sound/soc/qcom/qdsp6/topology.c1317
-rw-r--r--sound/soc/qcom/sc7180.c580
-rw-r--r--sound/soc/qcom/sc7280.c461
-rw-r--r--sound/soc/qcom/sc8280xp.c211
-rw-r--r--sound/soc/qcom/sdm845.c123
-rw-r--r--sound/soc/qcom/sdw.c192
-rw-r--r--sound/soc/qcom/sdw.h19
-rw-r--r--sound/soc/qcom/sm8250.c227
-rw-r--r--sound/soc/qcom/storm.c6
-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.c236
56 files changed, 15785 insertions, 1573 deletions
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index 5d6b2466a2f2..e86b4a03dd61 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -1,19 +1,29 @@
# SPDX-License-Identifier: GPL-2.0-only
-config SND_SOC_QCOM
+menuconfig SND_SOC_QCOM
tristate "ASoC support for QCOM platforms"
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
@@ -24,9 +34,22 @@ config SND_SOC_LPASS_APQ8016
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 SND_SOC_QCOM
+ depends on GPIOLIB
select SND_SOC_LPASS_IPQ806X
select SND_SOC_MAX98357A
help
@@ -35,7 +58,6 @@ config SND_SOC_STORM
config SND_SOC_APQ8016_SBC
tristate "SoC Audio support for APQ8016 SBC platforms"
- depends on SND_SOC_QCOM
select SND_SOC_LPASS_APQ8016
select SND_SOC_QCOM_COMMON
help
@@ -46,6 +68,9 @@ config SND_SOC_APQ8016_SBC
config SND_SOC_QCOM_COMMON
tristate
+config SND_SOC_QCOM_SDW
+ tristate
+
config SND_SOC_QDSP6_COMMON
tristate
@@ -58,6 +83,9 @@ config SND_SOC_QDSP6_AFE
config SND_SOC_QDSP6_AFE_DAI
tristate
+config SND_SOC_QDSP6_AFE_CLOCKS
+ tristate
+
config SND_SOC_QDSP6_ADM
tristate
@@ -71,17 +99,57 @@ 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
@@ -91,6 +159,7 @@ config SND_SOC_QDSP6
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
@@ -101,8 +170,10 @@ config SND_SOC_MSM8996
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
@@ -110,3 +181,74 @@ config SND_SOC_SDM845
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
+ 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
index 41b2c7a23a4d..985ce2ae286b 100644
--- a/sound/soc/qcom/Makefile
+++ b/sound/soc/qcom/Makefile
@@ -1,27 +1,49 @@
# SPDX-License-Identifier: GPL-2.0
# Platform
-snd-soc-lpass-cpu-objs := lpass-cpu.o
-snd-soc-lpass-platform-objs := lpass-platform.o
-snd-soc-lpass-ipq806x-objs := lpass-ipq806x.o
-snd-soc-lpass-apq8016-objs := lpass-apq8016.o
+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-objs := storm.o
-snd-soc-apq8016-sbc-objs := apq8016_sbc.o
-snd-soc-apq8096-objs := apq8096.o
-snd-soc-sdm845-objs := sdm845.o
-snd-soc-qcom-common-objs := common.o
+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
index 575e2aefefe3..3023cf180a75 100644
--- a/sound/soc/qcom/apq8016_sbc.c
+++ b/sound/soc/qcom/apq8016_sbc.c
@@ -16,7 +16,11 @@
#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;
@@ -24,24 +28,44 @@ struct apq8016_sbc_data {
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 int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd)
+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 *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
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 (cpu_dai->id) {
+ switch (mi2s) {
case MI2S_PRIMARY:
writel(readl(pdata->spkr_iomux) | SPKR_CTL_PRI_WS_SLAVE_SEL_11,
pdata->spkr_iomux);
@@ -53,6 +77,15 @@ static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd)
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,
@@ -69,13 +102,15 @@ static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd)
if (!pdata->jack_setup) {
struct snd_jack *jack;
- rval = snd_soc_card_jack_new(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, NULL, 0);
+ 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");
@@ -111,6 +146,13 @@ static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd)
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;
@@ -120,8 +162,121 @@ static void apq8016_sbc_add_ops(struct snd_soc_card *card)
link->init = apq8016_sbc_dai_init;
}
-static const struct snd_soc_dapm_widget apq8016_sbc_dapm_widgets[] = {
+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),
@@ -131,12 +286,16 @@ static const struct snd_soc_dapm_widget apq8016_sbc_dapm_widgets[] = {
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;
- struct resource *res;
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;
@@ -146,29 +305,30 @@ static int apq8016_sbc_platform_probe(struct platform_device *pdev)
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;
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mic-iomux");
- data->mic_iomux = devm_ioremap_resource(dev, res);
+ data->mic_iomux = devm_platform_ioremap_resource_byname(pdev, "mic-iomux");
if (IS_ERR(data->mic_iomux))
return PTR_ERR(data->mic_iomux);
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "spkr-iomux");
- data->spkr_iomux = devm_ioremap_resource(dev, res);
+ 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);
- apq8016_sbc_add_ops(card);
+ add_ops(card);
return devm_snd_soc_register_card(&pdev->dev, card);
}
-static const struct of_device_id apq8016_sbc_device_id[] = {
- { .compatible = "qcom,apq8016-sbc-sndcard" },
+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);
@@ -184,4 +344,4 @@ module_platform_driver(apq8016_sbc_platform_driver);
MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org");
MODULE_DESCRIPTION("APQ8016 ASoC Machine Driver");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/apq8096.c b/sound/soc/qcom/apq8096.c
index 1a69baefc5ce..4f6594cc723c 100644
--- a/sound/soc/qcom/apq8096.c
+++ b/sound/soc/qcom/apq8096.c
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018, Linaro Limited
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
-#include <linux/of_device.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
@@ -30,9 +30,9 @@ static int apq8096_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
static int msm_snd_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ 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;
@@ -60,13 +60,13 @@ end:
return ret;
}
-static struct snd_soc_ops apq8096_ops = {
+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 = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
/*
* Codec SLIMBUS configuration
@@ -113,6 +113,7 @@ static int apq8096_platform_probe(struct platform_device *pdev)
if (!card)
return -ENOMEM;
+ card->driver_name = "apq8096";
card->dev = dev;
card->owner = THIS_MODULE;
dev_set_drvdata(dev, card);
@@ -141,4 +142,4 @@ static struct platform_driver msm_snd_apq8096_driver = {
module_platform_driver(msm_snd_apq8096_driver);
MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org");
MODULE_DESCRIPTION("APQ8096 ASoC Machine Driver");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c
index fd69cf8b1f23..7ee60a58a336 100644
--- a/sound/soc/qcom/common.c
+++ b/sound/soc/qcom/common.c
@@ -2,9 +2,27 @@
// 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),
+};
+
int qcom_snd_parse_of(struct snd_soc_card *card)
{
struct device_node *np;
@@ -26,21 +44,35 @@ int qcom_snd_parse_of(struct snd_soc_card *card)
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_read_bool(dev->of_node, "audio-routing")) {
+ 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_read_bool(dev->of_node, "qcom,audio-routing")) {
+ 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_child_count(dev->of_node);
+ 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);
@@ -50,11 +82,11 @@ int qcom_snd_parse_of(struct snd_soc_card *card)
card->num_links = num_links;
link = card->dai_link;
- for_each_child_of_node(dev->of_node, np) {
- dlc = devm_kzalloc(dev, 2 * sizeof(*dlc), GFP_KERNEL);
+ for_each_available_child_of_node(dev->of_node, np) {
+ dlc = devm_kcalloc(dev, 2, sizeof(*dlc), GFP_KERNEL);
if (!dlc) {
ret = -ENOMEM;
- goto err;
+ goto err_put_np;
}
link->cpus = &dlc[0];
@@ -66,7 +98,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card)
ret = of_property_read_string(np, "link-name", &link->name);
if (ret) {
dev_err(card->dev, "error getting codec dai_link name\n");
- goto err;
+ goto err_put_np;
}
cpu = of_get_child_by_name(np, "cpu");
@@ -79,22 +111,14 @@ int qcom_snd_parse_of(struct snd_soc_card *card)
goto err;
}
- ret = of_parse_phandle_with_args(cpu, "sound-dai",
- "#sound-dai-cells", 0, &args);
+ ret = snd_soc_of_get_dlc(cpu, &args, link->cpus, 0);
if (ret) {
- dev_err(card->dev, "%s: error getting cpu phandle\n", link->name);
+ dev_err_probe(card->dev, ret,
+ "%s: error getting cpu dai name\n", link->name);
goto err;
}
- link->cpus->of_node = args.np;
- link->id = args.args[0];
- ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name);
- if (ret) {
- if (ret != -EPROBE_DEFER)
- dev_err(card->dev, "%s: error getting cpu dai name: %d\n",
- link->name, ret);
- goto err;
- }
+ link->id = args.args[0];
if (platform) {
link->platforms->of_node = of_parse_phandle(platform,
@@ -112,9 +136,8 @@ int qcom_snd_parse_of(struct snd_soc_card *card)
if (codec) {
ret = snd_soc_of_get_dai_link_codecs(dev, codec, link);
if (ret < 0) {
- if (ret != -EPROBE_DEFER)
- dev_err(card->dev, "%s: codec dai not found: %d\n",
- link->name, ret);
+ dev_err_probe(card->dev, ret,
+ "%s: codec dai not found\n", link->name);
goto err;
}
@@ -125,21 +148,13 @@ int qcom_snd_parse_of(struct snd_soc_card *card)
}
} else {
/* DPCM frontend */
- dlc = devm_kzalloc(dev, sizeof(*dlc), GFP_KERNEL);
- if (!dlc)
- return -ENOMEM;
-
- link->codecs = dlc;
+ link->codecs = &snd_soc_dummy_dlc;
link->num_codecs = 1;
-
- link->codecs->dai_name = "snd-soc-dummy-dai";
- link->codecs->name = "snd-soc-dummy";
link->dynamic = 1;
}
if (platform || !codec) {
/* DPCM */
- snd_soc_dai_link_set_capabilities(link);
link->ignore_suspend = 1;
link->nonatomic = 1;
}
@@ -152,14 +167,112 @@ int qcom_snd_parse_of(struct snd_soc_card *card)
of_node_put(platform);
}
+ if (!card->dapm_widgets) {
+ card->dapm_widgets = qcom_jack_snd_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(qcom_jack_snd_widgets);
+ }
+
return 0;
err:
- of_node_put(np);
of_node_put(cpu);
of_node_put(codec);
of_node_put(platform);
+err_put_np:
+ of_node_put(np);
return ret;
}
-EXPORT_SYMBOL(qcom_snd_parse_of);
+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 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_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ASoC Qualcomm helper functions");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h
index f05c05b12bd7..1b8d3f90bffa 100644
--- a/sound/soc/qcom/common.h
+++ b/sound/soc/qcom/common.h
@@ -7,5 +7,10 @@
#include <sound/soc.h>
int qcom_snd_parse_of(struct snd_soc_card *card);
+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
index b3610d05b651..b8f23414eb77 100644
--- a/sound/soc/qcom/lpass-apq8016.c
+++ b/sound/soc/qcom/lpass-apq8016.c
@@ -41,7 +41,6 @@ static struct snd_soc_dai_driver apq8016_lpass_cpu_dai_driver[] = {
.channels_min = 1,
.channels_max = 8,
},
- .probe = &asoc_qcom_lpass_cpu_dai_probe,
.ops = &asoc_qcom_lpass_cpu_dai_ops,
},
[MI2S_SECONDARY] = {
@@ -62,7 +61,6 @@ static struct snd_soc_dai_driver apq8016_lpass_cpu_dai_driver[] = {
.channels_min = 1,
.channels_max = 8,
},
- .probe = &asoc_qcom_lpass_cpu_dai_probe,
.ops = &asoc_qcom_lpass_cpu_dai_ops,
},
[MI2S_TERTIARY] = {
@@ -83,7 +81,6 @@ static struct snd_soc_dai_driver apq8016_lpass_cpu_dai_driver[] = {
.channels_min = 1,
.channels_max = 8,
},
- .probe = &asoc_qcom_lpass_cpu_dai_probe,
.ops = &asoc_qcom_lpass_cpu_dai_ops,
},
[MI2S_QUATERNARY] = {
@@ -119,15 +116,14 @@ static struct snd_soc_dai_driver apq8016_lpass_cpu_dai_driver[] = {
.channels_min = 1,
.channels_max = 8,
},
- .probe = &asoc_qcom_lpass_cpu_dai_probe,
.ops = &asoc_qcom_lpass_cpu_dai_ops,
},
};
static int apq8016_lpass_alloc_dma_channel(struct lpass_data *drvdata,
- int direction)
+ int direction, unsigned int dai_id)
{
- struct lpass_variant *v = drvdata->variant;
+ const struct lpass_variant *v = drvdata->variant;
int chan = 0;
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
@@ -151,7 +147,7 @@ static int apq8016_lpass_alloc_dma_channel(struct lpass_data *drvdata,
return chan;
}
-static int apq8016_lpass_free_dma_channel(struct lpass_data *drvdata, int 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);
@@ -161,50 +157,73 @@ static int apq8016_lpass_free_dma_channel(struct lpass_data *drvdata, int chan)
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;
+ int ret, i;
- drvdata->pcnoc_mport_clk = devm_clk_get(dev, "pcnoc-mport-clk");
- if (IS_ERR(drvdata->pcnoc_mport_clk)) {
- dev_err(dev, "error getting pcnoc-mport-clk: %ld\n",
- PTR_ERR(drvdata->pcnoc_mport_clk));
- return PTR_ERR(drvdata->pcnoc_mport_clk);
+
+ 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_prepare_enable(drvdata->pcnoc_mport_clk);
+ ret = clk_bulk_prepare_enable(drvdata->num_clks, drvdata->clks);
if (ret) {
- dev_err(dev, "Error enabling pcnoc-mport-clk: %d\n", ret);
+ dev_err(dev, "apq8016 clk_enable failed\n");
return ret;
}
- drvdata->pcnoc_sway_clk = devm_clk_get(dev, "pcnoc-sway-clk");
- if (IS_ERR(drvdata->pcnoc_sway_clk)) {
- dev_err(dev, "error getting pcnoc-sway-clk: %ld\n",
- PTR_ERR(drvdata->pcnoc_sway_clk));
- return PTR_ERR(drvdata->pcnoc_sway_clk);
+ 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_prepare_enable(drvdata->pcnoc_sway_clk);
+ ret = clk_set_rate(drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY);
if (ret) {
- dev_err(dev, "Error enabling pcnoc_sway_clk: %d\n", ret);
- return 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_disable_unprepare(drvdata->pcnoc_mport_clk);
- clk_disable_unprepare(drvdata->pcnoc_sway_clk);
+ clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks);
+ clk_disable_unprepare(drvdata->ahbix_clk);
return 0;
}
-static struct lpass_variant apq8016_data = {
+static const struct lpass_variant apq8016_data = {
.i2sctrl_reg_base = 0x1000,
.i2sctrl_reg_stride = 0x1000,
.i2s_ports = 4,
@@ -219,6 +238,35 @@ static struct lpass_variant apq8016_data = {
.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 *[]) {
@@ -239,8 +287,9 @@ static struct lpass_variant apq8016_data = {
.free_dma_channel = apq8016_lpass_free_dma_channel,
};
-static const struct of_device_id apq8016_lpass_cpu_device_id[] = {
+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);
@@ -251,10 +300,10 @@ static struct platform_driver apq8016_lpass_cpu_platform_driver = {
.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,
+ .remove = asoc_qcom_lpass_cpu_platform_remove,
};
module_platform_driver(apq8016_lpass_cpu_platform_driver);
MODULE_DESCRIPTION("APQ8016 LPASS CPU Driver");
-MODULE_LICENSE("GPL v2");
+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 000000000000..8106c586f68a
--- /dev/null
+++ b/sound/soc/qcom/lpass-cdc-dma.c
@@ -0,0 +1,302 @@
+// 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 ret, regval;
+ unsigned int channels = params_channels(params);
+ 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
index e00a4af29c13..242bc16da36d 100644
--- a/sound/soc/qcom/lpass-cpu.c
+++ b/sound/soc/qcom/lpass-cpu.c
@@ -5,11 +5,11 @@
* 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/of_device.h>
#include <linux/platform_device.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -28,6 +28,43 @@
#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)
@@ -54,14 +91,12 @@ static int lpass_cpu_daiops_startup(struct snd_pcm_substream *substream,
dev_err(dai->dev, "error in enabling mi2s osr clk: %d\n", ret);
return ret;
}
-
- ret = clk_prepare_enable(drvdata->mi2s_bit_clk[dai->driver->id]);
+ 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;
}
@@ -69,16 +104,39 @@ 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);
-
- clk_disable_unprepare(drvdata->mi2s_bit_clk[dai->driver->id]);
+ 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);
@@ -92,28 +150,45 @@ static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream,
return bitwidth;
}
- regval = LPAIF_I2SCTL_LOOPBACK_DISABLE |
- LPAIF_I2SCTL_WSSRC_INTERNAL;
+ 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;
+ regval = LPAIF_I2SCTL_BITWIDTH_16;
break;
case 24:
- regval |= LPAIF_I2SCTL_BITWIDTH_24;
+ regval = LPAIF_I2SCTL_BITWIDTH_24;
break;
case 32:
- regval |= LPAIF_I2SCTL_BITWIDTH_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[dai->driver->id];
+ mode = drvdata->mi2s_playback_sd_mode[id];
else
- mode = drvdata->mi2s_capture_sd_mode[dai->driver->id];
+ mode = drvdata->mi2s_capture_sd_mode[id];
if (!mode) {
dev_err(dai->dev, "no line is assigned\n");
@@ -175,30 +250,42 @@ static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream,
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- regval |= LPAIF_I2SCTL_SPKMODE(mode);
-
+ 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)
- regval |= LPAIF_I2SCTL_SPKMONO_STEREO;
+ ret = regmap_fields_write(i2sctl->spkmono, id,
+ LPAIF_I2SCTL_SPKMONO_STEREO);
else
- regval |= LPAIF_I2SCTL_SPKMONO_MONO;
+ ret = regmap_fields_write(i2sctl->spkmono, id,
+ LPAIF_I2SCTL_SPKMONO_MONO);
} else {
- regval |= LPAIF_I2SCTL_MICMODE(mode);
-
+ 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)
- regval |= LPAIF_I2SCTL_MICMONO_STEREO;
+ ret = regmap_fields_write(i2sctl->micmono, id,
+ LPAIF_I2SCTL_MICMONO_STEREO);
else
- regval |= LPAIF_I2SCTL_MICMONO_MONO;
+ ret = regmap_fields_write(i2sctl->micmono, id,
+ LPAIF_I2SCTL_MICMONO_MONO);
}
- ret = regmap_write(drvdata->lpaif_map,
- LPAIF_I2SCTL_REG(drvdata->variant, dai->driver->id),
- regval);
if (ret) {
- dev_err(dai->dev, "error writing to i2sctl reg: %d\n", ret);
+ dev_err(dai->dev, "error writing to i2sctl channels mode: %d\n",
+ ret);
return ret;
}
- ret = clk_set_rate(drvdata->mi2s_bit_clk[dai->driver->id],
+ 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",
@@ -209,130 +296,199 @@ static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream,
return 0;
}
-static int lpass_cpu_daiops_hw_free(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
- int ret;
-
- 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;
-}
-
-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);
- int ret;
- unsigned int val, mask;
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- val = LPAIF_I2SCTL_SPKEN_ENABLE;
- mask = LPAIF_I2SCTL_SPKEN_MASK;
- } else {
- val = LPAIF_I2SCTL_MICEN_ENABLE;
- mask = LPAIF_I2SCTL_MICEN_MASK;
- }
-
- ret = regmap_update_bits(drvdata->lpaif_map,
- LPAIF_I2SCTL_REG(drvdata->variant, dai->driver->id),
- mask, val);
- if (ret)
- dev_err(dai->dev, "error writing to i2sctl reg: %d\n", ret);
-
- return ret;
-}
-
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;
- unsigned int val, mask;
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) {
- val = LPAIF_I2SCTL_SPKEN_ENABLE;
- mask = LPAIF_I2SCTL_SPKEN_MASK;
+ ret = regmap_fields_write(i2sctl->spken, id,
+ LPAIF_I2SCTL_SPKEN_ENABLE);
} else {
- val = LPAIF_I2SCTL_MICEN_ENABLE;
- mask = LPAIF_I2SCTL_MICEN_MASK;
+ ret = regmap_fields_write(i2sctl->micen, id,
+ LPAIF_I2SCTL_MICEN_ENABLE);
}
-
- ret = regmap_update_bits(drvdata->lpaif_map,
- LPAIF_I2SCTL_REG(drvdata->variant,
- dai->driver->id),
- mask, val);
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) {
- val = LPAIF_I2SCTL_SPKEN_DISABLE;
- mask = LPAIF_I2SCTL_SPKEN_MASK;
+ ret = regmap_fields_write(i2sctl->spken, id,
+ LPAIF_I2SCTL_SPKEN_DISABLE);
} else {
- val = LPAIF_I2SCTL_MICEN_DISABLE;
- mask = LPAIF_I2SCTL_MICEN_MASK;
+ ret = regmap_fields_write(i2sctl->micen, id,
+ LPAIF_I2SCTL_MICEN_DISABLE);
}
-
- ret = regmap_update_bits(drvdata->lpaif_map,
- LPAIF_I2SCTL_REG(drvdata->variant,
- dai->driver->id),
- mask, val);
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,
- .hw_free = lpass_cpu_daiops_hw_free,
- .prepare = lpass_cpu_daiops_prepare,
.trigger = lpass_cpu_daiops_trigger,
+ .prepare = lpass_cpu_daiops_prepare,
};
EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_ops);
-int asoc_qcom_lpass_cpu_dai_probe(struct snd_soc_dai *dai)
+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_dai_get_drvdata(dai);
- int ret;
+ 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;
- /* 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);
+ 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;
}
-EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_probe);
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);
- struct lpass_variant *v = drvdata->variant;
+ const struct lpass_variant *v = drvdata->variant;
int i;
for (i = 0; i < v->i2s_ports; ++i)
@@ -374,7 +530,7 @@ static bool lpass_cpu_regmap_writeable(struct device *dev, unsigned int reg)
static bool lpass_cpu_regmap_readable(struct device *dev, unsigned int reg)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
- struct lpass_variant *v = drvdata->variant;
+ const struct lpass_variant *v = drvdata->variant;
int i;
for (i = 0; i < v->i2s_ports; ++i)
@@ -382,6 +538,8 @@ static bool lpass_cpu_regmap_readable(struct device *dev, unsigned int reg)
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))
@@ -420,12 +578,15 @@ static bool lpass_cpu_regmap_readable(struct device *dev, unsigned int reg)
static bool lpass_cpu_regmap_volatile(struct device *dev, unsigned int reg)
{
struct lpass_data *drvdata = dev_get_drvdata(dev);
- struct lpass_variant *v = drvdata->variant;
+ const struct lpass_variant *v = drvdata->variant;
int i;
- for (i = 0; i < v->irq_ports; ++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))
@@ -439,6 +600,7 @@ static bool lpass_cpu_regmap_volatile(struct device *dev, unsigned int reg)
}
static struct regmap_config lpass_cpu_regmap_config = {
+ .name = "lpass_cpu",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
@@ -448,6 +610,400 @@ static struct regmap_config lpass_cpu_regmap_config = {
.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)
@@ -491,43 +1047,71 @@ static void of_lpass_cpu_parse_dai_data(struct device *dev,
struct lpass_data *data)
{
struct device_node *node;
- int ret, id;
+ int ret, i, id;
/* Allow all channels by default for backwards compatibility */
- for (id = 0; id < data->variant->num_dai; id++) {
+ 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 || id >= data->variant->num_dai) {
+ if (ret || id < 0) {
dev_err(dev, "valid dai id not found: %d\n", ret);
continue;
}
-
- 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,
+ 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;
- struct lpass_variant *variant;
+ const struct lpass_variant *variant;
struct device *dev = &pdev->dev;
- const struct of_device_id *match;
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;
}
@@ -536,24 +1120,66 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev)
return -ENOMEM;
platform_set_drvdata(pdev, drvdata);
- match = of_match_device(dev->driver->of_match_table, dev);
- if (!match || !match->data)
+ variant = device_get_match_data(dev);
+ if (!variant)
return -EINVAL;
- drvdata->variant = (struct lpass_variant *)match->data;
- variant = drvdata->variant;
+ 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);
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-lpaif");
+ 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;
- drvdata->lpaif = devm_ioremap_resource(dev, res);
- if (IS_ERR((void const __force *)drvdata->lpaif)) {
- dev_err(dev, "error mapping reg resource: %ld\n",
- PTR_ERR((void const __force *)drvdata->lpaif));
- return PTR_ERR((void const __force *)drvdata->lpaif);
+ 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);
@@ -566,23 +1192,37 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev)
return PTR_ERR(drvdata->lpaif_map);
}
- if (variant->init)
- variant->init(pdev);
+ 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;
- drvdata->mi2s_osr_clk[dai_id] = devm_clk_get(dev,
- variant->dai_osr_clk_names[i]);
- if (IS_ERR(drvdata->mi2s_osr_clk[dai_id])) {
- dev_warn(dev,
- "%s() error getting optional %s: %ld\n",
- __func__,
- variant->dai_osr_clk_names[i],
- PTR_ERR(drvdata->mi2s_osr_clk[dai_id]));
-
- drvdata->mi2s_osr_clk[dai_id] = NULL;
- }
+ 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])) {
@@ -592,64 +1232,72 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev)
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;
+ }
}
- 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));
- return PTR_ERR(drvdata->ahbix_clk);
- }
+ /* Allocation for i2sctl regmap fields */
+ drvdata->i2sctl = devm_kzalloc(&pdev->dev, sizeof(struct lpaif_i2sctl),
+ GFP_KERNEL);
+ if (!drvdata->i2sctl)
+ return -ENOMEM;
- ret = clk_set_rate(drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY);
+ /* Initialize bitfields for dai I2SCTL register */
+ ret = lpass_cpu_init_i2sctl_bitfields(dev, drvdata->i2sctl,
+ drvdata->lpaif_map);
if (ret) {
- dev_err(dev, "error setting rate on ahbix_clk: %d\n", ret);
+ dev_err(dev, "error init i2sctl field: %d\n", ret);
return ret;
}
- 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);
- 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_clk;
+ goto err;
}
ret = asoc_qcom_lpass_platform_register(pdev);
if (ret) {
dev_err(dev, "error registering platform driver: %d\n", ret);
- goto err_clk;
+ goto err;
}
- return 0;
-
-err_clk:
- clk_disable_unprepare(drvdata->ahbix_clk);
+err:
return ret;
}
EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_probe);
-int asoc_qcom_lpass_cpu_platform_remove(struct platform_device *pdev)
+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);
- clk_disable_unprepare(drvdata->ahbix_clk);
+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);
- return 0;
}
-EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_remove);
+EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_shutdown);
MODULE_DESCRIPTION("QTi LPASS CPU Driver");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/lpass-hdmi.c b/sound/soc/qcom/lpass-hdmi.c
new file mode 100644
index 000000000000..ce753ebc0894
--- /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);
+ unsigned int ret;
+ 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;
+
+ 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 000000000000..ee74d783027a
--- /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
index 1987605482f7..e57d29ea4ce7 100644
--- a/sound/soc/qcom/lpass-ipq806x.c
+++ b/sound/soc/qcom/lpass-ipq806x.c
@@ -51,11 +51,51 @@ static struct snd_soc_dai_driver ipq806x_lpass_cpu_dai_driver = {
.channels_min = 1,
.channels_max = 8,
},
- .probe = &asoc_qcom_lpass_cpu_dai_probe,
.ops = &asoc_qcom_lpass_cpu_dai_ops,
};
-static int ipq806x_lpass_alloc_dma_channel(struct lpass_data *drvdata, int dir)
+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;
@@ -63,12 +103,12 @@ static int ipq806x_lpass_alloc_dma_channel(struct lpass_data *drvdata, int dir)
return -EINVAL;
}
-static int ipq806x_lpass_free_dma_channel(struct lpass_data *drvdata, int chan)
+static int ipq806x_lpass_free_dma_channel(struct lpass_data *drvdata, int chan, unsigned int dai_id)
{
return 0;
}
-static struct lpass_variant ipq806x_data = {
+static const struct lpass_variant ipq806x_data = {
.i2sctrl_reg_base = 0x0010,
.i2sctrl_reg_stride = 0x04,
.i2s_ports = 5,
@@ -82,6 +122,30 @@ static struct lpass_variant ipq806x_data = {
.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 *[]) {
@@ -90,11 +154,13 @@ static struct lpass_variant ipq806x_data = {
.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[] = {
+static const struct of_device_id ipq806x_lpass_cpu_device_id[] __maybe_unused = {
{ .compatible = "qcom,lpass-cpu", .data = &ipq806x_data },
{}
};
@@ -106,9 +172,9 @@ static struct platform_driver ipq806x_lpass_cpu_platform_driver = {
.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,
+ .remove = asoc_qcom_lpass_cpu_platform_remove,
};
module_platform_driver(ipq806x_lpass_cpu_platform_driver);
MODULE_DESCRIPTION("QTi LPASS CPU Driver");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/lpass-lpaif-reg.h b/sound/soc/qcom/lpass-lpaif-reg.h
index 72a3e2f69572..6d9d9d1f6a4d 100644
--- a/sound/soc/qcom/lpass-lpaif-reg.h
+++ b/sound/soc/qcom/lpass-lpaif-reg.h
@@ -12,15 +12,12 @@
(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_MASK 0x8000
-#define LPAIF_I2SCTL_LOOPBACK_SHIFT 15
-#define LPAIF_I2SCTL_LOOPBACK_DISABLE (0 << LPAIF_I2SCTL_LOOPBACK_SHIFT)
-#define LPAIF_I2SCTL_LOOPBACK_ENABLE (1 << LPAIF_I2SCTL_LOOPBACK_SHIFT)
-#define LPAIF_I2SCTL_SPKEN_MASK 0x4000
-#define LPAIF_I2SCTL_SPKEN_SHIFT 14
-#define LPAIF_I2SCTL_SPKEN_DISABLE (0 << LPAIF_I2SCTL_SPKEN_SHIFT)
-#define LPAIF_I2SCTL_SPKEN_ENABLE (1 << LPAIF_I2SCTL_SPKEN_SHIFT)
+#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
@@ -31,40 +28,41 @@
#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_SPKMODE_MASK 0x3C00
-#define LPAIF_I2SCTL_SPKMODE_SHIFT 10
-#define LPAIF_I2SCTL_SPKMODE(mode) ((mode) << LPAIF_I2SCTL_SPKMODE_SHIFT)
+#define LPAIF_I2SCTL_MICEN_DISABLE 0
+#define LPAIF_I2SCTL_MICEN_ENABLE 1
-#define LPAIF_I2SCTL_SPKMONO_MASK 0x0200
-#define LPAIF_I2SCTL_SPKMONO_SHIFT 9
-#define LPAIF_I2SCTL_SPKMONO_STEREO (0 << LPAIF_I2SCTL_SPKMONO_SHIFT)
-#define LPAIF_I2SCTL_SPKMONO_MONO (1 << LPAIF_I2SCTL_SPKMONO_SHIFT)
+#define LPAIF_I2SCTL_MICMODE(mode) mode
-#define LPAIF_I2SCTL_MICEN_MASK GENMASK(8, 8)
-#define LPAIF_I2SCTL_MICEN_SHIFT 8
-#define LPAIF_I2SCTL_MICEN_DISABLE (0 << LPAIF_I2SCTL_MICEN_SHIFT)
-#define LPAIF_I2SCTL_MICEN_ENABLE (1 << LPAIF_I2SCTL_MICEN_SHIFT)
+#define LPAIF_I2SCTL_MICMONO_STEREO 0
+#define LPAIF_I2SCTL_MICMONO_MONO 1
-#define LPAIF_I2SCTL_MICMODE_MASK GENMASK(7, 4)
-#define LPAIF_I2SCTL_MICMODE_SHIFT 4
-#define LPAIF_I2SCTL_MICMODE(mode) ((mode) << LPAIF_I2SCTL_MICMODE_SHIFT)
+#define LPAIF_I2SCTL_WSSRC_INTERNAL 0
+#define LPAIF_I2SCTL_WSSRC_EXTERNAL 1
-#define LPAIF_I2SCTL_MIMONO_MASK GENMASK(3, 3)
-#define LPAIF_I2SCTL_MICMONO_SHIFT 3
-#define LPAIF_I2SCTL_MICMONO_STEREO (0 << LPAIF_I2SCTL_MICMONO_SHIFT)
-#define LPAIF_I2SCTL_MICMONO_MONO (1 << LPAIF_I2SCTL_MICMONO_SHIFT)
+#define LPAIF_I2SCTL_BITWIDTH_16 0
+#define LPAIF_I2SCTL_BITWIDTH_24 1
+#define LPAIF_I2SCTL_BITWIDTH_32 2
-#define LPAIF_I2SCTL_WSSRC_MASK 0x0004
-#define LPAIF_I2SCTL_WSSRC_SHIFT 2
-#define LPAIF_I2SCTL_WSSRC_INTERNAL (0 << LPAIF_I2SCTL_WSSRC_SHIFT)
-#define LPAIF_I2SCTL_WSSRC_EXTERNAL (1 << LPAIF_I2SCTL_WSSRC_SHIFT)
+#define LPAIF_I2SCTL_RESET_STATE 0x003C0004
+#define LPAIF_DMACTL_RESET_STATE 0x00200000
-#define LPAIF_I2SCTL_BITWIDTH_MASK 0x0003
-#define LPAIF_I2SCTL_BITWIDTH_SHIFT 0
-#define LPAIF_I2SCTL_BITWIDTH_16 (0 << LPAIF_I2SCTL_BITWIDTH_SHIFT)
-#define LPAIF_I2SCTL_BITWIDTH_24 (1 << LPAIF_I2SCTL_BITWIDTH_SHIFT)
-#define LPAIF_I2SCTL_BITWIDTH_32 (2 << LPAIF_I2SCTL_BITWIDTH_SHIFT)
/* LPAIF IRQ */
#define LPAIF_IRQ_REG_ADDR(v, addr, port) \
@@ -76,6 +74,29 @@
#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)))
@@ -83,8 +104,22 @@
#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))
@@ -109,54 +144,176 @@
#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_DMA_REG(v, chan, dir, reg) \
- (dir == SNDRV_PCM_STREAM_PLAYBACK) ? \
- LPAIF_RDMA##reg##_REG(v, chan) : \
- LPAIF_WRDMA##reg##_REG(v, chan)
-
-#define LPAIF_DMACTL_REG(v, chan, dir) __LPAIF_DMA_REG(v, chan, dir, CTL)
-#define LPAIF_DMABASE_REG(v, chan, dir) __LPAIF_DMA_REG(v, chan, dir, BASE)
-#define LPAIF_DMABUFF_REG(v, chan, dir) __LPAIF_DMA_REG(v, chan, dir, BUFF)
-#define LPAIF_DMACURR_REG(v, chan, dir) __LPAIF_DMA_REG(v, chan, dir, CURR)
-#define LPAIF_DMAPER_REG(v, chan, dir) __LPAIF_DMA_REG(v, chan, dir, PER)
-#define LPAIF_DMAPERCNT_REG(v, chan, dir) __LPAIF_DMA_REG(v, chan, dir, PERCNT)
-
-#define LPAIF_DMACTL_BURSTEN_MASK 0x800
-#define LPAIF_DMACTL_BURSTEN_SHIFT 11
-#define LPAIF_DMACTL_BURSTEN_SINGLE (0 << LPAIF_DMACTL_BURSTEN_SHIFT)
-#define LPAIF_DMACTL_BURSTEN_INCR4 (1 << LPAIF_DMACTL_BURSTEN_SHIFT)
-
-#define LPAIF_DMACTL_WPSCNT_MASK 0x700
-#define LPAIF_DMACTL_WPSCNT_SHIFT 8
-#define LPAIF_DMACTL_WPSCNT_ONE (0 << LPAIF_DMACTL_WPSCNT_SHIFT)
-#define LPAIF_DMACTL_WPSCNT_TWO (1 << LPAIF_DMACTL_WPSCNT_SHIFT)
-#define LPAIF_DMACTL_WPSCNT_THREE (2 << LPAIF_DMACTL_WPSCNT_SHIFT)
-#define LPAIF_DMACTL_WPSCNT_FOUR (3 << LPAIF_DMACTL_WPSCNT_SHIFT)
-#define LPAIF_DMACTL_WPSCNT_SIX (5 << LPAIF_DMACTL_WPSCNT_SHIFT)
-#define LPAIF_DMACTL_WPSCNT_EIGHT (7 << LPAIF_DMACTL_WPSCNT_SHIFT)
-
-#define LPAIF_DMACTL_AUDINTF_MASK 0x0F0
-#define LPAIF_DMACTL_AUDINTF_SHIFT 4
-#define LPAIF_DMACTL_AUDINTF(id) (id << LPAIF_DMACTL_AUDINTF_SHIFT)
-
-#define LPAIF_DMACTL_FIFOWM_MASK 0x00E
-#define LPAIF_DMACTL_FIFOWM_SHIFT 1
-#define LPAIF_DMACTL_FIFOWM_1 (0 << LPAIF_DMACTL_FIFOWM_SHIFT)
-#define LPAIF_DMACTL_FIFOWM_2 (1 << LPAIF_DMACTL_FIFOWM_SHIFT)
-#define LPAIF_DMACTL_FIFOWM_3 (2 << LPAIF_DMACTL_FIFOWM_SHIFT)
-#define LPAIF_DMACTL_FIFOWM_4 (3 << LPAIF_DMACTL_FIFOWM_SHIFT)
-#define LPAIF_DMACTL_FIFOWM_5 (4 << LPAIF_DMACTL_FIFOWM_SHIFT)
-#define LPAIF_DMACTL_FIFOWM_6 (5 << LPAIF_DMACTL_FIFOWM_SHIFT)
-#define LPAIF_DMACTL_FIFOWM_7 (6 << LPAIF_DMACTL_FIFOWM_SHIFT)
-#define LPAIF_DMACTL_FIFOWM_8 (7 << LPAIF_DMACTL_FIFOWM_SHIFT)
-
-#define LPAIF_DMACTL_ENABLE_MASK 0x1
-#define LPAIF_DMACTL_ENABLE_SHIFT 0
-#define LPAIF_DMACTL_ENABLE_OFF (0 << LPAIF_DMACTL_ENABLE_SHIFT)
-#define LPAIF_DMACTL_ENABLE_ON (1 << LPAIF_DMACTL_ENABLE_SHIFT)
-
-#define LPAIF_DMACTL_DYNCLK_MASK BIT(12)
-#define LPAIF_DMACTL_DYNCLK_SHIFT 12
-#define LPAIF_DMACTL_DYNCLK_OFF (0 << LPAIF_DMACTL_DYNCLK_SHIFT)
-#define LPAIF_DMACTL_DYNCLK_ON (1 << LPAIF_DMACTL_DYNCLK_SHIFT)
+#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
index 01179bc0e5e5..9946f12254b3 100644
--- a/sound/soc/qcom/lpass-platform.c
+++ b/sound/soc/qcom/lpass-platform.c
@@ -5,6 +5,7 @@
* 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>
@@ -18,13 +19,11 @@
#define DRV_NAME "lpass-platform"
-struct lpass_pcm_data {
- int dma_ch;
- int i2s_port;
-};
-
-#define LPASS_PLATFORM_BUFFER_SIZE (16 * 1024)
+#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 |
@@ -50,18 +49,161 @@ static const struct snd_pcm_hardware lpass_platform_pcm_hardware = {
.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 = asoc_substream_to_rtd(substream);
- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0);
+ 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 lpass_variant *v = drvdata->variant;
+ 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 = devm_kzalloc(soc_runtime->dev, sizeof(*data), GFP_KERNEL);
+ component->id = dai_id;
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
@@ -69,39 +211,73 @@ static int lpass_platform_pcmops_open(struct snd_soc_component *component,
runtime->private_data = data;
if (v->alloc_dma_channel)
- dma_ch = v->alloc_dma_channel(drvdata, dir);
+ dma_ch = v->alloc_dma_channel(drvdata, dir, dai_id);
else
dma_ch = 0;
- if (dma_ch < 0)
+ if (dma_ch < 0) {
+ kfree(data);
return dma_ch;
+ }
- drvdata->substream[dma_ch] = substream;
-
- ret = regmap_write(drvdata->lpaif_map,
- LPAIF_DMACTL_REG(v, dma_ch, dir), 0);
- if (ret) {
- dev_err(soc_runtime->dev,
- "error writing to rdmactl reg: %d\n", ret);
- return ret;
+ 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;
-
- snd_soc_set_runtime_hwparams(substream, &lpass_platform_pcm_hardware);
-
- runtime->dma_bytes = lpass_platform_pcm_hardware.buffer_bytes_max;
-
+ 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;
}
- snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
-
return 0;
}
@@ -109,35 +285,155 @@ 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);
- struct lpass_variant *v = drvdata->variant;
+ const struct lpass_variant *v = drvdata->variant;
struct lpass_pcm_data *data;
+ unsigned int dai_id = cpu_dai->driver->id;
data = runtime->private_data;
- drvdata->substream[data->dma_ch] = NULL;
+
+ 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);
+ 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 = asoc_substream_to_rtd(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;
- struct lpass_variant *v = drvdata->variant;
+ const struct lpass_variant *v = drvdata->variant;
snd_pcm_format_t format = params_format(params);
unsigned int channels = params_channels(params);
unsigned int regval;
- int ch, dir = substream->stream;
+ 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;
- ch = pcm_data->dma_ch;
+ dmactl = __lpass_get_dmactl_handle(substream, component);
+ id = __lpass_get_id(substream, component);
bitwidth = snd_pcm_format_width(format);
if (bitwidth < 0) {
@@ -146,29 +442,79 @@ static int lpass_platform_pcmops_hw_params(struct snd_soc_component *component,
return bitwidth;
}
- regval = LPAIF_DMACTL_BURSTEN_INCR4 |
- LPAIF_DMACTL_AUDINTF(dma_port) |
- LPAIF_DMACTL_FIFOWM_8;
+ 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;
+ regval = LPAIF_DMACTL_WPSCNT_ONE;
break;
case 4:
- regval |= LPAIF_DMACTL_WPSCNT_TWO;
+ regval = LPAIF_DMACTL_WPSCNT_TWO;
break;
case 6:
- regval |= LPAIF_DMACTL_WPSCNT_THREE;
+ regval = LPAIF_DMACTL_WPSCNT_THREE;
break;
case 8:
- regval |= LPAIF_DMACTL_WPSCNT_FOUR;
+ regval = LPAIF_DMACTL_WPSCNT_FOUR;
break;
default:
- dev_err(soc_runtime->dev,
- "invalid PCM config given: bw=%d, ch=%u\n",
+ dev_err(soc_runtime->dev, "invalid PCM config given: bw=%d, ch=%u\n",
bitwidth, channels);
return -EINVAL;
}
@@ -177,23 +523,30 @@ static int lpass_platform_pcmops_hw_params(struct snd_soc_component *component,
case 32:
switch (channels) {
case 1:
- regval |= LPAIF_DMACTL_WPSCNT_ONE;
+ regval = LPAIF_DMACTL_WPSCNT_ONE;
break;
case 2:
- regval |= LPAIF_DMACTL_WPSCNT_TWO;
+ regval = (dai_id == LPASS_DP_RX ?
+ LPAIF_DMACTL_WPSCNT_ONE :
+ LPAIF_DMACTL_WPSCNT_TWO);
break;
case 4:
- regval |= LPAIF_DMACTL_WPSCNT_FOUR;
+ regval = (dai_id == LPASS_DP_RX ?
+ LPAIF_DMACTL_WPSCNT_TWO :
+ LPAIF_DMACTL_WPSCNT_FOUR);
break;
case 6:
- regval |= LPAIF_DMACTL_WPSCNT_SIX;
+ regval = (dai_id == LPASS_DP_RX ?
+ LPAIF_DMACTL_WPSCNT_THREE :
+ LPAIF_DMACTL_WPSCNT_SIX);
break;
case 8:
- regval |= LPAIF_DMACTL_WPSCNT_EIGHT;
+ 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",
+ dev_err(soc_runtime->dev, "invalid PCM config given: bw=%d, ch=%u\n",
bitwidth, channels);
return -EINVAL;
}
@@ -204,10 +557,9 @@ static int lpass_platform_pcmops_hw_params(struct snd_soc_component *component,
return -EINVAL;
}
- ret = regmap_write(drvdata->lpaif_map,
- LPAIF_DMACTL_REG(v, ch, dir), regval);
+ ret = regmap_fields_write(dmactl->wpscnt, id, regval);
if (ret) {
- dev_err(soc_runtime->dev, "error writing to rdmactl reg: %d\n",
+ dev_err(soc_runtime->dev, "error writing to dmactl reg: %d\n",
ret);
return ret;
}
@@ -218,16 +570,23 @@ static int lpass_platform_pcmops_hw_params(struct snd_soc_component *component,
static int lpass_platform_pcmops_hw_free(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream);
+ struct snd_soc_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;
- struct lpass_variant *v = drvdata->variant;
+ 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);
- ret = regmap_write(drvdata->lpaif_map, reg, 0);
+ 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);
@@ -239,26 +598,32 @@ 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 = asoc_substream_to_rtd(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;
- struct lpass_variant *v = drvdata->variant;
- int ret, ch, dir = substream->stream;
+ 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;
- ret = regmap_write(drvdata->lpaif_map,
- LPAIF_DMABASE_REG(v, ch, dir),
- runtime->dma_addr);
+ 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(drvdata->lpaif_map,
- LPAIF_DMABUFF_REG(v, ch, dir),
+ 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",
@@ -266,8 +631,7 @@ static int lpass_platform_pcmops_prepare(struct snd_soc_component *component,
return ret;
}
- ret = regmap_write(drvdata->lpaif_map,
- LPAIF_DMAPER_REG(v, ch, dir),
+ 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",
@@ -275,9 +639,15 @@ static int lpass_platform_pcmops_prepare(struct snd_soc_component *component,
return ret;
}
- ret = regmap_update_bits(drvdata->lpaif_map,
- LPAIF_DMACTL_REG(v, ch, dir),
- LPAIF_DMACTL_ENABLE_MASK, LPAIF_DMACTL_ENABLE_ON);
+ 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);
@@ -291,65 +661,190 @@ 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 = asoc_substream_to_rtd(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;
- struct lpass_variant *v = drvdata->variant;
- int ret, ch, dir = substream->stream;
+ 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:
- /* clear status before enabling interrupts */
- ret = regmap_write(drvdata->lpaif_map,
- LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST),
- LPAIF_IRQ_ALL(ch));
+ ret = regmap_fields_write(dmactl->enable, id,
+ LPAIF_DMACTL_ENABLE_ON);
if (ret) {
dev_err(soc_runtime->dev,
- "error writing to irqclear reg: %d\n", ret);
+ "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_update_bits(drvdata->lpaif_map,
- LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST),
- LPAIF_IRQ_ALL(ch),
- LPAIF_IRQ_ALL(ch));
+ ret = regmap_write_bits(map, reg_irqclr, val_irqclr, val_irqclr);
if (ret) {
- dev_err(soc_runtime->dev,
- "error writing to irqen reg: %d\n", ret);
+ dev_err(soc_runtime->dev, "error writing to irqclear reg: %d\n", ret);
return ret;
}
-
- ret = regmap_update_bits(drvdata->lpaif_map,
- LPAIF_DMACTL_REG(v, ch, dir),
- LPAIF_DMACTL_ENABLE_MASK,
- LPAIF_DMACTL_ENABLE_ON);
+ ret = regmap_update_bits(map, reg_irqen, val_mask, val_irqen);
if (ret) {
- dev_err(soc_runtime->dev,
- "error writing to rdmactl reg: %d\n", 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_update_bits(drvdata->lpaif_map,
- LPAIF_DMACTL_REG(v, ch, dir),
- LPAIF_DMACTL_ENABLE_MASK,
- LPAIF_DMACTL_ENABLE_OFF);
+ 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(drvdata->lpaif_map,
- LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST),
- LPAIF_IRQ_ALL(ch), 0);
+ 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);
@@ -365,26 +860,30 @@ 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 = asoc_substream_to_rtd(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;
- struct lpass_variant *v = drvdata->variant;
+ 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(drvdata->lpaif_map,
- LPAIF_DMABASE_REG(v, ch, dir), &base_addr);
+ 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(drvdata->lpaif_map,
- LPAIF_DMACURR_REG(v, ch, dir), &curr_addr);
+ 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);
@@ -394,15 +893,33 @@ static snd_pcm_uframes_t lpass_platform_pcmops_pointer(
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_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);
+ unsigned int dai_id = cpu_dai->driver->id;
- return dma_mmap_coherent(substream->pcm->card->dev, vma,
- runtime->dma_area, runtime->dma_addr,
- runtime->dma_bytes);
+ 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(
@@ -410,15 +927,50 @@ static irqreturn_t lpass_dma_interrupt_handler(
struct lpass_data *drvdata,
int chan, u32 interrupts)
{
- struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream);
- struct lpass_variant *v = drvdata->variant;
+ 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(drvdata->lpaif_map,
- LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST),
- 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);
@@ -429,23 +981,20 @@ static irqreturn_t lpass_dma_interrupt_handler(
}
if (interrupts & LPAIF_IRQ_XRUN(chan)) {
- rv = regmap_write(drvdata->lpaif_map,
- LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST),
- 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(soc_runtime->dev, "xrun warning\n");
+ 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(drvdata->lpaif_map,
- LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST),
- 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);
@@ -456,13 +1005,23 @@ static irqreturn_t lpass_dma_interrupt_handler(
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;
- struct lpass_variant *v = drvdata->variant;
+ const struct lpass_variant *v = drvdata->variant;
unsigned int irqs;
int rv, chan;
@@ -487,56 +1046,209 @@ static irqreturn_t lpass_platform_lpaif_irq(int irq, void *data)
return IRQ_HANDLED;
}
-static int lpass_platform_pcm_new(struct snd_soc_component *component,
- struct snd_soc_pcm_runtime *soc_runtime)
+static irqreturn_t lpass_platform_hdmiif_irq(int irq, void *data)
{
- struct snd_pcm *pcm = soc_runtime->pcm;
- struct snd_pcm_substream *psubstream, *csubstream;
- int ret = -EINVAL;
- size_t size = lpass_platform_pcm_hardware.buffer_bytes_max;
+ struct lpass_data *drvdata = data;
+ const struct lpass_variant *v = drvdata->variant;
+ unsigned int irqs;
+ int rv, chan;
- psubstream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
- if (psubstream) {
- ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
- component->dev,
- size, &psubstream->dma_buffer);
- if (ret) {
- dev_err(soc_runtime->dev, "Cannot allocate buffer(s)\n");
- return ret;
+ 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;
+}
- csubstream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
- if (csubstream) {
- ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
- component->dev,
- size, &csubstream->dma_buffer);
- if (ret) {
- dev_err(soc_runtime->dev, "Cannot allocate buffer(s)\n");
- if (psubstream)
- snd_dma_free_pages(&psubstream->dma_buffer);
- return ret;
+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 void lpass_platform_pcm_free(struct snd_soc_component *component,
- struct snd_pcm *pcm)
+static int lpass_platform_pcm_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *soc_runtime)
{
- struct snd_pcm_substream *substream;
- int i;
+ 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;
+ unsigned int dai_id = component->id;
+
+ if (dai_id == LPASS_DP_RX)
+ map = drvdata->hdmiif_map;
+ else
+ 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;
+ unsigned int dai_id = component->id;
+
+ if (dai_id == LPASS_DP_RX)
+ map = drvdata->hdmiif_map;
+ else
+ map = drvdata->lpaif_map;
+
+ regcache_cache_only(map, false);
+ return regcache_sync(map);
+}
- for_each_pcm_streams(i) {
- substream = pcm->streams[i].substream;
- if (substream) {
- snd_dma_free_pages(&substream->dma_buffer);
- substream->dma_buffer.area = NULL;
- substream->dma_buffer.addr = 0;
+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;
+ unsigned int dai_id = component->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 = {
@@ -550,14 +1262,16 @@ static const struct snd_soc_component_driver lpass_component_driver = {
.pointer = lpass_platform_pcmops_pointer,
.mmap = lpass_platform_pcmops_mmap,
.pcm_construct = lpass_platform_pcm_new,
- .pcm_destruct = lpass_platform_pcm_free,
+ .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);
- struct lpass_variant *v = drvdata->variant;
+ const struct lpass_variant *v = drvdata->variant;
int ret;
drvdata->lpaif_irq = platform_get_irq_byname(pdev, "lpass-irq-lpaif");
@@ -580,11 +1294,96 @@ int asoc_qcom_lpass_platform_register(struct platform_device *pdev)
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 v2");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/lpass-sc7180.c b/sound/soc/qcom/lpass-sc7180.c
new file mode 100644
index 000000000000..7c6a9b0fda89
--- /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 000000000000..817c824f9179
--- /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
index bd19ec57c73d..de3ec6f594c1 100644
--- a/sound/soc/qcom/lpass.h
+++ b/sound/soc/qcom/lpass.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
- * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2010-2011,2013-2015,2020 The Linux Foundation. All rights reserved.
*
* lpass.h - Definitions for the QTi LPASS
*/
@@ -12,10 +12,84 @@
#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 "lpass-hdmi.h"
#define LPASS_AHBIX_CLOCK_FREQUENCY 131072000
+#define LPASS_MAX_PORTS (DISPLAY_PORT_RX_7 + 1)
#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 {
@@ -29,48 +103,269 @@ struct lpass_data {
/* 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 */
- struct lpass_variant *variant;
+ 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;
- /* 8016 specific */
- struct clk *pcnoc_mport_clk;
- struct clk *pcnoc_sway_clk;
+ /* 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 i2sctrl_reg_base;
- u32 i2sctrl_reg_stride;
- u32 i2s_ports;
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
@@ -78,24 +373,38 @@ struct lpass_variant {
**/
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);
- int (*free_dma_channel)(struct lpass_data *data, int ch);
+ 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 *);
-int asoc_qcom_lpass_cpu_platform_remove(struct platform_device *pdev);
+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);
-int asoc_qcom_lpass_cpu_dai_probe(struct snd_soc_dai *dai);
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
index 7e91e96f7ad5..67267304e7e9 100644
--- a/sound/soc/qcom/qdsp6/Makefile
+++ b/sound/soc/qcom/qdsp6/Makefile
@@ -1,9 +1,20 @@
# SPDX-License-Identifier: GPL-2.0-only
-obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o
+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 000000000000..4ebaaf736fb9
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/audioreach.c
@@ -0,0 +1,1386 @@
+// 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_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_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)
+
+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,
+ 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,
+ 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,
+ 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,
+ 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, struct audioreach_graph_info *info,
+ struct apm_graph_open_params *open,
+ 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, 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;
+ 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,
+ struct gpr_pkt *pkt, uint32_t rsp_opcode)
+{
+
+ 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, 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,
+ struct audioreach_module *module,
+ 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, ep_sz, fs_sz, dl_sz;
+ int rc, payload_size;
+ struct gpr_pkt *pkt;
+ void *p;
+
+ ic_sz = APM_DP_INTF_CFG_PSIZE;
+ ep_sz = APM_HW_EP_CFG_PSIZE;
+ fs_sz = APM_FS_CFG_PSIZE;
+ dl_sz = 0;
+
+ payload_size = ic_sz + ep_sz + fs_sz + dl_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;
+
+ 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;
+
+ rc = q6apm_send_cmd_sync(graph->apm, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+/* LPASS Codec DMA port Module Media Format Setup */
+static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph,
+ struct audioreach_module *module,
+ 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, ep_sz, fs_sz, pm_sz, dl_sz;
+ int rc, payload_size;
+ struct gpr_pkt *pkt;
+ void *p;
+
+ ic_sz = APM_CDMA_INTF_CFG_PSIZE;
+ ep_sz = APM_HW_EP_CFG_PSIZE;
+ fs_sz = APM_FS_CFG_PSIZE;
+ pm_sz = APM_HW_EP_PMODE_CFG_PSIZE;
+ dl_sz = 0;
+
+ payload_size = ic_sz + ep_sz + fs_sz + pm_sz + dl_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;
+
+ 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 = (1 << cfg->num_channels) - 1;
+ 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;
+
+ rc = q6apm_send_cmd_sync(graph->apm, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+int audioreach_send_u32_param(struct q6apm_graph *graph, struct audioreach_module *module,
+ uint32_t param_id, uint32_t param_val)
+{
+ struct apm_module_param_data *param_data;
+ struct gpr_pkt *pkt;
+ uint32_t *param;
+ int rc, payload_size;
+ void *p;
+
+ payload_size = sizeof(uint32_t) + APM_MODULE_PARAM_DATA_SIZE;
+ p = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(p))
+ return -ENOMEM;
+
+ pkt = p;
+ p = p + 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;
+
+ rc = q6apm_send_cmd_sync(graph->apm, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(audioreach_send_u32_param);
+
+static int audioreach_sal_limiter_enable(struct q6apm_graph *graph,
+ 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,
+ struct audioreach_module *module,
+ 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,
+ 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,
+ struct audioreach_module *module,
+ struct audioreach_module_config *cfg)
+{
+ return audioreach_send_u32_param(graph, module, PARAM_ID_EARLY_EOS_DELAY,
+ EARLY_EOS_DELAY_MS);
+}
+
+static int audioreach_mfc_set_media_format(struct q6apm_graph *graph,
+ struct audioreach_module *module,
+ 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;
+ struct gpr_pkt *pkt;
+ int rc, i;
+ void *p;
+
+ payload_size = APM_MFC_CFG_PSIZE(media_format, num_channels) +
+ 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 = 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;
+ for (i = 0; i < num_channels; i++)
+ media_format->channel_mapping[i] = cfg->channel_map[i];
+
+ rc = q6apm_send_cmd_sync(graph->apm, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+static int audioreach_set_compr_media_format(struct media_format *media_fmt_hdr,
+ void *p, 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;
+
+ 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;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int audioreach_compr_set_param(struct q6apm_graph *graph, struct audioreach_module_config *mcfg)
+{
+ struct media_format *header;
+ struct gpr_pkt *pkt;
+ int iid, payload_size, rc;
+ void *p;
+
+ payload_size = sizeof(struct apm_sh_module_media_fmt_cmd);
+
+ iid = q6apm_graph_get_rx_shmem_module_iid(graph);
+ pkt = 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) {
+ kfree(pkt);
+ return rc;
+ }
+
+ rc = gpr_send_port_pkt(graph->port, pkt);
+ kfree(pkt);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(audioreach_compr_set_param);
+
+static int audioreach_i2s_set_media_format(struct q6apm_graph *graph,
+ struct audioreach_module *module,
+ 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, ep_sz, fs_sz;
+ int rc, payload_size;
+ struct gpr_pkt *pkt;
+ void *p;
+
+ ic_sz = APM_I2S_INTF_CFG_PSIZE;
+ ep_sz = APM_HW_EP_CFG_PSIZE;
+ fs_sz = APM_FS_CFG_PSIZE;
+
+ payload_size = ic_sz + ep_sz + fs_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;
+ 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.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;
+
+ rc = q6apm_send_cmd_sync(graph->apm, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+static int audioreach_logging_set_media_format(struct q6apm_graph *graph,
+ struct audioreach_module *module)
+{
+ struct apm_module_param_data *param_data;
+ struct data_logging_config *cfg;
+ int rc, payload_size;
+ struct gpr_pkt *pkt;
+ 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 = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_DATA_LOGGING_CONFIG;
+ param_data->param_size = payload_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;
+
+ rc = q6apm_send_cmd_sync(graph->apm, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+static int audioreach_pcm_set_media_format(struct q6apm_graph *graph,
+ struct audioreach_module *module,
+ 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 rc, payload_size;
+ struct gpr_pkt *pkt;
+
+ 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);
+
+ pkt = 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;
+ memcpy(media_cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels);
+
+ rc = q6apm_send_cmd_sync(graph->apm, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+static int audioreach_shmem_set_media_format(struct q6apm_graph *graph,
+ struct audioreach_module *module,
+ 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;
+ struct gpr_pkt *pkt;
+ 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;
+
+ pkt = 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;
+ memcpy(cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels);
+ } else {
+ rc = audioreach_set_compr_media_format(header, p, mcfg);
+ if (rc) {
+ kfree(pkt);
+ return rc;
+ }
+ }
+
+ rc = audioreach_graph_send_cmd_sync(graph, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+int audioreach_gain_set_vol_ctrl(struct q6apm *apm, struct audioreach_module *module, int vol)
+{
+ struct param_id_vol_ctrl_master_gain *cfg;
+ struct apm_module_param_data *param_data;
+ int rc, payload_size;
+ struct gpr_pkt *pkt;
+ 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 = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_VOL_CTRL_MASTER_GAIN;
+ param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE;
+
+ p = p + APM_MODULE_PARAM_DATA_SIZE;
+ cfg = p;
+ cfg->master_gain = vol;
+ rc = q6apm_send_cmd_sync(apm, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(audioreach_gain_set_vol_ctrl);
+
+static int audioreach_gain_set(struct q6apm_graph *graph, struct audioreach_module *module)
+{
+ struct apm_module_param_data *param_data;
+ struct apm_gain_module_cfg *cfg;
+ int rc, payload_size;
+ struct gpr_pkt *pkt;
+
+ payload_size = APM_GAIN_CFG_PSIZE;
+ pkt = 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 = APM_PARAM_ID_GAIN;
+ param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE;
+
+ cfg->gain_cfg.gain = module->gain;
+
+ rc = q6apm_send_cmd_sync(graph->apm, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+int audioreach_set_media_format(struct q6apm_graph *graph, struct audioreach_module *module,
+ 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_I2S_SOURCE:
+ case MODULE_ID_I2S_SINK:
+ rc = audioreach_i2s_set_media_format(graph, module, cfg);
+ break;
+ case MODULE_ID_WR_SHARED_MEM_EP:
+ 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;
+ 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_map_memory_regions(struct q6apm_graph *graph, unsigned int dir, size_t period_sz,
+ unsigned int periods, bool is_contiguous)
+{
+ struct apm_shared_map_region_payload *mregions;
+ struct apm_cmd_shared_mem_map_regions *cmd;
+ uint32_t num_regions, buf_sz, payload_size;
+ struct audioreach_graph_data *data;
+ struct gpr_pkt *pkt;
+ void *p;
+ int rc, i;
+
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ data = &graph->rx_data;
+ else
+ data = &graph->tx_data;
+
+ 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);
+
+ payload_size = sizeof(*cmd) + (sizeof(*mregions) * num_regions);
+
+ pkt = audioreach_alloc_apm_pkt(payload_size, APM_CMD_SHARED_MEM_MAP_REGIONS, dir,
+ graph->port->id);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE;
+ cmd = p;
+ cmd->mem_pool_id = APM_MEMORY_MAP_SHMEM8_4K_POOL;
+ cmd->num_regions = num_regions;
+
+ cmd->property_flag = 0x0;
+
+ mregions = p + sizeof(*cmd);
+
+ mutex_lock(&graph->lock);
+
+ for (i = 0; i < num_regions; i++) {
+ struct audio_buffer *ab;
+
+ ab = &data->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;
+ }
+ mutex_unlock(&graph->lock);
+
+ rc = audioreach_graph_send_cmd_sync(graph, pkt, APM_CMD_RSP_SHARED_MEM_MAP_REGIONS);
+
+ kfree(pkt);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(audioreach_map_memory_regions);
+
+int audioreach_shared_memory_send_eos(struct q6apm_graph *graph)
+{
+ struct data_cmd_wr_sh_mem_ep_eos *eos;
+ struct gpr_pkt *pkt;
+ int rc = 0, iid;
+
+ iid = q6apm_graph_get_rx_shmem_module_iid(graph);
+ pkt = 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;
+
+ rc = gpr_send_port_pkt(graph->port, pkt);
+ kfree(pkt);
+
+ return rc;
+}
+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 000000000000..61a69df4f50f
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/audioreach.h
@@ -0,0 +1,804 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __AUDIOREACH_H__
+#define __AUDIOREACH_H__
+#include <linux/types.h>
+#include <linux/soc/qcom/apr.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_PLACEHOLDER_ENCODER 0x07001008
+#define MODULE_ID_PLACEHOLDER_DECODER 0x07001009
+#define MODULE_ID_SAL 0x07001010
+#define MODULE_ID_MFC 0x07001015
+#define MODULE_ID_CODEC_DMA_SINK 0x07001023
+#define MODULE_ID_CODEC_DMA_SOURCE 0x07001024
+#define MODULE_ID_I2S_SINK 0x0700100A
+#define MODULE_ID_I2S_SOURCE 0x0700100B
+#define MODULE_ID_DATA_LOGGING 0x0700101A
+#define MODULE_ID_AAC_DEC 0x0700101F
+#define MODULE_ID_FLAC_DEC 0x0700102F
+#define MODULE_ID_MP3_DECODE 0x0700103B
+#define MODULE_ID_GAPLESS 0x0700104D
+#define MODULE_ID_DISPLAY_PORT_SINK 0x07001069
+
+#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_RSP_SHARED_MEM_MAP_REGIONS 0x02001001
+#define APM_CMD_RSP_GET_CFG 0x02001000
+#define APM_CMD_CLOSE_ALL 0x01001013
+#define APM_CMD_REGISTER_SHARED_CFG 0x0100100A
+
+#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 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_SECOINDARY 1
+#define I2S_INTF_TYPE_TERTINARY 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_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;
+
+#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;
+
+/* 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 num_sub_graphs;
+ struct list_head sg_list;
+ /* 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 in_port;
+ uint32_t out_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;
+
+ /* 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_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;
+ 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, 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_map_memory_regions(struct q6apm_graph *graph,
+ unsigned int dir, size_t period_sz,
+ unsigned int periods,
+ bool is_contiguous);
+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,
+ struct gpr_pkt *pkt, uint32_t rsp_opcode);
+int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, struct gpr_pkt *pkt,
+ uint32_t rsp_opcode);
+int audioreach_set_media_format(struct q6apm_graph *graph,
+ struct audioreach_module *module,
+ struct audioreach_module_config *cfg);
+int audioreach_shared_memory_send_eos(struct q6apm_graph *graph);
+int audioreach_gain_set_vol_ctrl(struct q6apm *apm,
+ struct audioreach_module *module, int vol);
+int audioreach_send_u32_param(struct q6apm_graph *graph, struct audioreach_module *module,
+ uint32_t param_id, uint32_t param_val);
+int audioreach_compr_set_param(struct q6apm_graph *graph, struct audioreach_module_config *mcfg);
+
+#endif /* __AUDIOREACH_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6adm.c b/sound/soc/qcom/qdsp6/q6adm.c
index 2f3ea6beb066..1530e98df165 100644
--- a/sound/soc/qcom/qdsp6/q6adm.c
+++ b/sound/soc/qcom/qdsp6/q6adm.c
@@ -90,7 +90,7 @@ struct q6adm_session_map_node_v5 {
static struct q6copp *q6adm_find_copp(struct q6adm *adm, int port_idx,
int copp_idx)
{
- struct q6copp *c = NULL;
+ struct q6copp *c;
struct q6copp *ret = NULL;
unsigned long flags;
@@ -180,7 +180,7 @@ static int q6adm_callback(struct apr_device *adev, struct apr_resp_pkt *data)
u32 status;
u16 copp_id;
u16 reserved;
- } __packed * open = data->payload;
+ } __packed *open = data->payload;
copp = q6adm_find_copp(adm, port_idx, copp_idx);
if (!copp)
@@ -217,7 +217,7 @@ static struct q6copp *q6adm_alloc_copp(struct q6adm *adm, int port_idx)
idx = find_first_zero_bit(&adm->copp_bitmap[port_idx],
MAX_COPPS_PER_PORT);
- if (idx > MAX_COPPS_PER_PORT)
+ if (idx >= MAX_COPPS_PER_PORT)
return ERR_PTR(-EBUSY);
c = kzalloc(sizeof(*c), GFP_ATOMIC);
@@ -299,7 +299,7 @@ static struct q6copp *q6adm_find_matching_copp(struct q6adm *adm,
int channel_mode, int bit_width,
int app_type)
{
- struct q6copp *c = NULL;
+ struct q6copp *c;
struct q6copp *ret = NULL;
unsigned long flags;
@@ -390,7 +390,7 @@ struct q6copp *q6adm_open(struct device *dev, int port_id, int path, int rate,
int ret = 0;
if (port_id < 0) {
- dev_err(dev, "Invalid port_id 0x%x\n", port_id);
+ dev_err(dev, "Invalid port_id %d\n", port_id);
return ERR_PTR(-EINVAL);
}
@@ -465,7 +465,7 @@ int q6adm_matrix_map(struct device *dev, int path,
struct apr_pkt *pkt;
uint16_t *copps_list;
int pkt_size, ret, i, copp_idx;
- void *matrix_map = NULL;
+ void *matrix_map;
struct q6copp *copp;
/* Assumes port_ids have already been validated during adm_open */
@@ -508,7 +508,7 @@ int q6adm_matrix_map(struct device *dev, int path,
int port_idx = payload_map.port_id[i];
if (port_idx < 0) {
- dev_err(dev, "Invalid port_id 0x%x\n",
+ dev_err(dev, "Invalid port_id %d\n",
payload_map.port_id[i]);
kfree(pkt);
return -EINVAL;
@@ -601,25 +601,19 @@ static int q6adm_probe(struct apr_device *adev)
INIT_LIST_HEAD(&adm->copps_list);
spin_lock_init(&adm->copps_list_lock);
- return of_platform_populate(dev->of_node, NULL, NULL, dev);
-}
-
-static int q6adm_remove(struct apr_device *adev)
-{
- of_platform_depopulate(&adev->dev);
-
- return 0;
+ 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,
- .remove = q6adm_remove,
.callback = q6adm_callback,
.driver = {
.name = "qcom-q6adm",
diff --git a/sound/soc/qcom/qdsp6/q6afe-clocks.c b/sound/soc/qcom/qdsp6/q6afe-clocks.c
new file mode 100644
index 000000000000..84b9018c36ba
--- /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
index 0168af849272..0f47aadaabe1 100644
--- a/sound/soc/qcom/qdsp6/q6afe-dai.c
+++ b/sound/soc/qcom/qdsp6/q6afe-dai.c
@@ -2,6 +2,7 @@
// 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>
@@ -11,49 +12,10 @@
#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"
-#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, \
- .ops = &q6tdm_ops, \
- .id = did, \
- .probe = msm_dai_q6_dai_probe, \
- .remove = msm_dai_q6_dai_remove, \
- }
-
-#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, \
- .ops = &q6tdm_ops, \
- .id = did, \
- .probe = msm_dai_q6_dai_probe, \
- .remove = msm_dai_q6_dai_remove, \
- }
struct q6afe_dai_priv_data {
uint32_t sd_line_mask;
@@ -109,6 +71,7 @@ static int q6hdmi_hw_params(struct snd_pcm_substream *substream,
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)) {
@@ -120,31 +83,42 @@ static int q6hdmi_hw_params(struct snd_pcm_substream *substream,
break;
}
- /* HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4 */
- switch (channels) {
- case 2:
- hdmi->channel_allocation = 0;
- break;
- case 3:
- hdmi->channel_allocation = 0x02;
- break;
- case 4:
- hdmi->channel_allocation = 0x06;
- break;
- case 5:
- hdmi->channel_allocation = 0x0A;
- break;
- case 6:
- hdmi->channel_allocation = 0x0B;
+ 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 7:
- hdmi->channel_allocation = 0x12;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_S24_3LE:
+ usb->bit_width = 24;
break;
- case 8:
- hdmi->channel_allocation = 0x13;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ usb->bit_width = 32;
break;
default:
- dev_err(dai->dev, "invalid Channels = %u\n", channels);
+ dev_err(dai->dev, "%s: invalid format %d\n",
+ __func__, params_format(params));
return -EINVAL;
}
@@ -219,7 +193,7 @@ static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai,
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;
+ 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",
@@ -231,8 +205,8 @@ static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai,
}
static int q6tdm_set_channel_map(struct snd_soc_dai *dai,
- unsigned int tx_num, unsigned int *tx_slot,
- unsigned int rx_num, unsigned int *rx_slot)
+ 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);
@@ -307,6 +281,92 @@ static int q6tdm_hw_params(struct snd_pcm_substream *substream,
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)
{
@@ -349,6 +409,7 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream,
q6afe_slim_port_prepare(dai_data->port[dai->id],
&dai_data->port_config[dai->id].slim);
break;
+ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX:
case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX:
rc = q6afe_i2s_port_prepare(dai_data->port[dai->id],
&dai_data->port_config[dai->id].i2s_cfg);
@@ -362,6 +423,14 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream,
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;
}
@@ -377,8 +446,10 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream,
}
static int q6slim_set_channel_map(struct snd_soc_dai *dai,
- unsigned int tx_num, unsigned int *tx_slot,
- unsigned int rx_num, unsigned int *rx_slot)
+ 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];
@@ -430,6 +501,7 @@ static int q6afe_mi2s_set_sysclk(struct snd_soc_dai *dai,
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,
@@ -446,7 +518,7 @@ static int q6afe_mi2s_set_sysclk(struct snd_soc_dai *dai,
static const struct snd_soc_dapm_route q6afe_dapm_routes[] = {
{"HDMI Playback", NULL, "HDMI_RX"},
- {"Display Port Playback", NULL, "DISPLAY_PORT_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"},
@@ -467,6 +539,7 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = {
{"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"},
{"Primary TDM0 Playback", NULL, "PRIMARY_TDM_RX_0"},
{"Primary TDM1 Playback", NULL, "PRIMARY_TDM_RX_1"},
@@ -562,36 +635,33 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = {
{"PRI_MI2S_TX", NULL, "Primary MI2S Capture"},
{"SEC_MI2S_TX", NULL, "Secondary MI2S Capture"},
{"QUAT_MI2S_TX", NULL, "Quaternary MI2S Capture"},
-};
-
-static const struct snd_soc_dai_ops q6hdmi_ops = {
- .prepare = q6afe_dai_prepare,
- .hw_params = q6hdmi_hw_params,
- .shutdown = q6afe_dai_shutdown,
-};
-
-static const struct snd_soc_dai_ops q6i2s_ops = {
- .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 = {
- .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 = {
- .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,
+ {"QUIN_MI2S_TX", NULL, "Quinary 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"},
};
static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai)
@@ -619,535 +689,70 @@ static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai)
return 0;
}
-static struct snd_soc_dai_driver q6afe_dais[] = {
- {
- .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,
- },
- .ops = &q6hdmi_ops,
- .id = HDMI_RX,
- .name = "HDMI",
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- }, {
- .name = "SLIMBUS_0_RX",
- .ops = &q6slim_ops,
- .id = SLIMBUS_0_RX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- .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",
- .ops = &q6slim_ops,
- .id = SLIMBUS_0_TX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- .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",
- .ops = &q6slim_ops,
- .id = SLIMBUS_1_RX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- }, {
- .name = "SLIMBUS_1_TX",
- .ops = &q6slim_ops,
- .id = SLIMBUS_1_TX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- .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",
- .ops = &q6slim_ops,
- .id = SLIMBUS_2_RX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
-
- }, {
- .name = "SLIMBUS_2_TX",
- .ops = &q6slim_ops,
- .id = SLIMBUS_2_TX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- .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",
- .ops = &q6slim_ops,
- .id = SLIMBUS_3_RX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
-
- }, {
- .name = "SLIMBUS_3_TX",
- .ops = &q6slim_ops,
- .id = SLIMBUS_3_TX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- .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",
- .ops = &q6slim_ops,
- .id = SLIMBUS_4_RX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
-
- }, {
- .name = "SLIMBUS_4_TX",
- .ops = &q6slim_ops,
- .id = SLIMBUS_4_TX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- .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",
- .ops = &q6slim_ops,
- .id = SLIMBUS_5_RX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
-
- }, {
- .name = "SLIMBUS_5_TX",
- .ops = &q6slim_ops,
- .id = SLIMBUS_5_TX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- .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,
- },
- .ops = &q6slim_ops,
- .name = "SLIMBUS_6_RX",
- .id = SLIMBUS_6_RX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
-
- }, {
- .name = "SLIMBUS_6_TX",
- .ops = &q6slim_ops,
- .id = SLIMBUS_6_TX,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- .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",
- .ops = &q6i2s_ops,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- }, {
- .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",
- .ops = &q6i2s_ops,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- }, {
- .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,
- .ops = &q6i2s_ops,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- }, {
- .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",
- .ops = &q6i2s_ops,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- }, {
- .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,
- .ops = &q6i2s_ops,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- }, {
- .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",
- .ops = &q6i2s_ops,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- }, {
- .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,
- .ops = &q6i2s_ops,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- }, {
- .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",
- .ops = &q6i2s_ops,
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- },
- 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),
- {
- .playback = {
- .stream_name = "Display Port 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,
- },
- .ops = &q6hdmi_ops,
- .id = DISPLAY_PORT_RX,
- .name = "DISPLAY_PORT",
- .probe = msm_dai_q6_dai_probe,
- .remove = msm_dai_q6_dai_remove,
- },
+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 int q6afe_of_xlate_dai_name(struct snd_soc_component *component,
- struct of_phandle_args *args,
- const char **dai_name)
-{
- int id = args->args[0];
- int ret = -EINVAL;
- int i;
+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,
+};
- for (i = 0; i < ARRAY_SIZE(q6afe_dais); i++) {
- if (q6afe_dais[i].id == id) {
- *dai_name = q6afe_dais[i].name;
- ret = 0;
- break;
- }
- }
+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,
+};
- return ret;
-}
+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),
@@ -1165,6 +770,10 @@ static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = {
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("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,
@@ -1350,6 +959,53 @@ static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = {
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),
};
static const struct snd_soc_component_driver q6afe_dai_component = {
@@ -1358,7 +1014,7 @@ static const struct snd_soc_component_driver q6afe_dai_component = {
.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 = q6afe_of_xlate_dai_name,
+ .of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name,
};
@@ -1381,6 +1037,7 @@ static void of_q6afe_parse_dai_data(struct device *dev,
switch (id) {
/* MI2S specific properties */
+ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX:
case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX:
priv = &data->priv[id];
ret = of_property_read_variable_u32_array(node,
@@ -1445,26 +1102,37 @@ static void of_q6afe_parse_dai_data(struct device *dev,
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);
- return devm_snd_soc_register_component(dev, &q6afe_dai_component,
- q6afe_dais, ARRAY_SIZE(q6afe_dais));
+ 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 = {
@@ -1475,5 +1143,5 @@ static struct platform_driver q6afe_dai_platform_driver = {
};
module_platform_driver(q6afe_dai_platform_driver);
-MODULE_DESCRIPTION("Q6 Audio Fronend dai 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
index e0945f7a58c8..7b59d514b432 100644
--- a/sound/soc/qcom/qdsp6/q6afe.c
+++ b/sound/soc/qcom/qdsp6/q6afe.c
@@ -2,6 +2,7 @@
// 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>
@@ -34,6 +35,8 @@
#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
@@ -42,6 +45,11 @@
#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
@@ -67,12 +75,16 @@
#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
@@ -116,6 +128,8 @@
#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
/* Start of the range of port IDs for TDM devices. */
#define AFE_PORT_ID_TDM_PORT_RANGE_START 0x9000
@@ -299,22 +313,71 @@
#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 1000
+#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;
};
@@ -448,11 +511,105 @@ struct afe_param_id_tdm_cfg {
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;
@@ -486,6 +643,18 @@ struct q6afe_port {
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;
@@ -545,6 +714,10 @@ static struct afe_port_map port_maps[AFE_PORT_MAX] = {
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},
[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,
@@ -707,6 +880,51 @@ static struct afe_port_map port_maps[AFE_PORT_MAX] = {
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},
};
static void q6afe_port_free(struct kref *ref)
@@ -726,7 +944,7 @@ static void q6afe_port_free(struct kref *ref)
static struct q6afe_port *q6afe_find_port(struct q6afe *afe, int token)
{
- struct q6afe_port *p = NULL;
+ struct q6afe_port *p;
struct q6afe_port *ret = NULL;
unsigned long flags;
@@ -769,6 +987,9 @@ static int q6afe_callback(struct apr_device *adev, struct apr_resp_pkt *data)
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:
@@ -777,6 +998,11 @@ static int q6afe_callback(struct apr_device *adev, struct apr_resp_pkt *data)
}
}
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;
}
@@ -801,15 +1027,23 @@ int q6afe_get_port_id(int index)
EXPORT_SYMBOL_GPL(q6afe_get_port_id);
static int afe_apr_send_pkt(struct q6afe *afe, struct apr_pkt *pkt,
- struct q6afe_port *port)
+ struct q6afe_port *port, uint32_t rsp_opcode)
{
- wait_queue_head_t *wait = &port->wait;
- struct apr_hdr *hdr = &pkt->hdr;
+ wait_queue_head_t *wait;
+ struct aprv2_ibasic_rsp_result_t *result;
int ret;
mutex_lock(&afe->lock);
- port->result.opcode = 0;
- port->result.status = 0;
+ 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) {
@@ -818,13 +1052,13 @@ static int afe_apr_send_pkt(struct q6afe *afe, struct apr_pkt *pkt,
goto err;
}
- ret = wait_event_timeout(*wait, (port->result.opcode == hdr->opcode),
+ ret = wait_event_timeout(*wait, (result->opcode == rsp_opcode),
msecs_to_jiffies(TIMEOUT_MS));
if (!ret) {
ret = -ETIMEDOUT;
- } else if (port->result.status > 0) {
+ } else if (result->status > 0) {
dev_err(afe->dev, "DSP returned error[%x]\n",
- port->result.status);
+ result->status);
ret = -EINVAL;
} else {
ret = 0;
@@ -836,14 +1070,13 @@ err:
return ret;
}
-static int q6afe_port_set_param(struct q6afe_port *port, void *data,
- int param_id, int module_id, int psize)
+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 q6afe *afe = port->afe;
struct apr_pkt *pkt;
- u16 port_id = port->id;
int ret, pkt_size;
void *p, *pl;
@@ -864,7 +1097,7 @@ static int q6afe_port_set_param(struct q6afe_port *port, void *data,
pkt->hdr.pkt_size = pkt_size;
pkt->hdr.src_port = 0;
pkt->hdr.dest_port = 0;
- pkt->hdr.token = port->token;
+ pkt->hdr.token = token;
pkt->hdr.opcode = AFE_SVC_CMD_SET_PARAM;
param->payload_size = sizeof(*pdata) + psize;
@@ -875,15 +1108,21 @@ static int q6afe_port_set_param(struct q6afe_port *port, void *data,
pdata->param_id = param_id;
pdata->param_size = psize;
- ret = afe_apr_send_pkt(afe, pkt, port);
+ ret = afe_apr_send_pkt(afe, pkt, port, AFE_SVC_CMD_SET_PARAM);
if (ret)
- dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
- port_id, ret);
+ dev_err(afe->dev, "AFE set params failed %d\n", ret);
kfree(pkt);
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)
{
@@ -924,7 +1163,7 @@ static int q6afe_port_set_param_v2(struct q6afe_port *port, void *data,
pdata->param_id = param_id;
pdata->param_size = psize;
- ret = afe_apr_send_pkt(afe, pkt, port);
+ 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);
@@ -933,7 +1172,7 @@ static int q6afe_port_set_param_v2(struct q6afe_port *port, void *data,
return ret;
}
-static int q6afe_set_lpass_clock(struct q6afe_port *port,
+static int q6afe_port_set_lpass_clock(struct q6afe_port *port,
struct afe_clk_cfg *cfg)
{
return q6afe_port_set_param_v2(port, cfg,
@@ -958,6 +1197,25 @@ static int q6afe_set_digital_codec_core_clock(struct q6afe_port *port,
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)
@@ -980,7 +1238,7 @@ int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id,
ccfg.clk_src = clk_src;
ccfg.clk_root = clk_root;
ccfg.clk_set_mode = Q6AFE_LPASS_MODE_CLK1_VALID;
- ret = q6afe_set_lpass_clock(port, &ccfg);
+ ret = q6afe_port_set_lpass_clock(port, &ccfg);
break;
case LPAIF_OSR_CLK:
@@ -989,11 +1247,12 @@ int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id,
ccfg.clk_src = clk_src;
ccfg.clk_root = clk_root;
ccfg.clk_set_mode = Q6AFE_LPASS_MODE_CLK2_VALID;
- ret = q6afe_set_lpass_clock(port, &ccfg);
+ 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;
@@ -1028,7 +1287,6 @@ int q6afe_port_stop(struct q6afe_port *port)
int index, pkt_size;
void *p;
- port_id = port->id;
index = port->token;
if (index < 0 || index >= AFE_PORT_MAX) {
dev_err(afe->dev, "AFE port index[%d] invalid!\n", index);
@@ -1054,7 +1312,7 @@ int q6afe_port_stop(struct q6afe_port *port)
stop->port_id = port_id;
stop->reserved = 0;
- ret = afe_apr_send_pkt(afe, pkt, port);
+ 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);
@@ -1125,6 +1383,99 @@ void q6afe_tdm_port_prepare(struct q6afe_port *port,
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
@@ -1163,11 +1514,11 @@ int q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg)
pcfg->i2s_cfg.bit_width = cfg->bit_width;
pcfg->i2s_cfg.data_format = AFE_LINEAR_PCM_DATA;
- switch (cfg->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBS_CFS:
+ 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_CBM_CFM:
+ case SND_SOC_DAIFMT_BC_FC:
/* CPU is slave */
pcfg->i2s_cfg.ws_src = AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL;
break;
@@ -1289,6 +1640,28 @@ int q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg)
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
@@ -1344,7 +1717,7 @@ int q6afe_port_start(struct q6afe_port *port)
start->port_id = port_id;
- ret = afe_apr_send_pkt(afe, pkt, port);
+ 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);
@@ -1415,12 +1788,19 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id)
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:
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);
@@ -1458,6 +1838,85 @@ void q6afe_port_put(struct q6afe_port *port)
}
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;
+ void *p;
+
+ pkt_size = APR_HDR_SIZE + sizeof(*vote_cfg);
+ p = 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);
+
+ kfree(pkt);
+ 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;
+ void *p;
+
+ pkt_size = APR_HDR_SIZE + sizeof(*vote_cfg);
+ p = 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);
+
+
+ kfree(pkt);
+ return ret;
+}
+EXPORT_SYMBOL(q6afe_vote_lpass_core_hw);
+
static int q6afe_probe(struct apr_device *adev)
{
struct q6afe *afe;
@@ -1470,31 +1929,26 @@ static int q6afe_probe(struct apr_device *adev)
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 of_platform_populate(dev->of_node, NULL, NULL, dev);
-}
-
-static int q6afe_remove(struct apr_device *adev)
-{
- of_platform_depopulate(&adev->dev);
-
- return 0;
+ 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,
- .remove = q6afe_remove,
.callback = q6afe_callback,
.driver = {
.name = "qcom-q6afe",
diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h
index c7ed5422baff..a29abe4ce436 100644
--- a/sound/soc/qcom/qdsp6/q6afe.h
+++ b/sound/soc/qcom/qdsp6/q6afe.h
@@ -3,9 +3,7 @@
#ifndef __Q6AFE_H__
#define __Q6AFE_H__
-#include <dt-bindings/sound/qcom,q6afe.h>
-
-#define AFE_PORT_MAX 105
+#define AFE_PORT_MAX 137
#define MSM_AFE_PORT_TYPE_RX 0
#define MSM_AFE_PORT_TYPE_TX 1
@@ -133,6 +131,19 @@
/* 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 */
@@ -184,11 +195,52 @@ struct q6afe_tdm_cfg {
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;
@@ -198,14 +250,25 @@ 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 000000000000..2cd522108221
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6apm-dai.c
@@ -0,0 +1,888 @@
+// 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/dma.h>
+#include <linux/dma-mapping.h>
+#include <sound/pcm_params.h>
+#include "q6apm.h"
+
+#define DRV_NAME "q6apm-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 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;
+ phys_addr_t phys;
+ unsigned int pcm_size;
+ unsigned int pcm_count;
+ unsigned int periods;
+ unsigned int bytes_sent;
+ unsigned int bytes_received;
+ unsigned int 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_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_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_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;
+ unsigned long flags;
+ uint32_t wflags = 0;
+ uint64_t avail;
+ uint32_t bytes_written, bytes_to_write;
+ bool is_last_buffer = false;
+
+ switch (opcode) {
+ case APM_CLIENT_EVENT_CMD_EOS_DONE:
+ spin_lock_irqsave(&prtd->lock, flags);
+ if (prtd->notify_on_drain) {
+ snd_compr_drain_notify(prtd->cstream);
+ prtd->notify_on_drain = false;
+ } else {
+ prtd->state = Q6APM_STREAM_STOPPED;
+ }
+ spin_unlock_irqrestore(&prtd->lock, flags);
+ break;
+ case APM_CLIENT_EVENT_DATA_WRITE_DONE:
+ spin_lock_irqsave(&prtd->lock, flags);
+ bytes_written = token >> APM_WRITE_TOKEN_LEN_SHIFT;
+ prtd->copied_total += bytes_written;
+ snd_compr_fragment_elapsed(substream);
+
+ if (prtd->state != Q6APM_STREAM_RUNNING) {
+ spin_unlock_irqrestore(&prtd->lock, flags);
+ 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);
+ }
+
+ spin_unlock_irqrestore(&prtd->lock, flags);
+ 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_unmap_memory_regions(prtd->graph, substream->stream);
+ }
+
+ prtd->pcm_count = snd_pcm_lib_period_bytes(substream);
+ /* rate and channels are sent to audio driver */
+ ret = q6apm_graph_media_format_shmem(prtd->graph, &cfg);
+ if (ret < 0) {
+ dev_err(dev, "%s: q6apm_open_write failed\n", __func__);
+ return ret;
+ }
+
+ ret = q6apm_graph_media_format_pcm(prtd->graph, &cfg);
+ if (ret < 0)
+ dev_err(dev, "%s: CMD Format block failed\n", __func__);
+
+ ret = q6apm_map_memory_regions(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 -ENOMEM;
+ }
+
+ 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 (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 (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;
+ 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(sizeof(*prtd), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&prtd->lock);
+ prtd->substream = substream;
+ prtd->graph = q6apm_graph_open(dev, event_handler, prtd, graph_id);
+ 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);
+
+ 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_unmap_memory_regions(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;
+
+ 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_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd)
+{
+ int size = BUFFER_BYTES_MAX;
+
+ return snd_pcm_set_fixed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, component->dev, size);
+}
+
+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(sizeof(*prtd), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ prtd->cstream = stream;
+ prtd->graph = q6apm_graph_open(dev, event_handler_compr, prtd, graph_id);
+ 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_unmap_memory_regions(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK);
+ 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 = 3;
+ caps->codecs[0] = SND_AUDIOCODEC_MP3;
+ caps->codecs[1] = SND_AUDIOCODEC_AAC;
+ caps->codecs[2] = SND_AUDIOCODEC_FLAC;
+
+ 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_tstamp *tstamp)
+{
+ struct snd_compr_runtime *runtime = stream->runtime;
+ struct q6apm_dai_rtd *prtd = runtime->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&prtd->lock, flags);
+ tstamp->copied_total = prtd->copied_total;
+ tstamp->byte_offset = prtd->copied_total % prtd->pcm_size;
+ spin_unlock_irqrestore(&prtd->lock, flags);
+
+ 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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&prtd->lock, flags);
+ prtd->bytes_received += count;
+ spin_unlock_irqrestore(&prtd->lock, flags);
+
+ 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_map_memory_regions(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;
+ unsigned long flags;
+ size_t copy;
+ u32 wflags = 0;
+ u32 app_pointer;
+ u32 bytes_received;
+ uint32_t bytes_to_write;
+ int avail, bytes_in_flight = 0;
+
+ bytes_received = prtd->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);
+
+ app_pointer = bytes_received/prtd->pcm_size;
+ app_pointer = bytes_received - (app_pointer * 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;
+ }
+
+ spin_lock_irqsave(&prtd->lock, flags);
+ 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;
+ }
+
+ spin_unlock_irqrestore(&prtd->lock, flags);
+
+ 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_construct = q6apm_dai_pcm_new,
+ .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,
+};
+
+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 000000000000..a0d90462fd6a
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c
@@ -0,0 +1,321 @@
+// 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/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 "audioreach.h"
+#include "q6apm.h"
+
+#define AUDIOREACH_BE_PCM_BASE 16
+
+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];
+};
+
+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, "fail to close 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_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;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ q6apm_graph_close(dai_data->graph[dai->id]);
+ dai_data->graph[dai->id] = NULL;
+ }
+ }
+
+ /**
+ * 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) {
+ graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id);
+ 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;
+ }
+
+ rc = q6apm_graph_start(dai_data->graph[dai->id]);
+ if (rc < 0) {
+ dev_err(dai->dev, "Failed to start APM port %d\n", dai->id);
+ goto err;
+ }
+ dai_data->is_port_started[dai->id] = true;
+
+ return 0;
+err:
+ 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);
+ 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_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 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,
+};
+
+static const struct snd_soc_dai_ops q6i2s_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,
+};
+
+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,
+};
+
+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,
+};
+
+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;
+
+ dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL);
+ if (!dai_data)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, dai_data);
+
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.q6i2s_ops = &q6i2s_ops;
+ cfg.q6dma_ops = &q6dma_ops;
+ cfg.q6hdmi_ops = &q6hdmi_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 000000000000..b4ffa0f0b188
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6apm.c
@@ -0,0 +1,848 @@
+// 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, 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(sizeof(*graph), GFP_KERNEL);
+ 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, rc, payload_size;
+ struct gpr_pkt *pkt;
+
+ payload_size = APM_GRAPH_MGMT_PSIZE(mgmt_cmd, num_sub_graphs);
+
+ pkt = 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;
+
+ rc = q6apm_send_cmd_sync(apm, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+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;
+
+ pkt = 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);
+
+ kfree(pkt);
+
+ 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_RD_SHARED_MEM_EP);
+ else
+ module = q6apm_find_module_by_mid(graph, MODULE_ID_WR_SHARED_MEM_EP);
+
+ if (!module)
+ return -ENODEV;
+
+ audioreach_set_media_format(graph, module, cfg);
+
+ return 0;
+
+}
+EXPORT_SYMBOL_GPL(q6apm_graph_media_format_shmem);
+
+int q6apm_map_memory_regions(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;
+ int rc;
+
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ data = &graph->rx_data;
+ else
+ data = &graph->tx_data;
+
+ mutex_lock(&graph->lock);
+
+ if (data->buf) {
+ mutex_unlock(&graph->lock);
+ return 0;
+ }
+
+ buf = kcalloc(periods, sizeof(struct audio_buffer), GFP_KERNEL);
+ 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);
+
+ rc = audioreach_map_memory_regions(graph, dir, period_sz, periods, 1);
+ if (rc < 0) {
+ dev_err(graph->dev, "Memory_map_regions failed\n");
+ audioreach_graph_free_buf(graph);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(q6apm_map_memory_regions);
+
+int q6apm_unmap_memory_regions(struct q6apm_graph *graph, unsigned int dir)
+{
+ struct apm_cmd_shared_mem_unmap_regions *cmd;
+ struct audioreach_graph_data *data;
+ struct gpr_pkt *pkt;
+ int rc;
+
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ data = &graph->rx_data;
+ else
+ data = &graph->tx_data;
+
+ if (!data->mem_map_handle)
+ return 0;
+
+ pkt = audioreach_alloc_apm_pkt(sizeof(*cmd), APM_CMD_SHARED_MEM_UNMAP_REGIONS, dir,
+ graph->port->id);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ cmd = (void *)pkt + GPR_HDR_SIZE;
+ cmd->mem_map_handle = data->mem_map_handle;
+
+ rc = audioreach_graph_send_cmd_sync(graph, pkt, APM_CMD_SHARED_MEM_UNMAP_REGIONS);
+ kfree(pkt);
+
+ audioreach_graph_free_buf(graph);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(q6apm_unmap_memory_regions);
+
+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;
+ 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;
+
+ 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))
+ continue;
+
+ audioreach_set_media_format(graph, module, cfg);
+ }
+ }
+ }
+
+ return 0;
+
+}
+EXPORT_SYMBOL_GPL(q6apm_graph_media_format_pcm);
+
+static int q6apm_graph_get_tx_shmem_module_iid(struct q6apm_graph *graph)
+{
+ struct audioreach_module *module;
+
+ module = q6apm_find_module_by_mid(graph, MODULE_ID_RD_SHARED_MEM_EP);
+ if (!module)
+ return -ENODEV;
+
+ return module->instance_id;
+
+}
+
+int q6apm_graph_get_rx_shmem_module_iid(struct q6apm_graph *graph)
+{
+ struct audioreach_module *module;
+
+ module = q6apm_find_module_by_mid(graph, MODULE_ID_WR_SHARED_MEM_EP);
+ if (!module)
+ return -ENODEV;
+
+ return module->instance_id;
+
+}
+EXPORT_SYMBOL_GPL(q6apm_graph_get_rx_shmem_module_iid);
+
+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;
+ int rc, iid;
+
+ iid = q6apm_graph_get_rx_shmem_module_iid(graph);
+ pkt = 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, 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->rx_data.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);
+
+ rc = gpr_send_port_pkt(graph->port, pkt);
+
+ kfree(pkt);
+
+ return rc;
+}
+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;
+ int rc, iid;
+
+ iid = q6apm_graph_get_tx_shmem_module_iid(graph);
+ pkt = audioreach_alloc_pkt(sizeof(*read_buffer), DATA_CMD_RD_SH_MEM_EP_DATA_BUFFER_V2,
+ graph->tx_data.dsp_buf, graph->port->id, 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 = port->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);
+
+ rc = gpr_send_port_pkt(graph->port, pkt);
+ kfree(pkt);
+
+ return rc;
+}
+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(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_cmd_rsp_shared_mem_map_regions *rsp;
+ struct gpr_ibasic_rsp_result_t *result;
+ struct q6apm_graph *graph = priv;
+ 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 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;
+ 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 APM_CMD_RSP_SHARED_MEM_MAP_REGIONS:
+ graph->result.opcode = hdr->opcode;
+ graph->result.status = 0;
+ rsp = data->payload;
+
+ if (hdr->token == SNDRV_PCM_STREAM_PLAYBACK)
+ graph->rx_data.mem_map_handle = rsp->mem_map_handle;
+ else
+ graph->tx_data.mem_map_handle = rsp->mem_map_handle;
+
+ wake_up(&graph->cmd_wait);
+ 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;
+ 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_UNMAP_REGIONS:
+ graph->result.opcode = result->opcode;
+ graph->result.status = 0;
+ if (hdr->token == SNDRV_PCM_STREAM_PLAYBACK)
+ graph->rx_data.mem_map_handle = 0;
+ else
+ graph->tx_data.mem_map_handle = 0;
+
+ wake_up(&graph->cmd_wait);
+ break;
+ case APM_CMD_SHARED_MEM_MAP_REGIONS:
+ case DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT:
+ 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;
+}
+
+struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb,
+ void *priv, int graph_id)
+{
+ struct q6apm *apm = dev_get_drvdata(dev->parent);
+ struct audioreach_graph *ar_graph;
+ struct q6apm_graph *graph;
+ int ret;
+
+ 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(sizeof(*graph), GFP_KERNEL);
+ 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;
+
+ 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 = 0;
+
+ if (ar_graph->start_count == 0)
+ ret = audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_START);
+
+ ar_graph->start_count++;
+
+ return ret;
+}
+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;
+
+ 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,
+};
+
+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 = devm_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;
+ }
+
+ return of_platform_populate(dev->of_node, NULL, NULL, 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(struct gpr_resp_pkt *data, void *priv, int op)
+{
+ gpr_device_t *gdev = priv;
+ struct q6apm *apm = dev_get_drvdata(&gdev->dev);
+ struct device *dev = &gdev->dev;
+ struct gpr_ibasic_rsp_result_t *result;
+ struct gpr_hdr *hdr = &data->hdr;
+
+ 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_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;
+ default:
+ break;
+ }
+ 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,
+ .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 000000000000..7ce08b401e31
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6apm.h
@@ -0,0 +1,156 @@
+/* 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 "audioreach.h"
+
+#define APM_PORT_MAX 127
+#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_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;
+ uint32_t mem_map_handle;
+ 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;
+ 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 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_regions(struct q6apm_graph *graph,
+ unsigned int dir, phys_addr_t phys,
+ size_t period_sz, unsigned int periods);
+int q6apm_unmap_memory_regions(struct q6apm_graph *graph,
+ unsigned int dir);
+/* Helpers */
+int q6apm_send_cmd_sync(struct q6apm *apm, 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);
+int q6apm_graph_get_rx_shmem_module_iid(struct q6apm_graph *graph);
+
+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);
+#endif /* __APM_GRAPH_ */
diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c
index 9b7b218f2a20..a400c9a31fea 100644
--- a/sound/soc/qcom/qdsp6/q6asm-dai.c
+++ b/sound/soc/qcom/qdsp6/q6asm-dai.c
@@ -2,9 +2,11 @@
// 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>
@@ -14,7 +16,6 @@
#include <sound/compress_driver.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
-#include <linux/of_device.h>
#include <sound/pcm_params.h>
#include "q6asm.h"
#include "q6routing.h"
@@ -50,7 +51,7 @@ enum stream_state {
struct q6asm_dai_rtd {
struct snd_pcm_substream *substream;
struct snd_compr_stream *cstream;
- struct snd_compr_params codec_param;
+ struct snd_codec codec;
struct snd_dma_buffer dma_buffer;
spinlock_t lock;
phys_addr_t phys;
@@ -64,8 +65,14 @@ struct q6asm_dai_rtd {
uint16_t bits_per_sample;
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 {
@@ -96,7 +103,7 @@ static const struct snd_pcm_hardware q6asm_dai_hardware_capture = {
.fifo_size = 0,
};
-static struct snd_pcm_hardware q6asm_dai_hardware_playback = {
+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 |
@@ -121,8 +128,13 @@ static struct snd_pcm_hardware q6asm_dai_hardware_playback = {
#define Q6ASM_FEDAI_DRIVER(num) { \
.playback = { \
.stream_name = "MultiMedia"#num" Playback", \
- .rates = (SNDRV_PCM_RATE_8000_192000| \
- SNDRV_PCM_RATE_KNOT), \
+ .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, \
@@ -132,8 +144,9 @@ static struct snd_pcm_hardware q6asm_dai_hardware_playback = {
}, \
.capture = { \
.stream_name = "MultiMedia"#num" Capture", \
- .rates = (SNDRV_PCM_RATE_8000_48000| \
- SNDRV_PCM_RATE_KNOT), \
+ .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, \
@@ -145,18 +158,6 @@ static struct snd_pcm_hardware q6asm_dai_hardware_playback = {
.id = MSM_FRONTEND_DAI_MULTIMEDIA##num, \
}
-/* Conventional and unconventional sample rate supported */
-static unsigned int supported_sample_rates[] = {
- 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
- 88200, 96000, 176400, 192000
-};
-
-static struct snd_pcm_hw_constraint_list constraints_sample_rates = {
- .count = ARRAY_SIZE(supported_sample_rates),
- .list = supported_sample_rates,
- .mask = 0,
-};
-
static const struct snd_compr_codec_caps q6asm_compr_caps = {
.num_descriptors = 1,
.descriptor[0].max_ch = 2,
@@ -181,8 +182,8 @@ static void event_handler(uint32_t opcode, uint32_t token,
switch (opcode) {
case ASM_CLIENT_EVENT_CMD_RUN_DONE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- q6asm_write_async(prtd->audio_client,
- prtd->pcm_count, 0, 0, NO_TIMESTAMP);
+ q6asm_write_async(prtd->audio_client, prtd->stream_id,
+ prtd->pcm_count, 0, 0, 0);
break;
case ASM_CLIENT_EVENT_CMD_EOS_DONE:
prtd->state = Q6ASM_STREAM_STOPPED;
@@ -191,8 +192,8 @@ static void event_handler(uint32_t opcode, uint32_t token,
prtd->pcm_irq_pos += prtd->pcm_count;
snd_pcm_period_elapsed(substream);
if (prtd->state == Q6ASM_STREAM_RUNNING)
- q6asm_write_async(prtd->audio_client,
- prtd->pcm_count, 0, 0, NO_TIMESTAMP);
+ q6asm_write_async(prtd->audio_client, prtd->stream_id,
+ prtd->pcm_count, 0, 0, 0);
break;
}
@@ -200,7 +201,7 @@ static void event_handler(uint32_t opcode, uint32_t token,
prtd->pcm_irq_pos += prtd->pcm_count;
snd_pcm_period_elapsed(substream);
if (prtd->state == Q6ASM_STREAM_RUNNING)
- q6asm_read(prtd->audio_client);
+ q6asm_read(prtd->audio_client, prtd->stream_id);
break;
default:
@@ -212,7 +213,7 @@ 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 = asoc_substream_to_rtd(substream);
+ 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;
@@ -233,7 +234,7 @@ static int q6asm_dai_prepare(struct snd_soc_component *component,
/* rate and channels are sent to audio driver */
if (prtd->state) {
/* clear the previous setup if any */
- q6asm_cmd(prtd->audio_client, CMD_CLOSE);
+ q6asm_cmd(prtd->audio_client, prtd->stream_id, CMD_CLOSE);
q6asm_unmap_memory_regions(substream->stream,
prtd->audio_client);
q6routing_stream_close(soc_prtd->dai_link->id,
@@ -252,18 +253,18 @@ static int q6asm_dai_prepare(struct snd_soc_component *component,
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM,
- 0, prtd->bits_per_sample);
+ 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, FORMAT_LINEAR_PCM,
- prtd->bits_per_sample);
+ 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__);
- q6asm_audio_client_free(prtd->audio_client);
- prtd->audio_client = NULL;
- return -ENOMEM;
+ goto open_err;
}
prtd->session_id = q6asm_get_session_id(prtd->audio_client);
@@ -271,30 +272,41 @@ static int q6asm_dai_prepare(struct snd_soc_component *component,
prtd->session_id, substream->stream);
if (ret) {
dev_err(dev, "%s: stream reg failed ret:%d\n", __func__, ret);
- return ret;
+ goto routing_err;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = q6asm_media_format_block_multi_ch_pcm(
- prtd->audio_client, runtime->rate,
- runtime->channels, NULL,
+ 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,
- runtime->rate, runtime->channels,
- prtd->bits_per_sample);
+ 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);
+ 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;
- prtd->state = Q6ASM_STREAM_RUNNING;
+ return ret;
- return 0;
+routing_err:
+ q6asm_cmd(prtd->audio_client, prtd->stream_id, CMD_CLOSE);
+open_err:
+ q6asm_unmap_memory_regions(substream->stream, prtd->audio_client);
+ q6asm_audio_client_free(prtd->audio_client);
+ prtd->audio_client = NULL;
+
+ return ret;
}
static int q6asm_dai_trigger(struct snd_soc_component *component,
@@ -308,15 +320,18 @@ static int q6asm_dai_trigger(struct snd_soc_component *component,
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0);
+ ret = q6asm_run_nowait(prtd->audio_client, prtd->stream_id,
+ 0, 0, 0);
break;
case SNDRV_PCM_TRIGGER_STOP:
prtd->state = Q6ASM_STREAM_STOPPED;
- ret = q6asm_cmd_nowait(prtd->audio_client, CMD_EOS);
+ 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, CMD_PAUSE);
+ ret = q6asm_cmd_nowait(prtd->audio_client, prtd->stream_id,
+ CMD_PAUSE);
break;
default:
ret = -EINVAL;
@@ -330,8 +345,8 @@ 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 = asoc_substream_to_rtd(substream);
- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_prtd, 0);
+ 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;
@@ -361,16 +376,14 @@ static int q6asm_dai_open(struct snd_soc_component *component,
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;
- ret = snd_pcm_hw_constraint_list(runtime, 0,
- SNDRV_PCM_HW_PARAM_RATE,
- &constraints_sample_rates);
- if (ret < 0)
- dev_info(dev, "snd_pcm_hw_constraint_list failed\n");
/* Ensure that buffer size is a multiple of period size */
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
@@ -413,8 +426,6 @@ static int q6asm_dai_open(struct snd_soc_component *component,
else
prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32);
- snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
-
return 0;
}
@@ -422,12 +433,13 @@ 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 = asoc_substream_to_rtd(substream);
+ 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_cmd(prtd->audio_client, CMD_CLOSE);
+ q6asm_cmd(prtd->audio_client, prtd->stream_id,
+ CMD_CLOSE);
q6asm_unmap_memory_regions(substream->stream,
prtd->audio_client);
@@ -453,18 +465,6 @@ static snd_pcm_uframes_t q6asm_dai_pointer(struct snd_soc_component *component,
return bytes_to_frames(runtime, (prtd->pcm_irq_pos));
}
-static int q6asm_dai_mmap(struct snd_soc_component *component,
- struct snd_pcm_substream *substream,
- struct vm_area_struct *vma)
-{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct device *dev = component->dev;
-
- return dma_mmap_coherent(dev, vma,
- runtime->dma_area, runtime->dma_addr,
- runtime->dma_bytes);
-}
-
static int q6asm_dai_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
@@ -493,14 +493,21 @@ static void compress_event_handler(uint32_t opcode, uint32_t token,
struct q6asm_dai_rtd *prtd = priv;
struct snd_compr_stream *substream = prtd->cstream;
unsigned long flags;
+ u32 wflags = 0;
uint64_t avail;
+ uint32_t bytes_written, bytes_to_write;
+ bool is_last_buffer = false;
switch (opcode) {
case ASM_CLIENT_EVENT_CMD_RUN_DONE:
spin_lock_irqsave(&prtd->lock, flags);
if (!prtd->bytes_sent) {
- q6asm_write_async(prtd->audio_client, prtd->pcm_count,
- 0, 0, NO_TIMESTAMP);
+ 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;
}
@@ -508,13 +515,37 @@ static void compress_event_handler(uint32_t opcode, uint32_t token,
break;
case ASM_CLIENT_EVENT_CMD_EOS_DONE:
- prtd->state = Q6ASM_STREAM_STOPPED;
+ spin_lock_irqsave(&prtd->lock, flags);
+ 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;
+
+ } else {
+ prtd->state = Q6ASM_STREAM_STOPPED;
+ }
+ spin_unlock_irqrestore(&prtd->lock, flags);
break;
case ASM_CLIENT_EVENT_DATA_WRITE_DONE:
spin_lock_irqsave(&prtd->lock, flags);
- prtd->copied_total += prtd->pcm_count;
+ bytes_written = token >> ASM_WRITE_TOKEN_LEN_SHIFT;
+ prtd->copied_total += bytes_written;
snd_compr_fragment_elapsed(substream);
if (prtd->state != Q6ASM_STREAM_RUNNING) {
@@ -523,13 +554,32 @@ static void compress_event_handler(uint32_t opcode, uint32_t token,
}
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 (avail >= prtd->pcm_count) {
- q6asm_write_async(prtd->audio_client,
- prtd->pcm_count, 0, 0, NO_TIMESTAMP);
- prtd->bytes_sent += prtd->pcm_count;
+ 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);
+
spin_unlock_irqrestore(&prtd->lock, flags);
break;
@@ -543,7 +593,7 @@ static int q6asm_dai_compr_open(struct snd_soc_component *component,
{
struct snd_soc_pcm_runtime *rtd = stream->private_data;
struct snd_compr_runtime *runtime = stream->runtime;
- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ 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;
@@ -560,6 +610,9 @@ static int q6asm_dai_compr_open(struct snd_soc_component *component,
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,
@@ -606,8 +659,15 @@ static int q6asm_dai_compr_free(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd = stream->private_data;
if (prtd->audio_client) {
- if (prtd->state)
- q6asm_cmd(prtd->audio_client, CMD_CLOSE);
+ if (prtd->state) {
+ 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);
+ }
+ }
snd_dma_free_pages(&prtd->dma_buffer);
q6asm_unmap_memory_regions(stream->direction,
@@ -621,15 +681,13 @@ static int q6asm_dai_compr_free(struct snd_soc_component *component,
return 0;
}
-static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
- struct snd_compr_stream *stream,
- struct snd_compr_params *params)
+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 snd_soc_pcm_runtime *rtd = stream->private_data;
- int dir = stream->direction;
- struct q6asm_dai_data *pdata;
struct q6asm_flac_cfg flac_cfg;
struct q6asm_wma_cfg wma_cfg;
struct q6asm_alac_cfg alac_cfg;
@@ -643,52 +701,18 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
struct snd_dec_alac *alac;
struct snd_dec_ape *ape;
- codec_options = &(prtd->codec_param.codec.options);
-
-
- memcpy(&prtd->codec_param, params, sizeof(*params));
-
- 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, params->codec.id,
- params->codec.profile, prtd->bits_per_sample);
-
- if (ret < 0) {
- dev_err(dev, "q6asm_open_write failed\n");
- q6asm_audio_client_free(prtd->audio_client);
- prtd->audio_client = NULL;
- return ret;
- }
- }
+ codec_options = &(prtd->codec.options);
- 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);
- return ret;
- }
+ memcpy(&prtd->codec, codec, sizeof(*codec));
- switch (params->codec.id) {
+ 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 = params->codec.ch_in;
- flac_cfg.sample_rate = params->codec.sample_rate;
+ 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;
@@ -697,6 +721,7 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
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);
@@ -709,10 +734,10 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
memset(&wma_cfg, 0x0, sizeof(struct q6asm_wma_cfg));
- wma_cfg.sample_rate = params->codec.sample_rate;
- wma_cfg.num_channels = params->codec.ch_in;
- wma_cfg.bytes_per_sec = params->codec.bit_rate / 8;
- wma_cfg.block_align = params->codec.align;
+ 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;
@@ -726,7 +751,7 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
return -EINVAL;
/* check the codec profile */
- switch (params->codec.profile) {
+ switch (codec->profile) {
case SND_AUDIOPROFILE_WMA9:
wma_cfg.fmtag = 0x161;
wma_v9 = 1;
@@ -750,16 +775,18 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
default:
dev_err(dev, "Unknown WMA profile:%x\n",
- params->codec.profile);
+ codec->profile);
return -EIO;
}
if (wma_v9)
ret = q6asm_stream_media_format_block_wma_v9(
- prtd->audio_client, &wma_cfg);
+ prtd->audio_client, stream_id,
+ &wma_cfg);
else
ret = q6asm_stream_media_format_block_wma_v10(
- prtd->audio_client, &wma_cfg);
+ prtd->audio_client, stream_id,
+ &wma_cfg);
if (ret < 0) {
dev_err(dev, "WMA9 CMD failed:%d\n", ret);
return -EIO;
@@ -770,10 +797,10 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
memset(&alac_cfg, 0x0, sizeof(alac_cfg));
alac = &codec_options->alac_d;
- alac_cfg.sample_rate = params->codec.sample_rate;
- alac_cfg.avg_bit_rate = params->codec.bit_rate;
+ 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 = params->codec.ch_in;
+ alac_cfg.num_channels = codec->ch_in;
alac_cfg.frame_length = alac->frame_length;
alac_cfg.pb = alac->pb;
@@ -783,7 +810,7 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
alac_cfg.compatible_version = alac->compatible_version;
alac_cfg.max_frame_bytes = alac->max_frame_bytes;
- switch (params->codec.ch_in) {
+ switch (codec->ch_in) {
case 1:
alac_cfg.channel_layout_tag = ALAC_CH_LAYOUT_MONO;
break;
@@ -792,6 +819,7 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
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);
@@ -803,8 +831,8 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
memset(&ape_cfg, 0x0, sizeof(ape_cfg));
ape = &codec_options->ape_d;
- ape_cfg.sample_rate = params->codec.sample_rate;
- ape_cfg.num_channels = params->codec.ch_in;
+ 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;
@@ -816,6 +844,7 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
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);
@@ -827,18 +856,132 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component,
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 q6_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);
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto q6_err;
}
prtd->state = Q6ASM_STREAM_RUNNING;
return 0;
+
+q6_err:
+ q6asm_cmd(prtd->audio_client, prtd->stream_id, CMD_CLOSE);
+
+open_err:
+ q6asm_audio_client_free(prtd->audio_client);
+ prtd->audio_client = NULL;
+ 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,
@@ -852,15 +995,26 @@ static int q6asm_dai_compr_trigger(struct snd_soc_component *component,
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0);
+ ret = q6asm_run_nowait(prtd->audio_client, prtd->stream_id,
+ 0, 0, 0);
break;
case SNDRV_PCM_TRIGGER_STOP:
prtd->state = Q6ASM_STREAM_STOPPED;
- ret = q6asm_cmd_nowait(prtd->audio_client, CMD_EOS);
+ 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, CMD_PAUSE);
+ 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;
@@ -888,16 +1042,71 @@ static int q6asm_dai_compr_pointer(struct snd_soc_component *component,
return 0;
}
-static int q6asm_dai_compr_ack(struct snd_soc_component *component,
- struct snd_compr_stream *stream,
- size_t count)
+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;
unsigned long flags;
+ u32 wflags = 0;
+ int avail, bytes_in_flight = 0;
+ void *dstn;
+ size_t copy;
+ u32 app_pointer;
+ u32 bytes_received;
+
+ bytes_received = prtd->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);
+
+ app_pointer = bytes_received/prtd->pcm_size;
+ app_pointer = bytes_received - (app_pointer * 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;
+ }
spin_lock_irqsave(&prtd->lock, flags);
- prtd->bytes_received += count;
+
+ 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;
+ }
+
spin_unlock_irqrestore(&prtd->lock, flags);
return count;
@@ -950,81 +1159,61 @@ static int q6asm_dai_compr_get_codec_caps(struct snd_soc_component *component,
return 0;
}
-static struct snd_compress_ops q6asm_dai_compress_ops = {
+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,
- .ack = q6asm_dai_compr_ack,
+ .copy = q6asm_compr_copy,
};
static int q6asm_dai_pcm_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
- struct snd_pcm_substream *psubstream, *csubstream;
struct snd_pcm *pcm = rtd->pcm;
- struct device *dev;
- int size, ret;
-
- dev = component->dev;
- size = q6asm_dai_hardware_playback.buffer_bytes_max;
- psubstream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
- if (psubstream) {
- ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size,
- &psubstream->dma_buffer);
- if (ret) {
- dev_err(dev, "Cannot allocate buffer(s)\n");
- return ret;
- }
- }
+ size_t size = q6asm_dai_hardware_playback.buffer_bytes_max;
- csubstream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
- if (csubstream) {
- ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size,
- &csubstream->dma_buffer);
- if (ret) {
- dev_err(dev, "Cannot allocate buffer(s)\n");
- if (psubstream)
- snd_dma_free_pages(&psubstream->dma_buffer);
- return ret;
- }
- }
-
- return 0;
+ return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ component->dev, size);
}
-static void q6asm_dai_pcm_free(struct snd_soc_component *component,
- struct snd_pcm *pcm)
-{
- struct snd_pcm_substream *substream;
- int i;
-
- for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) {
- substream = pcm->streams[i].substream;
- if (substream) {
- snd_dma_free_pages(&substream->dma_buffer);
- substream->dma_buffer.area = NULL;
- substream->dma_buffer.addr = 0;
- }
- }
-}
+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,
- .pointer = q6asm_dai_pointer,
- .mmap = q6asm_dai_mmap,
- .pcm_construct = q6asm_dai_pcm_new,
- .pcm_destruct = q6asm_dai_pcm_free,
- .compress_ops = &q6asm_dai_compress_ops,
+ .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,
+ .pointer = q6asm_dai_pointer,
+ .pcm_construct = 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[] = {
@@ -1038,6 +1227,10 @@ static struct snd_soc_dai_driver q6asm_fe_dais_template[] = {
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)
{
@@ -1080,7 +1273,7 @@ static int of_q6asm_parse_dai_data(struct device *dev,
dai_drv->playback = empty_stream;
if (of_property_read_bool(node, "is-compress-dai"))
- dai_drv->compress_new = snd_soc_new_compress;
+ dai_drv->ops = &q6asm_dai_ops;
}
return 0;
@@ -1114,11 +1307,13 @@ static int q6asm_dai_probe(struct platform_device *pdev)
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 = {
diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c
index 755062eadcc8..06a802f9dba5 100644
--- a/sound/soc/qcom/qdsp6/q6asm.c
+++ b/sound/soc/qcom/qdsp6/q6asm.c
@@ -2,6 +2,7 @@
// 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>
@@ -51,6 +52,8 @@
#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
@@ -270,7 +273,6 @@ struct audio_client {
wait_queue_head_t cmd_wait;
struct aprv2_ibasic_rsp_result_t result;
int perf_mode;
- int stream_id;
struct q6asm *q6asm;
struct device *dev;
};
@@ -490,7 +492,7 @@ static int __q6asm_memory_map_regions(struct audio_client *ac, int dir,
*
* @dir: direction of audio stream
* @ac: audio client instanace
- * @phys: physcial address that needs mapping.
+ * @phys: physical address that needs mapping.
* @period_sz: audio period size
* @periods: number of periods
*
@@ -512,7 +514,7 @@ int q6asm_map_memory_regions(unsigned int dir, struct audio_client *ac,
return 0;
}
- buf = kzalloc(((sizeof(struct audio_buffer)) * periods), GFP_ATOMIC);
+ buf = kcalloc(periods, sizeof(*buf), GFP_ATOMIC);
if (!buf) {
spin_unlock_irqrestore(&ac->lock, flags);
return -ENOMEM;
@@ -640,6 +642,8 @@ static int32_t q6asm_stream_callback(struct apr_device *adev,
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",
@@ -671,6 +675,7 @@ static int32_t q6asm_stream_callback(struct apr_device *adev,
if (ac->io_mode & ASM_SYNC_IO_MODE) {
phys_addr_t phys;
unsigned long flags;
+ int token = hdr->token & ASM_WRITE_TOKEN_MASK;
spin_lock_irqsave(&ac->lock, flags);
@@ -682,12 +687,12 @@ static int32_t q6asm_stream_callback(struct apr_device *adev,
goto done;
}
- phys = port->buf[hdr->token].phys;
+ 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[hdr->token].phys);
+ &port->buf[token].phys);
spin_unlock_irqrestore(&ac->lock, flags);
ret = -EINVAL;
goto done;
@@ -828,21 +833,21 @@ EXPORT_SYMBOL_GPL(q6asm_get_session_id);
* @dev: Pointer to asm child device.
* @cb: event callback.
* @priv: private data associated with this client.
- * @stream_id: stream id
+ * @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 stream_id,
+ 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, stream_id + 1);
+ ac = q6asm_get_audio_client(a, session_id + 1);
if (ac) {
dev_err(dev, "Audio Client already active\n");
return ac;
@@ -853,17 +858,15 @@ struct audio_client *q6asm_audio_client_alloc(struct device *dev, q6asm_cb cb,
return ERR_PTR(-ENOMEM);
spin_lock_irqsave(&a->slock, flags);
- a->session[stream_id + 1] = ac;
+ a->session[session_id + 1] = ac;
spin_unlock_irqrestore(&a->slock, flags);
- ac->session = stream_id + 1;
+ 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;
- /* DSP expects stream id from 1 */
- ac->stream_id = 1;
ac->adev = a->adev;
kref_init(&ac->refcount);
@@ -913,14 +916,17 @@ err:
/**
* 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 format,
- u32 codec_profile, uint16_t bits_per_sample)
+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;
@@ -935,11 +941,13 @@ int q6asm_open_write(struct audio_client *ac, uint32_t format,
pkt = p;
open = p + APR_HDR_SIZE;
- q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id);
+ 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;
@@ -998,8 +1006,9 @@ err:
}
EXPORT_SYMBOL_GPL(q6asm_open_write);
-static int __q6asm_run(struct audio_client *ac, uint32_t flags,
- uint32_t msw_ts, uint32_t lsw_ts, bool wait)
+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;
@@ -1014,7 +1023,7 @@ static int __q6asm_run(struct audio_client *ac, uint32_t flags,
pkt = p;
run = p + APR_HDR_SIZE;
- q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id);
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id);
pkt->hdr.opcode = ASM_SESSION_CMD_RUN_V2;
run->flags = flags;
@@ -1036,16 +1045,17 @@ static int __q6asm_run(struct audio_client *ac, uint32_t flags,
* 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 flags,
+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, flags, msw_ts, lsw_ts, true);
+ return __q6asm_run(ac, stream_id, flags, msw_ts, lsw_ts, true);
}
EXPORT_SYMBOL_GPL(q6asm_run);
@@ -1053,16 +1063,17 @@ 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 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)
{
- return __q6asm_run(ac, flags, msw_ts, lsw_ts, false);
+ return __q6asm_run(ac, stream_id, flags, msw_ts, lsw_ts, false);
}
EXPORT_SYMBOL_GPL(q6asm_run_nowait);
@@ -1070,6 +1081,7 @@ 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
@@ -1078,6 +1090,7 @@ EXPORT_SYMBOL_GPL(q6asm_run_nowait);
* 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)
@@ -1096,7 +1109,7 @@ int q6asm_media_format_block_multi_ch_pcm(struct audio_client *ac,
pkt = p;
fmt = p + APR_HDR_SIZE;
- q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id);
+ 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);
@@ -1125,8 +1138,8 @@ err:
}
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;
@@ -1142,7 +1155,7 @@ int q6asm_stream_media_format_block_flac(struct audio_client *ac,
pkt = p;
fmt = p + APR_HDR_SIZE;
- q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id);
+ 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);
@@ -1163,6 +1176,7 @@ int q6asm_stream_media_format_block_flac(struct audio_client *ac,
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;
@@ -1178,7 +1192,7 @@ int q6asm_stream_media_format_block_wma_v9(struct audio_client *ac,
pkt = p;
fmt = p + APR_HDR_SIZE;
- q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id);
+ 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);
@@ -1200,6 +1214,7 @@ int q6asm_stream_media_format_block_wma_v9(struct audio_client *ac,
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;
@@ -1215,7 +1230,7 @@ int q6asm_stream_media_format_block_wma_v10(struct audio_client *ac,
pkt = p;
fmt = p + APR_HDR_SIZE;
- q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id);
+ 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);
@@ -1238,6 +1253,7 @@ int q6asm_stream_media_format_block_wma_v10(struct audio_client *ac,
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;
@@ -1253,7 +1269,7 @@ int q6asm_stream_media_format_block_alac(struct audio_client *ac,
pkt = p;
fmt = p + APR_HDR_SIZE;
- q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id);
+ 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);
@@ -1279,6 +1295,7 @@ int q6asm_stream_media_format_block_alac(struct audio_client *ac,
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;
@@ -1294,7 +1311,7 @@ int q6asm_stream_media_format_block_ape(struct audio_client *ac,
pkt = p;
fmt = p + APR_HDR_SIZE;
- q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id);
+ 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);
@@ -1317,10 +1334,60 @@ int q6asm_stream_media_format_block_ape(struct audio_client *ac,
}
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;
+ void *p;
+ int rc, pkt_size;
+
+ pkt_size = APR_HDR_SIZE + sizeof(uint32_t);
+ p = 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;
+
+ kfree(pkt);
+
+ 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
@@ -1328,7 +1395,9 @@ EXPORT_SYMBOL_GPL(q6asm_stream_media_format_block_ape);
* 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 rate, uint32_t channels, uint16_t bits_per_sample)
+ 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;
@@ -1344,7 +1413,7 @@ int q6asm_enc_cfg_blk_pcm_format_support(struct audio_client *ac,
pkt = p;
enc_cfg = p + APR_HDR_SIZE;
- q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id);
+ 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;
@@ -1376,10 +1445,11 @@ 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)
+int q6asm_read(struct audio_client *ac, uint32_t stream_id)
{
struct asm_data_cmd_read_v2 *read;
struct audio_port_data *port;
@@ -1400,7 +1470,7 @@ int q6asm_read(struct audio_client *ac)
spin_lock_irqsave(&ac->lock, flags);
port = &ac->port[SNDRV_PCM_STREAM_CAPTURE];
- q6asm_add_hdr(ac, &pkt->hdr, pkt_size, false, ac->stream_id);
+ 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);
@@ -1428,7 +1498,7 @@ int q6asm_read(struct audio_client *ac)
}
EXPORT_SYMBOL_GPL(q6asm_read);
-static int __q6asm_open_read(struct audio_client *ac,
+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;
@@ -1444,7 +1514,7 @@ static int __q6asm_open_read(struct audio_client *ac,
pkt = p;
open = p + APR_HDR_SIZE;
- q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id);
+ 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;
@@ -1475,15 +1545,16 @@ static int __q6asm_open_read(struct audio_client *ac,
* 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 format,
- uint16_t bits_per_sample)
+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, format, bits_per_sample);
+ return __q6asm_open_read(ac, stream_id, format, bits_per_sample);
}
EXPORT_SYMBOL_GPL(q6asm_open_read);
@@ -1491,6 +1562,7 @@ 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
@@ -1498,8 +1570,8 @@ EXPORT_SYMBOL_GPL(q6asm_open_read);
*
* Return: Will be an negative value on error or zero on success
*/
-int q6asm_write_async(struct audio_client *ac, uint32_t len, uint32_t msw_ts,
- uint32_t lsw_ts, uint32_t wflags)
+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;
@@ -1520,10 +1592,10 @@ int q6asm_write_async(struct audio_client *ac, uint32_t len, uint32_t msw_ts,
spin_lock_irqsave(&ac->lock, flags);
port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK];
- q6asm_add_hdr(ac, &pkt->hdr, pkt_size, false, ac->stream_id);
+ q6asm_add_hdr(ac, &pkt->hdr, pkt_size, false, stream_id);
ab = &port->buf[port->dsp_buf];
- pkt->hdr.token = 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);
@@ -1534,10 +1606,7 @@ int q6asm_write_async(struct audio_client *ac, uint32_t len, uint32_t msw_ts,
write->mem_map_handle =
ac->port[SNDRV_PCM_STREAM_PLAYBACK].mem_map_handle;
- if (wflags == NO_TIMESTAMP)
- write->flags = (wflags & 0x800000FF);
- else
- write->flags = (0x80000000 | wflags);
+ write->flags = wflags;
port->dsp_buf++;
@@ -1556,7 +1625,7 @@ EXPORT_SYMBOL_GPL(q6asm_write_async);
static void q6asm_reset_buf_state(struct audio_client *ac)
{
- struct audio_port_data *port = NULL;
+ struct audio_port_data *port;
unsigned long flags;
spin_lock_irqsave(&ac->lock, flags);
@@ -1567,9 +1636,9 @@ static void q6asm_reset_buf_state(struct audio_client *ac)
spin_unlock_irqrestore(&ac->lock, flags);
}
-static int __q6asm_cmd(struct audio_client *ac, int cmd, bool wait)
+static int __q6asm_cmd(struct audio_client *ac, uint32_t stream_id, int cmd,
+ bool wait)
{
- int stream_id = ac->stream_id;
struct apr_pkt pkt;
int rc;
@@ -1616,13 +1685,14 @@ static int __q6asm_cmd(struct audio_client *ac, int cmd, bool wait)
* 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, int cmd)
+int q6asm_cmd(struct audio_client *ac, uint32_t stream_id, int cmd)
{
- return __q6asm_cmd(ac, cmd, true);
+ return __q6asm_cmd(ac, stream_id, cmd, true);
}
EXPORT_SYMBOL_GPL(q6asm_cmd);
@@ -1630,13 +1700,14 @@ 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, int cmd)
+int q6asm_cmd_nowait(struct audio_client *ac, uint32_t stream_id, int cmd)
{
- return __q6asm_cmd(ac, cmd, false);
+ return __q6asm_cmd(ac, stream_id, cmd, false);
}
EXPORT_SYMBOL_GPL(q6asm_cmd_nowait);
@@ -1657,24 +1728,19 @@ static int q6asm_probe(struct apr_device *adev)
spin_lock_init(&q6asm->slock);
dev_set_drvdata(dev, q6asm);
- return of_platform_populate(dev->of_node, NULL, NULL, dev);
+ return devm_of_platform_populate(dev);
}
-static int q6asm_remove(struct apr_device *adev)
-{
- of_platform_depopulate(&adev->dev);
-
- return 0;
-}
+#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,
- .remove = q6asm_remove,
.callback = q6asm_srvc_callback,
.driver = {
.name = "qcom-q6asm",
diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h
index 38a207d6cd95..519e1b3a3f7c 100644
--- a/sound/soc/qcom/qdsp6/q6asm.h
+++ b/sound/soc/qcom/qdsp6/q6asm.h
@@ -2,7 +2,6 @@
#ifndef __Q6_ASM_H__
#define __Q6_ASM_H__
#include "q6dsp-common.h"
-#include <dt-bindings/sound/qcom,q6asm.h>
/* ASM client callback events */
#define CMD_PAUSE 0x0001
@@ -20,6 +19,9 @@
#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,
@@ -29,20 +31,20 @@ enum {
};
#define MAX_SESSIONS 8
-#define NO_TIMESTAMP 0xFF00
#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;
+ 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 {
@@ -93,41 +95,57 @@ 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 len, uint32_t msw_ts,
- uint32_t lsw_ts, uint32_t flags);
-int q6asm_open_write(struct audio_client *ac, uint32_t format,
- u32 codec_profile, uint16_t bits_per_sample);
+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 format,
- uint16_t bits_per_sample);
+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 rate, uint32_t channels, uint16_t bits_per_sample);
-int q6asm_read(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 flags, uint32_t msw_ts,
- uint32_t lsw_ts);
-int q6asm_run_nowait(struct audio_client *ac, uint32_t flags, uint32_t msw_ts,
- uint32_t lsw_ts);
-int q6asm_cmd(struct audio_client *ac, int cmd);
-int q6asm_cmd_nowait(struct audio_client *ac, int cmd);
-int q6asm_get_session_id(struct audio_client *ac);
+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 bufsz, unsigned int bufcnt);
+ size_t period_sz, unsigned int periods);
int q6asm_unmap_memory_regions(unsigned int dir, struct audio_client *ac);
#endif /* __Q6_ASM_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6core.c b/sound/soc/qcom/qdsp6/q6core.c
index ae314a652efe..49cfb32cd209 100644
--- a/sound/soc/qcom/qdsp6/q6core.c
+++ b/sound/soc/qcom/qdsp6/q6core.c
@@ -339,7 +339,7 @@ static int q6core_probe(struct apr_device *adev)
return 0;
}
-static int q6core_exit(struct apr_device *adev)
+static void q6core_exit(struct apr_device *adev)
{
struct q6core *core = dev_get_drvdata(&adev->dev);
@@ -350,15 +350,15 @@ static int q6core_exit(struct apr_device *adev)
g_core = NULL;
kfree(core);
-
- return 0;
}
+#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,
diff --git a/sound/soc/qcom/qdsp6/q6dsp-common.c b/sound/soc/qcom/qdsp6/q6dsp-common.c
index d393003492c7..f74585d88bd6 100644
--- a/sound/soc/qcom/qdsp6/q6dsp-common.c
+++ b/sound/soc/qcom/qdsp6/q6dsp-common.c
@@ -63,4 +63,41 @@ int q6dsp_map_channels(u8 ch_map[PCM_MAX_NUM_CHANNEL], int ch)
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
index 01094d108b8a..9e704db5f604 100644
--- a/sound/soc/qcom/qdsp6/q6dsp-common.h
+++ b/sound/soc/qcom/qdsp6/q6dsp-common.h
@@ -20,5 +20,6 @@
#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-lpass-clocks.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c
new file mode 100644
index 000000000000..e758411603be
--- /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 104
+#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 long clk_q6dsp_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ return rate;
+}
+
+static const struct clk_ops clk_q6dsp_ops = {
+ .prepare = clk_q6dsp_prepare,
+ .unprepare = clk_q6dsp_unprepare,
+ .set_rate = clk_q6dsp_set_rate,
+ .round_rate = clk_q6dsp_round_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 000000000000..3770d81f2bd6
--- /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 000000000000..4eed54b071a5
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c
@@ -0,0 +1,658 @@
+// 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, \
+ }
+
+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",
+ },
+ 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 QUINARY_MI2S_RX ... QUINARY_MI2S_TX:
+ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX:
+ 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 000000000000..d8dde6dd0aca
--- /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 000000000000..4c574b48ab00
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c
@@ -0,0 +1,94 @@
+// 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_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),
+ 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"),
+};
+
+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 000000000000..81554d202658
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6prm.c
@@ -0,0 +1,255 @@
+// 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
+
+struct prm_cmd_request_hw_core {
+ struct apm_module_param_data param_data;
+ uint32_t hw_clk_id;
+} __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;
+ struct gpr_pkt *pkt;
+ int rc;
+
+ 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;
+ }
+
+ pkt = 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 = PARAM_ID_RSC_HW_CORE;
+ param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE;
+
+ req->hw_clk_id = hw_block_id;
+
+ rc = q6prm_send_cmd_sync(prm, pkt, rsp_opcode);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+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;
+ int rc;
+
+ pkt = 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;
+
+ rc = q6prm_send_cmd_sync(prm, pkt, PRM_CMD_RSP_REQUEST_HW_RSC);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+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;
+ int rc;
+
+ pkt = 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;
+
+ rc = q6prm_send_cmd_sync(prm, pkt, PRM_CMD_RSP_RELEASE_HW_RSC);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+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(struct gpr_resp_pkt *data, void *priv, int op)
+{
+ gpr_device_t *gdev = priv;
+ struct q6prm *prm = dev_get_drvdata(&gdev->dev);
+ struct gpr_ibasic_rsp_result_t *result;
+ 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 000000000000..a988a32086fe
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6prm.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __Q6PRM_H__
+#define __Q6PRM_H__
+
+/* 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
+
+#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_SRC_INTERNAL 1
+#define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0
+#define Q6PRM_HW_CORE_ID_LPASS 1
+#define Q6PRM_HW_CORE_ID_DCODEC 2
+
+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
index 25d23e0266c7..f49243daa517 100644
--- a/sound/soc/qcom/qdsp6/q6routing.c
+++ b/sound/soc/qcom/qdsp6/q6routing.c
@@ -2,14 +2,15 @@
// 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/of_platform.h>
#include <linux/bitops.h>
#include <linux/mutex.h>
-#include <linux/of_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/soc.h>
@@ -66,6 +67,7 @@
{ 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" }, \
@@ -113,7 +115,19 @@
{ 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, "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"}
#define Q6ROUTING_TX_MIXERS(id) \
SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, \
@@ -128,6 +142,9 @@
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), \
@@ -268,6 +285,42 @@
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),
struct session_data {
@@ -320,6 +373,12 @@ int q6routing_stream_open(int fedai_id, int perf_mode,
}
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);
@@ -376,6 +435,7 @@ static struct session_data *get_session_from_id(struct msm_routing_data *data,
return NULL;
}
+
/**
* q6routing_stream_close() - Deregister a stream
*
@@ -440,9 +500,15 @@ static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol,
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);
}
@@ -450,6 +516,9 @@ static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol,
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) };
@@ -465,6 +534,9 @@ static const struct snd_kcontrol_new secondary_mi2s_rx_mixer_controls[] = {
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) };
@@ -609,6 +681,36 @@ static const struct snd_kcontrol_new quin_tdm_rx_6_mixer_controls[] = {
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 mmul1_mixer_controls[] = {
Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA1) };
@@ -635,24 +737,6 @@ 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[] = {
- /* Frontend AIF */
- SND_SOC_DAPM_AIF_IN("MM_DL1", "MultiMedia1 Playback", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_IN("MM_DL2", "MultiMedia2 Playback", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_IN("MM_DL3", "MultiMedia3 Playback", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_IN("MM_DL4", "MultiMedia4 Playback", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_IN("MM_DL5", "MultiMedia5 Playback", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_IN("MM_DL6", "MultiMedia6 Playback", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_IN("MM_DL7", "MultiMedia7 Playback", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_IN("MM_DL8", "MultiMedia8 Playback", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_OUT("MM_UL1", "MultiMedia1 Capture", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_OUT("MM_UL2", "MultiMedia2 Capture", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_OUT("MM_UL3", "MultiMedia3 Capture", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_OUT("MM_UL4", "MultiMedia4 Capture", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_OUT("MM_UL5", "MultiMedia5 Capture", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_OUT("MM_UL6", "MultiMedia6 Capture", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_OUT("MM_UL7", "MultiMedia7 Capture", 0, 0, 0, 0),
- SND_SOC_DAPM_AIF_OUT("MM_UL8", "MultiMedia8 Capture", 0, 0, 0, 0),
-
/* Mixer definitions */
SND_SOC_DAPM_MIXER("HDMI Mixer", SND_SOC_NOPM, 0, 0,
hdmi_mixer_controls,
@@ -692,6 +776,9 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = {
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)),
@@ -819,6 +906,40 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = {
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("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,
@@ -835,7 +956,6 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = {
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[] = {
@@ -850,6 +970,7 @@ static const struct snd_soc_dapm_route intercon[] = {
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"),
@@ -901,6 +1022,17 @@ static const struct snd_soc_dapm_route intercon[] = {
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_TX_DAPM_ROUTE("MultiMedia1 Mixer"),
Q6ROUTING_TX_DAPM_ROUTE("MultiMedia2 Mixer"),
Q6ROUTING_TX_DAPM_ROUTE("MultiMedia3 Mixer"),
@@ -924,9 +1056,9 @@ 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 = asoc_substream_to_rtd(substream);
+ 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 = asoc_rtd_to_cpu(rtd, 0)->id;
+ unsigned int be_id = snd_soc_rtd_to_cpu(rtd, 0)->id;
struct session_data *session;
int path_type;
@@ -1016,19 +1148,19 @@ static int q6pcm_routing_probe(struct platform_device *pdev)
NULL, 0);
}
-static int q6pcm_routing_remove(struct platform_device *pdev)
+static void q6pcm_routing_remove(struct platform_device *pdev)
{
kfree(routing_data);
routing_data = NULL;
-
- return 0;
}
+#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 = {
diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c
new file mode 100644
index 000000000000..ebe0c2425927
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6usb.c
@@ -0,0 +1,421 @@
+// 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_pcm_runtime *rtd;
+ struct snd_soc_dai *dai;
+
+ for_each_card_rtds(w->dapm->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 000000000000..83319a928f29
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/topology.c
@@ -0,0 +1,1317 @@
+// 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(sizeof(*info), GFP_KERNEL);
+ 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(sizeof(*sg), GFP_KERNEL);
+ 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(sizeof(*cont), GFP_KERNEL);
+ 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(sizeof(*mod), GFP_KERNEL);
+ 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 struct snd_soc_tplg_vendor_array *audioreach_get_sg_array(
+ struct snd_soc_tplg_private *private)
+{
+ struct snd_soc_tplg_vendor_array *sg_array = NULL;
+ bool found = false;
+ int sz;
+
+ for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) {
+ 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 struct snd_soc_tplg_vendor_array *audioreach_get_cont_array(
+ struct snd_soc_tplg_private *private)
+{
+ struct snd_soc_tplg_vendor_array *cont_array = NULL;
+ bool found = false;
+ int sz;
+
+ for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) {
+ 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 struct snd_soc_tplg_vendor_array *audioreach_get_module_array(
+ struct snd_soc_tplg_private *private)
+{
+ 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)); ) {
+ 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_sub_graph *audioreach_parse_sg_tokens(struct q6apm *apm,
+ struct snd_soc_tplg_private *private)
+{
+ struct snd_soc_tplg_vendor_value_elem *sg_elem;
+ 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,
+ struct snd_soc_tplg_private *private)
+{
+ struct snd_soc_tplg_vendor_value_elem *cont_elem;
+ 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,
+ struct snd_soc_tplg_private *private,
+ struct snd_soc_dapm_widget *w)
+{
+ uint32_t max_ip_port = 0, max_op_port = 0, in_port = 0, out_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;
+ struct snd_soc_tplg_vendor_value_elem *mod_elem;
+ 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_IN_PORTS:
+ in_port = le32_to_cpu(mod_elem->value);
+ break;
+ case AR_TKN_U32_MODULE_OUT_PORTS:
+ out_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->in_port = in_port;
+ mod->out_port = out_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,
+ 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(mod))
+ return PTR_ERR(mod);
+
+ 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,
+ struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+ struct snd_soc_tplg_vendor_value_elem *mod_elem;
+ 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,
+ struct snd_soc_tplg_vendor_array *mod_array)
+{
+ 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,
+ struct snd_soc_tplg_vendor_array *mod_array)
+{
+ 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,
+ struct snd_soc_tplg_vendor_array *mod_array)
+{
+ 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_dp_module_load(struct audioreach_module *mod,
+ struct snd_soc_tplg_vendor_array *mod_array)
+{
+ 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,
+ struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+ 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_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,
+ struct snd_soc_tplg_dapm_widget *tplg_w)
+{
+ struct snd_soc_tplg_vendor_value_elem *w_elem;
+ 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(sizeof(*scontrol), GFP_KERNEL);
+ 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,
+ 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)
+{
+ 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;
+
+ mod = dobj->private;
+ cont = mod->container;
+
+ if (w->id == snd_soc_dapm_mixer) {
+ /* virtual widget */
+ struct snd_ar_control *scontrol = dobj->private;
+
+ list_del(&scontrol->node);
+ kfree(scontrol);
+ return 0;
+ }
+
+ mutex_lock(&apm->lock);
+ idr_remove(&apm->modules_idr, mod->instance_id);
+ cont->num_modules--;
+
+ list_del(&mod->node);
+ 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)
+{
+ 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,
+ struct snd_ar_control *m1,
+ 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,
+ struct snd_ar_control *m1,
+ struct snd_ar_control *m2)
+{
+ 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_dapm(kcontrol);
+ struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol);
+ struct snd_soc_component *c = snd_soc_dapm_to_component(dapm);
+ struct snd_ar_control *dapm_scontrol = dw->dobj.private;
+ 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_dapm(kcontrol);
+ struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol);
+ struct snd_soc_component *c = snd_soc_dapm_to_component(dapm);
+ struct snd_ar_control *dapm_scontrol = dw->dobj.private;
+ 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_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_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,
+ struct snd_soc_tplg_ctl_hdr *hdr)
+{
+ struct snd_soc_tplg_vendor_value_elem *c_elem;
+ struct snd_soc_tplg_vendor_array *c_array;
+ struct snd_soc_tplg_mixer_control *mc;
+ int tkn_count = 0;
+
+ mc = container_of(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(sizeof(*scontrol), GFP_KERNEL);
+ 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;
+ const struct firmware *fw;
+ 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;
+
+ 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);
+ }
+
+ release_firmware(fw);
+
+ 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 000000000000..84b6ac5d2a20
--- /dev/null
+++ b/sound/soc/qcom/sc7180.c
@@ -0,0 +1,580 @@
+// 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_dapm(kcontrol);
+ struct sc7180_snd_data *data = snd_soc_card_get_drvdata(dapm->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_dapm(kcontrol);
+ struct sc7180_snd_data *data = snd_soc_card_get_drvdata(dapm->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 000000000000..af412bd0c89f
--- /dev/null
+++ b/sound/soc/qcom/sc7280.c
@@ -0,0 +1,461 @@
+// 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/mod_devicetable.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;
+ struct sdw_stream_runtime *sruntime[LPASS_MAX_PORTS];
+ 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);
+ struct snd_soc_dai *codec_dai;
+ const struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct sc7280_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card);
+ struct sdw_stream_runtime *sruntime;
+ int i;
+
+ 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);
+ }
+
+ switch (cpu_dai->id) {
+ case LPASS_CDC_DMA_TX3:
+ case LPASS_CDC_DMA_RX0:
+ case RX_CODEC_DMA_RX_0:
+ case SECONDARY_MI2S_RX:
+ case TX_CODEC_DMA_TX_3:
+ case VA_CODEC_DMA_TX_0:
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ sruntime = snd_soc_dai_get_stream(codec_dai, substream->stream);
+ if (sruntime != ERR_PTR(-ENOTSUPP))
+ pdata->sruntime[cpu_dai->id] = sruntime;
+ }
+ break;
+ }
+
+ 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);
+ struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
+ int ret;
+
+ if (!sruntime)
+ return 0;
+
+ if (data->stream_prepared[cpu_dai->id]) {
+ sdw_disable_stream(sruntime);
+ sdw_deprepare_stream(sruntime);
+ data->stream_prepared[cpu_dai->id] = false;
+ }
+
+ ret = sdw_prepare_stream(sruntime);
+ if (ret)
+ return ret;
+
+ ret = sdw_enable_stream(sruntime);
+ if (ret) {
+ sdw_deprepare_stream(sruntime);
+ return ret;
+ }
+ data->stream_prepared[cpu_dai->id] = true;
+
+ return ret;
+}
+
+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);
+ struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
+
+ 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:
+ if (sruntime && data->stream_prepared[cpu_dai->id]) {
+ sdw_disable_stream(sruntime);
+ sdw_deprepare_stream(sruntime);
+ data->stream_prepared[cpu_dai->id] = false;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+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);
+ struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
+
+ 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;
+ }
+
+ data->sruntime[cpu_dai->id] = NULL;
+ sdw_release_stream(sruntime);
+}
+
+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 000000000000..99fd34728e38
--- /dev/null
+++ b/sound/soc/qcom/sc8280xp.c
@@ -0,0 +1,211 @@
+// 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 <linux/soundwire/sdw.h>
+#include <sound/jack.h>
+#include <linux/input-event-codes.h>
+#include "qdsp6/q6afe.h"
+#include "common.h"
+#include "sdw.h"
+
+struct sc8280xp_snd_data {
+ bool stream_prepared[AFE_PORT_MAX];
+ struct snd_soc_card *card;
+ struct sdw_stream_runtime *sruntime[AFE_PORT_MAX];
+ struct snd_soc_jack jack;
+ struct snd_soc_jack dp_jack[8];
+ bool jack_setup;
+};
+
+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);
+
+ return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup);
+}
+
+static void sc8280xp_snd_shutdown(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 *pdata = snd_soc_card_get_drvdata(rtd->card);
+ struct sdw_stream_runtime *sruntime = pdata->sruntime[cpu_dai->id];
+
+ pdata->sruntime[cpu_dai->id] = NULL;
+ sdw_release_stream(sruntime);
+}
+
+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);
+
+ rate->min = rate->max = 48000;
+ 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 *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct sc8280xp_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card);
+
+ return qcom_snd_sdw_hw_params(substream, params, &pdata->sruntime[cpu_dai->id]);
+}
+
+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);
+ struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
+
+ return qcom_snd_sdw_prepare(substream, sruntime,
+ &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);
+ struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
+
+ return qcom_snd_sdw_hw_free(substream, sruntime,
+ &data->stream_prepared[cpu_dai->id]);
+}
+
+static const struct snd_soc_ops sc8280xp_be_ops = {
+ .startup = qcom_snd_sdw_startup,
+ .shutdown = sc8280xp_snd_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;
+ 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);
+ sc8280xp_add_be_ops(card);
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static const struct of_device_id snd_sc8280xp_dt_match[] = {
+ {.compatible = "qcom,qcm6490-idp-sndcard", "qcm6490"},
+ {.compatible = "qcom,qcs6490-rb3gen2-sndcard", "qcs6490"},
+ {.compatible = "qcom,qcs9075-sndcard", "qcs9075"},
+ {.compatible = "qcom,qcs9100-sndcard", "qcs9100"},
+ {.compatible = "qcom,sc8280xp-sndcard", "sc8280xp"},
+ {.compatible = "qcom,sm8450-sndcard", "sm8450"},
+ {.compatible = "qcom,sm8550-sndcard", "sm8550"},
+ {.compatible = "qcom,sm8650-sndcard", "sm8650"},
+ {.compatible = "qcom,sm8750-sndcard", "sm8750"},
+ {}
+};
+
+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
index ab1bf23c21a6..a233b80049ee 100644
--- a/sound/soc/qcom/sdm845.c
+++ b/sound/soc/qcom/sdm845.c
@@ -3,9 +3,9 @@
* 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 <linux/of_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -15,8 +15,10 @@
#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
@@ -26,18 +28,30 @@
#define SPK_TDM_RX_MASK 0x03
#define NUM_TDM_SLOTS 8
#define SLIM_MAX_TX_PORTS 16
-#define SLIM_MAX_RX_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 stream_prepared[SLIM_MAX_RX_PORTS];
+ bool slim_port_setup;
+ bool stream_prepared[AFE_PORT_MAX];
struct snd_soc_card *card;
uint32_t pri_mi2s_clk_count;
uint32_t sec_mi2s_clk_count;
uint32_t quat_tdm_clk_count;
- struct sdw_stream_runtime *sruntime[SLIM_MAX_RX_PORTS];
+ struct sdw_stream_runtime *sruntime[AFE_PORT_MAX];
+};
+
+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};
@@ -45,8 +59,8 @@ 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 = asoc_substream_to_rtd(substream);
- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ 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;
struct sdm845_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card);
u32 rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS];
@@ -55,8 +69,8 @@ static int sdm845_slim_snd_hw_params(struct snd_pcm_substream *substream,
int ret = 0, i;
for_each_rtd_codec_dais(rtd, i, codec_dai) {
- sruntime = snd_soc_dai_get_sdw_stream(codec_dai,
- substream->stream);
+ sruntime = snd_soc_dai_get_stream(codec_dai,
+ substream->stream);
if (sruntime != ERR_PTR(-ENOTSUPP))
pdata->sruntime[cpu_dai->id] = sruntime;
@@ -77,6 +91,10 @@ static int sdm845_slim_snd_hw_params(struct snd_pcm_substream *substream,
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;
@@ -85,8 +103,8 @@ static int sdm845_slim_snd_hw_params(struct snd_pcm_substream *substream,
static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ 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;
@@ -170,9 +188,9 @@ end:
static int sdm845_snd_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
- struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ 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) {
@@ -201,6 +219,7 @@ static int sdm845_snd_hw_params(struct snd_pcm_substream *substream,
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);
@@ -220,9 +239,10 @@ 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 = asoc_rtd_to_codec(rtd, 0);
- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ 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
@@ -239,12 +259,14 @@ static int sdm845_dai_init(struct snd_soc_pcm_runtime *rtd)
if (!pdata->jack_setup) {
- rval = snd_soc_card_jack_new(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, NULL, 0);
+ 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");
@@ -275,6 +297,10 @@ static int sdm845_dai_init(struct snd_soc_pcm_runtime *rtd)
}
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),
@@ -287,7 +313,17 @@ static int sdm845_dai_init(struct snd_soc_pcm_runtime *rtd)
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;
@@ -299,13 +335,13 @@ static int sdm845_dai_init(struct snd_soc_pcm_runtime *rtd)
static int sdm845_snd_startup(struct snd_pcm_substream *substream)
{
- unsigned int fmt = SND_SOC_DAIFMT_CBS_CFS;
- unsigned int codec_dai_fmt = SND_SOC_DAIFMT_CBS_CFS;
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(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 = asoc_rtd_to_cpu(rtd, 0);
- struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ 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;
@@ -325,6 +361,7 @@ static int sdm845_snd_startup(struct snd_pcm_substream *substream)
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) {
@@ -339,9 +376,7 @@ static int sdm845_snd_startup(struct snd_pcm_substream *substream)
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, SND_SOC_DAIFMT_CBS_CFS);
-
-
+ snd_soc_dai_set_fmt(cpu_dai, fmt);
break;
case QUATERNARY_TDM_RX_0:
@@ -386,15 +421,16 @@ static int sdm845_snd_startup(struct snd_pcm_substream *substream)
pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
break;
}
- return 0;
+ return qcom_snd_sdw_startup(substream);
}
static void sdm845_snd_shutdown(struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(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 = asoc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
switch (cpu_dai->id) {
case PRIMARY_MI2S_RX:
@@ -409,6 +445,7 @@ static void sdm845_snd_shutdown(struct snd_pcm_substream *substream)
}
break;
+ case SECONDARY_MI2S_RX:
case SECONDARY_MI2S_TX:
if (--(data->sec_mi2s_clk_count) == 0) {
snd_soc_dai_set_sysclk(cpu_dai,
@@ -433,13 +470,16 @@ static void sdm845_snd_shutdown(struct snd_pcm_substream *substream)
pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
break;
}
+
+ data->sruntime[cpu_dai->id] = NULL;
+ sdw_release_stream(sruntime);
}
static int sdm845_snd_prepare(struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(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 = asoc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
int ret;
@@ -476,9 +516,9 @@ static int sdm845_snd_prepare(struct snd_pcm_substream *substream)
static int sdm845_snd_hw_free(struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(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 = asoc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
if (sruntime && data->stream_prepared[cpu_dai->id]) {
@@ -522,6 +562,11 @@ static const struct snd_soc_dapm_widget sdm845_snd_widgets[] = {
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;
@@ -552,8 +597,11 @@ static int sdm845_snd_platform_probe(struct platform_device *pdev)
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);
@@ -570,6 +618,7 @@ static int sdm845_snd_platform_probe(struct platform_device *pdev)
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" },
{},
@@ -586,4 +635,4 @@ static struct platform_driver sdm845_snd_driver = {
module_platform_driver(sdm845_snd_driver);
MODULE_DESCRIPTION("sdm845 ASoC Machine Driver");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/qcom/sdw.c b/sound/soc/qcom/sdw.c
new file mode 100644
index 000000000000..7d7981d4295b
--- /dev/null
+++ b/sound/soc/qcom/sdw.c
@@ -0,0 +1,192 @@
+// 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,q6afe.h>
+#include <linux/module.h>
+#include <sound/soc.h>
+#include "sdw.h"
+
+/**
+ * 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;
+
+ 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,
+ struct sdw_stream_runtime *sruntime,
+ 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);
+ int ret;
+
+ if (!sruntime)
+ return 0;
+
+ switch (cpu_dai->id) {
+ 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 TX_CODEC_DMA_TX_0:
+ case TX_CODEC_DMA_TX_1:
+ case TX_CODEC_DMA_TX_2:
+ case TX_CODEC_DMA_TX_3:
+ break;
+ default:
+ 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);
+
+int qcom_snd_sdw_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct sdw_stream_runtime **psruntime)
+{
+ 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;
+
+ switch (cpu_dai->id) {
+ case WSA_CODEC_DMA_RX_0:
+ case RX_CODEC_DMA_RX_0:
+ case RX_CODEC_DMA_RX_1:
+ 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) {
+ sruntime = snd_soc_dai_get_stream(codec_dai, substream->stream);
+ if (sruntime != ERR_PTR(-ENOTSUPP))
+ *psruntime = sruntime;
+ }
+ break;
+ }
+
+ return 0;
+
+}
+EXPORT_SYMBOL_GPL(qcom_snd_sdw_hw_params);
+
+int qcom_snd_sdw_hw_free(struct snd_pcm_substream *substream,
+ struct sdw_stream_runtime *sruntime, 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);
+
+ switch (cpu_dai->id) {
+ 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 TX_CODEC_DMA_TX_0:
+ case TX_CODEC_DMA_TX_1:
+ case TX_CODEC_DMA_TX_2:
+ case TX_CODEC_DMA_TX_3:
+ if (sruntime && *stream_prepared) {
+ sdw_disable_stream(sruntime);
+ sdw_deprepare_stream(sruntime);
+ *stream_prepared = false;
+ }
+ break;
+ default:
+ break;
+ }
+
+ 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 000000000000..392e3455f1b1
--- /dev/null
+++ b/sound/soc/qcom/sdw.h
@@ -0,0 +1,19 @@
+/* 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);
+int qcom_snd_sdw_prepare(struct snd_pcm_substream *substream,
+ struct sdw_stream_runtime *runtime,
+ bool *stream_prepared);
+int qcom_snd_sdw_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct sdw_stream_runtime **psruntime);
+int qcom_snd_sdw_hw_free(struct snd_pcm_substream *substream,
+ struct sdw_stream_runtime *sruntime,
+ bool *stream_prepared);
+#endif
diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c
new file mode 100644
index 000000000000..b70b2a5031df
--- /dev/null
+++ b/sound/soc/qcom/sm8250.c
@@ -0,0 +1,227 @@
+// 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 DRIVER_NAME "sm8250"
+#define MI2S_BCLK_RATE 1536000
+
+struct sm8250_snd_data {
+ bool stream_prepared[AFE_PORT_MAX];
+ struct snd_soc_card *card;
+ struct sdw_stream_runtime *sruntime[AFE_PORT_MAX];
+ struct snd_soc_jack jack;
+ struct snd_soc_jack usb_offload_jack;
+ bool usb_offload_jack_setup;
+ 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);
+ int ret;
+
+ if (cpu_dai->id == USB_RX)
+ ret = qcom_snd_usb_offload_jack_setup(rtd, &data->usb_offload_jack,
+ &data->usb_offload_jack_setup);
+ else
+ ret = qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup);
+ return ret;
+}
+
+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;
+ default:
+ break;
+ }
+
+ return qcom_snd_sdw_startup(substream);
+}
+
+static void sm8250_snd_shutdown(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);
+ struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
+
+ data->sruntime[cpu_dai->id] = NULL;
+ sdw_release_stream(sruntime);
+}
+
+static int sm8250_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 sm8250_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card);
+
+ return qcom_snd_sdw_hw_params(substream, params, &pdata->sruntime[cpu_dai->id]);
+}
+
+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);
+ struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
+
+ return qcom_snd_sdw_prepare(substream, sruntime,
+ &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);
+ struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
+
+ return qcom_snd_sdw_hw_free(substream, sruntime,
+ &data->stream_prepared[cpu_dai->id]);
+}
+
+static const struct snd_soc_ops sm8250_be_ops = {
+ .startup = sm8250_snd_startup,
+ .shutdown = sm8250_snd_shutdown,
+ .hw_params = sm8250_snd_hw_params,
+ .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 = DRIVER_NAME;
+ sm8250_add_be_ops(card);
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static const struct of_device_id snd_sm8250_dt_match[] = {
+ {.compatible = "qcom,sm8250-sndcard"},
+ {.compatible = "qcom,qrb4210-rb2-sndcard"},
+ {.compatible = "qcom,qrb5165-rb5-sndcard"},
+ {}
+};
+
+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
index 80c9cf2f254a..c8d5ac43a176 100644
--- a/sound/soc/qcom/storm.c
+++ b/sound/soc/qcom/storm.c
@@ -19,7 +19,7 @@
static int storm_ops_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
- struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream);
+ 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);
@@ -39,7 +39,7 @@ static int storm_ops_hw_params(struct snd_pcm_substream *substream,
*/
sysclk_freq = rate * bitwidth * 2 * STORM_SYSCLK_MULT;
- ret = snd_soc_dai_set_sysclk(asoc_rtd_to_cpu(soc_runtime, 0), 0, sysclk_freq, 0);
+ 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);
@@ -140,4 +140,4 @@ static struct platform_driver storm_platform_driver = {
module_platform_driver(storm_platform_driver);
MODULE_DESCRIPTION("QTi IPQ806x-based Storm Machine Driver");
-MODULE_LICENSE("GPL v2");
+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 000000000000..0a24b278fcdf
--- /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 000000000000..c3f411f565b0
--- /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 000000000000..8eb57fc12f0d
--- /dev/null
+++ b/sound/soc/qcom/x1e80100.c
@@ -0,0 +1,236 @@
+// 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 sdw_stream_runtime *sruntime[AFE_PORT_MAX];
+ 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_jack *dp_jack = NULL;
+ int dp_pcm_id = 0;
+
+ switch (cpu_dai->id) {
+ 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 void x1e80100_snd_shutdown(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);
+ struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
+
+ data->sruntime[cpu_dai->id] = NULL;
+ sdw_release_stream(sruntime);
+}
+
+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_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 x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+
+ return qcom_snd_sdw_hw_params(substream, params, &data->sruntime[cpu_dai->id]);
+}
+
+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);
+ struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
+ 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, sruntime,
+ &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);
+ struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
+
+ return qcom_snd_sdw_hw_free(substream, sruntime,
+ &data->stream_prepared[cpu_dai->id]);
+}
+
+static const struct snd_soc_ops x1e80100_be_ops = {
+ .startup = qcom_snd_sdw_startup,
+ .shutdown = x1e80100_snd_shutdown,
+ .hw_params = x1e80100_snd_hw_params,
+ .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 = "x1e80100";
+ 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", },
+ {}
+};
+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");