diff options
Diffstat (limited to 'sound/soc/qcom')
26 files changed, 2923 insertions, 289 deletions
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index b2173847dc47..8c7398bc1ca8 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -20,6 +20,10 @@ 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 @@ -36,6 +40,13 @@ config SND_SOC_LPASS_SC7180 select SND_SOC_LPASS_PLATFORM select SND_SOC_LPASS_HDMI +config SND_SOC_LPASS_SC7280 + tristate + select SND_SOC_LPASS_CPU + select SND_SOC_LPASS_PLATFORM + select SND_SOC_LPASS_HDMI + select SND_SOC_LPASS_CDC_DMA + config SND_SOC_STORM tristate "ASoC I2S support for Storm boards" depends on GPIOLIB @@ -162,17 +173,47 @@ config SND_SOC_SM8250 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 + 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_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. + endif #SND_SOC_QCOM diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index 1600ae55bd34..8b97172cf990 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -1,32 +1,40 @@ # SPDX-License-Identifier: GPL-2.0 # Platform snd-soc-lpass-cpu-objs := lpass-cpu.o +snd-soc-lpass-cdc-dma-objs := lpass-cdc-dma.o snd-soc-lpass-hdmi-objs := lpass-hdmi.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-sc7180-objs := lpass-sc7180.o +snd-soc-lpass-sc7280-objs := 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-sc7180-objs := sc7180.o +snd-soc-sc7280-objs := sc7280.o snd-soc-sdm845-objs := sdm845.o snd-soc-sm8250-objs := sm8250.o +snd-soc-sc8280xp-objs := sc8280xp.o snd-soc-qcom-common-objs := common.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 diff --git a/sound/soc/qcom/apq8016_sbc.c b/sound/soc/qcom/apq8016_sbc.c index ba2a98268ee4..e54b8961112f 100644 --- a/sound/soc/qcom/apq8016_sbc.c +++ b/sound/soc/qcom/apq8016_sbc.c @@ -17,6 +17,9 @@ #include <uapi/linux/input-event-codes.h> #include <dt-bindings/sound/apq8016-lpass.h> #include "common.h" +#include "qdsp6/q6afe.h" + +#define MI2S_COUNT (MI2S_QUATERNARY + 1) struct apq8016_sbc_data { struct snd_soc_card card; @@ -24,6 +27,7 @@ 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) @@ -38,10 +42,10 @@ struct apq8016_sbc_data { #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 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; @@ -49,7 +53,7 @@ static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd) 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); @@ -92,7 +96,7 @@ static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd) SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_BTN_4, - &pdata->jack, NULL, 0); + &pdata->jack); if (rval < 0) { dev_err(card->dev, "Unable to add Headphone Jack\n"); @@ -128,6 +132,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 = asoc_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; @@ -137,6 +148,113 @@ static void apq8016_sbc_add_ops(struct snd_soc_card *card) link->init = apq8016_sbc_dai_init; } +static int qdsp6_dai_get_lpass_id(struct snd_soc_dai *cpu_dai) +{ + switch (cpu_dai->id) { + case PRIMARY_MI2S_RX: + case PRIMARY_MI2S_TX: + return MI2S_PRIMARY; + case SECONDARY_MI2S_RX: + case SECONDARY_MI2S_TX: + return MI2S_SECONDARY; + case TERTIARY_MI2S_RX: + case TERTIARY_MI2S_TX: + return MI2S_TERTIARY; + case QUATERNARY_MI2S_RX: + case QUATERNARY_MI2S_TX: + return MI2S_QUATERNARY; + default: + return -EINVAL; + } +} + +static int msm8916_qdsp6_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *cpu_dai = asoc_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 = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct apq8016_sbc_data *data = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *cpu_dai = asoc_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 = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct apq8016_sbc_data *data = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *cpu_dai = asoc_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_soc_dapm_widget apq8016_sbc_dapm_widgets[] = { SND_SOC_DAPM_MIC("Handset Mic", NULL), @@ -148,11 +266,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; 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; @@ -177,12 +300,13 @@ static int apq8016_sbc_platform_probe(struct platform_device *pdev) 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[] __maybe_unused = { - { .compatible = "qcom,apq8016-sbc-sndcard" }, + { .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); diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index 2e1c618f7529..69dd3b504e20 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -3,6 +3,9 @@ // Copyright (c) 2018, The Linux Foundation. All rights reserved. #include <linux/module.h> +#include <sound/jack.h> +#include <linux/input-event-codes.h> +#include "qdsp6/q6afe.h" #include "common.h" int qcom_snd_parse_of(struct snd_soc_card *card) @@ -26,6 +29,12 @@ int qcom_snd_parse_of(struct snd_soc_card *card) return ret; } + if (of_property_read_bool(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")) { ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); @@ -39,6 +48,10 @@ int qcom_snd_parse_of(struct snd_soc_card *card) 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; @@ -94,9 +107,8 @@ int qcom_snd_parse_of(struct snd_soc_card *card) 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); + dev_err_probe(card->dev, ret, + "%s: error getting cpu dai name\n", link->name); goto err; } @@ -116,9 +128,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; } @@ -167,6 +178,174 @@ err_put_np: of_node_put(np); return ret; } -EXPORT_SYMBOL(qcom_snd_parse_of); +EXPORT_SYMBOL_GPL(qcom_snd_parse_of); +#if IS_ENABLED(CONFIG_SOUNDWIRE) +int qcom_snd_sdw_prepare(struct snd_pcm_substream *substream, + struct sdw_stream_runtime *sruntime, + bool *stream_prepared) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_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) { + sdw_disable_stream(sruntime); + sdw_deprepare_stream(sruntime); + *stream_prepared = false; + } + + 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 = substream->private_data; + struct snd_soc_dai *codec_dai; + struct snd_soc_dai *cpu_dai = asoc_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 = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_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); +#endif + +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 = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + int rval, i; + + if (!*jack_setup) { + rval = snd_soc_card_jack_new(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); + + 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); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h index f05c05b12bd7..c5472a642de0 100644 --- a/sound/soc/qcom/common.h +++ b/sound/soc/qcom/common.h @@ -5,7 +5,42 @@ #define __QCOM_SND_COMMON_H__ #include <sound/soc.h> +#include <linux/soundwire/sdw.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); +#if IS_ENABLED(CONFIG_SOUNDWIRE) +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); +#else +static inline int qcom_snd_sdw_prepare(struct snd_pcm_substream *substream, + struct sdw_stream_runtime *runtime, + bool *stream_prepared) +{ + return -ENOTSUPP; +} + +static inline int qcom_snd_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sdw_stream_runtime **psruntime) +{ + return -ENOTSUPP; +} + +static inline int qcom_snd_sdw_hw_free(struct snd_pcm_substream *substream, + struct sdw_stream_runtime *sruntime, + bool *stream_prepared) +{ + return -ENOTSUPP; +} +#endif #endif diff --git a/sound/soc/qcom/lpass-apq8016.c b/sound/soc/qcom/lpass-apq8016.c index 3efa133d1c64..abaf694ee9a3 100644 --- a/sound/soc/qcom/lpass-apq8016.c +++ b/sound/soc/qcom/lpass-apq8016.c @@ -293,6 +293,7 @@ static struct lpass_variant apq8016_data = { 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); diff --git a/sound/soc/qcom/lpass-cdc-dma.c b/sound/soc/qcom/lpass-cdc-dma.c new file mode 100644 index 000000000000..31b9f1c22bee --- /dev/null +++ b/sound/soc/qcom/lpass-cdc-dma.c @@ -0,0 +1,301 @@ +// 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 <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 = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_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; + 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 = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_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 = asoc_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 = asoc_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 = asoc_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 = asoc_substream_to_rtd(substream); + struct lpaif_dmactl *dmactl; + 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 3bd9eb3cc688..54353842dc07 100644 --- a/sound/soc/qcom/lpass-cpu.c +++ b/sound/soc/qcom/lpass-cpu.c @@ -28,6 +28,8 @@ #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 @@ -470,6 +472,7 @@ static int asoc_qcom_of_xlate_dai_name(struct snd_soc_component *component, 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) @@ -779,10 +782,20 @@ static bool lpass_hdmi_regmap_volatile(struct device *dev, unsigned int reg) 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; } @@ -798,6 +811,189 @@ static struct regmap_config lpass_hdmi_regmap_config = { .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); + 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); + 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); + 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); + 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) @@ -857,6 +1053,8 @@ static void of_lpass_cpu_parse_dai_data(struct device *dev, } 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, @@ -868,10 +1066,33 @@ static void of_lpass_cpu_parse_dai_data(struct device *dev, } } +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; struct device *dev = &pdev->dev; const struct of_device_id *match; @@ -880,6 +1101,7 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) 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; } @@ -892,11 +1114,57 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) if (!match || !match->data) return -EINVAL; + if (of_device_is_compatible(dev->of_node, "qcom,lpass-cpu-apq8016")) { + dev_warn(dev, "%s compatible is deprecated\n", + match->compatible); + } + drvdata->variant = (struct lpass_variant *)match->data; variant = drvdata->variant; of_lpass_cpu_parse_dai_data(dev, drvdata); + if (drvdata->codec_dma_enable) { + drvdata->rxtx_lpaif = + devm_platform_ioremap_resource_byname(pdev, "lpass-rxtx-lpaif"); + if (IS_ERR(drvdata->rxtx_lpaif)) + return PTR_ERR(drvdata->rxtx_lpaif); + + drvdata->va_lpaif = devm_platform_ioremap_resource_byname(pdev, "lpass-va-lpaif"); + if (IS_ERR(drvdata->va_lpaif)) + return PTR_ERR(drvdata->va_lpaif); + + lpass_rxtx_regmap_config.max_register = LPAIF_CDC_RXTX_WRDMAPER_REG(variant, + variant->rxtx_wrdma_channels + + variant->rxtx_wrdma_channel_start, LPASS_CDC_DMA_TX3); + + drvdata->rxtx_lpaif_map = devm_regmap_init_mmio(dev, drvdata->rxtx_lpaif, + &lpass_rxtx_regmap_config); + if (IS_ERR(drvdata->rxtx_lpaif_map)) + return PTR_ERR(drvdata->rxtx_lpaif_map); + + lpass_va_regmap_config.max_register = LPAIF_CDC_VA_WRDMAPER_REG(variant, + variant->va_wrdma_channels + + variant->va_wrdma_channel_start, LPASS_CDC_DMA_VA_TX0); + + drvdata->va_lpaif_map = devm_regmap_init_mmio(dev, drvdata->va_lpaif, + &lpass_va_regmap_config); + if (IS_ERR(drvdata->va_lpaif_map)) + return PTR_ERR(drvdata->va_lpaif_map); + + ret = of_lpass_cdc_dma_clks_parse(dev, drvdata); + if (ret) { + dev_err(dev, "failed to get cdc dma clocks %d\n", ret); + return ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-rxtx-cdc-dma-lpm"); + drvdata->rxtx_cdc_dma_lpm_buf = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-va-cdc-dma-lpm"); + 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); @@ -939,7 +1207,7 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) for (i = 0; i < variant->num_dai; i++) { dai_id = variant->dai_driver[i].id; - if (dai_id == LPASS_DP_RX) + if (dai_id == LPASS_DP_RX || is_cdc_dma_port(dai_id)) continue; drvdata->mi2s_osr_clk[dai_id] = devm_clk_get_optional(dev, diff --git a/sound/soc/qcom/lpass-lpaif-reg.h b/sound/soc/qcom/lpass-lpaif-reg.h index 2eb03ad9b7c7..6d9d9d1f6a4d 100644 --- a/sound/soc/qcom/lpass-lpaif-reg.h +++ b/sound/soc/qcom/lpass-lpaif-reg.h @@ -74,6 +74,21 @@ #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)) @@ -139,12 +154,112 @@ (LPAIF_INTFDMA_REG(v, chan, reg, dai_id)) : \ LPAIF_WRDMA##reg##_REG(v, chan)) -#define LPAIF_DMACTL_REG(v, chan, dir, dai_id) __LPAIF_DMA_REG(v, chan, dir, CTL, dai_id) -#define LPAIF_DMABASE_REG(v, chan, dir, dai_id) __LPAIF_DMA_REG(v, chan, dir, BASE, dai_id) -#define LPAIF_DMABUFF_REG(v, chan, dir, dai_id) __LPAIF_DMA_REG(v, chan, dir, BUFF, dai_id) -#define LPAIF_DMACURR_REG(v, chan, dir, dai_id) __LPAIF_DMA_REG(v, chan, dir, CURR, dai_id) -#define LPAIF_DMAPER_REG(v, chan, dir, dai_id) __LPAIF_DMA_REG(v, chan, dir, PER, dai_id) -#define LPAIF_DMAPERCNT_REG(v, chan, dir, dai_id) __LPAIF_DMA_REG(v, chan, dir, PERCNT, dai_id) +#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 diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c index a59e9d20cb46..b41ab7a321ae 100644 --- a/sound/soc/qcom/lpass-platform.c +++ b/sound/soc/qcom/lpass-platform.c @@ -18,13 +18,11 @@ #define DRV_NAME "lpass-platform" -struct lpass_pcm_data { - int dma_ch; - int i2s_port; -}; - #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,6 +48,99 @@ 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); + 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); + 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) { @@ -128,25 +219,55 @@ static int lpass_platform_pcmops_open(struct snd_soc_component *component, return dma_ch; } - if (cpu_dai->driver->id == LPASS_DP_RX) { - map = drvdata->hdmiif_map; - drvdata->hdmi_substream[dma_ch] = substream; - } else { + 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; - ret = regmap_write(map, - LPAIF_DMACTL_REG(v, dma_ch, dir, data->i2s_port), 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: + 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; } - snd_soc_set_runtime_hwparams(substream, &lpass_platform_pcm_hardware); - - runtime->dma_bytes = lpass_platform_pcm_hardware.buffer_bytes_max; - ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); if (ret < 0) { @@ -171,10 +292,25 @@ static int lpass_platform_pcmops_close(struct snd_soc_component *component, unsigned int dai_id = cpu_dai->driver->id; data = runtime->private_data; - if (dai_id == LPASS_DP_RX) - drvdata->hdmi_substream[data->dma_ch] = NULL; - else + + switch (dai_id) { + case MI2S_PRIMARY ... MI2S_QUINARY: drvdata->substream[data->dma_ch] = NULL; + break; + case LPASS_DP_RX: + drvdata->hdmi_substream[data->dma_ch] = NULL; + break; + case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9: + case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8: + drvdata->rxtx_substream[data->dma_ch] = NULL; + break; + case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8: + drvdata->va_substream[data->dma_ch] = NULL; + break; + default: + break; + } + if (v->free_dma_channel) v->free_dma_channel(drvdata, data->dma_ch, dai_id); @@ -182,6 +318,100 @@ static int lpass_platform_pcmops_close(struct snd_soc_component *component, 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 = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_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 = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_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 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 = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_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) @@ -196,22 +426,13 @@ static int lpass_platform_pcmops_hw_params(struct snd_soc_component *component, unsigned int channels = params_channels(params); unsigned int regval; struct lpaif_dmactl *dmactl; - int id, dir = substream->stream; + int id; int bitwidth; int ret, dma_port = pcm_data->i2s_port + v->dmactl_audif_start; unsigned int dai_id = cpu_dai->driver->id; - if (dir == SNDRV_PCM_STREAM_PLAYBACK) { - id = pcm_data->dma_ch; - if (dai_id == LPASS_DP_RX) - dmactl = drvdata->hdmi_rd_dmactl; - else - dmactl = drvdata->rd_dmactl; - - } else { - dmactl = drvdata->wr_dmactl; - id = pcm_data->dma_ch - v->wrdma_channel_start; - } + dmactl = __lpass_get_dmactl_handle(substream, component); + id = __lpass_get_id(substream, component); bitwidth = snd_pcm_format_width(format); if (bitwidth < 0) { @@ -267,6 +488,10 @@ static int lpass_platform_pcmops_hw_params(struct snd_soc_component *component, } 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; @@ -355,10 +580,9 @@ static int lpass_platform_pcmops_hw_free(struct snd_soc_component *component, struct regmap *map; unsigned int dai_id = cpu_dai->driver->id; - if (dai_id == LPASS_DP_RX) - map = drvdata->hdmiif_map; - else - map = drvdata->lpaif_map; + if (is_cdc_dma_port(dai_id)) + return 0; + map = __lpass_get_regmap_handle(substream, component); reg = LPAIF_DMACTL_REG(v, pcm_data->dma_ch, substream->stream, dai_id); ret = regmap_write(map, reg, 0); @@ -384,23 +608,11 @@ static int lpass_platform_pcmops_prepare(struct snd_soc_component *component, int ret, id, ch, dir = substream->stream; unsigned int dai_id = cpu_dai->driver->id; - ch = pcm_data->dma_ch; - if (dir == SNDRV_PCM_STREAM_PLAYBACK) { - if (dai_id == LPASS_DP_RX) { - dmactl = drvdata->hdmi_rd_dmactl; - map = drvdata->hdmiif_map; - } else { - dmactl = drvdata->rd_dmactl; - map = drvdata->lpaif_map; - } - id = pcm_data->dma_ch; - } else { - dmactl = drvdata->wr_dmactl; - id = pcm_data->dma_ch - v->wrdma_channel_start; - map = drvdata->lpaif_map; - } + 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); @@ -426,6 +638,14 @@ static int lpass_platform_pcmops_prepare(struct snd_soc_component *component, return ret; } + if (is_cdc_dma_port(dai_id)) { + ret = regmap_fields_write(dmactl->fifowm, id, LPAIF_DMACTL_FIFOWM_8); + if (ret) { + dev_err(soc_runtime->dev, "error writing fifowm field to dmactl reg: %d, id: %d\n", + ret, id); + return ret; + } + } ret = regmap_fields_write(dmactl->enable, id, LPAIF_DMACTL_ENABLE_ON); if (ret) { dev_err(soc_runtime->dev, "error writing to rdmactl reg: %d\n", @@ -449,26 +669,14 @@ static int lpass_platform_pcmops_trigger(struct snd_soc_component *component, struct lpaif_dmactl *dmactl; struct regmap *map; int ret, ch, id; - int dir = substream->stream; 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; - if (dir == SNDRV_PCM_STREAM_PLAYBACK) { - id = pcm_data->dma_ch; - if (dai_id == LPASS_DP_RX) { - dmactl = drvdata->hdmi_rd_dmactl; - map = drvdata->hdmiif_map; - } else { - dmactl = drvdata->rd_dmactl; - map = drvdata->lpaif_map; - } - } else { - dmactl = drvdata->wr_dmactl; - id = pcm_data->dma_ch - v->wrdma_channel_start; - map = drvdata->lpaif_map; - } + 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: @@ -519,12 +727,41 @@ static int lpass_platform_pcmops_trigger(struct snd_soc_component *component, 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(map, reg_irqclr, val_irqclr, val_irqclr); + ret = regmap_write_bits(map, reg_irqclr, val_irqclr, val_irqclr); if (ret) { dev_err(soc_runtime->dev, "error writing to irqclear reg: %d\n", ret); return ret; @@ -570,6 +807,37 @@ static int lpass_platform_pcmops_trigger(struct snd_soc_component *component, 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; @@ -602,11 +870,7 @@ static snd_pcm_uframes_t lpass_platform_pcmops_pointer( struct regmap *map; unsigned int dai_id = cpu_dai->driver->id; - if (dai_id == LPASS_DP_RX) - map = drvdata->hdmiif_map; - else - map = drvdata->lpaif_map; - + map = __lpass_get_regmap_handle(substream, component); ch = pcm_data->dma_ch; ret = regmap_read(map, @@ -628,6 +892,35 @@ 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_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + unsigned int dai_id = cpu_dai->driver->id; + + if (is_cdc_dma_port(dai_id)) + return lpass_platform_cdc_dma_mmap(substream, vma); + + return snd_pcm_lib_default_mmap(substream, vma); +} + static irqreturn_t lpass_dma_interrupt_handler( struct snd_pcm_substream *substream, struct lpass_data *drvdata, @@ -660,12 +953,23 @@ static irqreturn_t lpass_dma_interrupt_handler( 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_update_bits(map, reg, mask, (LPAIF_IRQ_PER(chan) | val)); + 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); @@ -676,19 +980,20 @@ static irqreturn_t lpass_dma_interrupt_handler( } if (interrupts & LPAIF_IRQ_XRUN(chan)) { - rv = regmap_update_bits(map, reg, mask, (LPAIF_IRQ_XRUN(chan) | val)); + 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_update_bits(map, reg, mask, (LPAIF_IRQ_ERR(chan) | val)); + 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); @@ -767,16 +1072,115 @@ static irqreturn_t lpass_platform_hdmiif_irq(int irq, void *data) return rv; } } + return IRQ_HANDLED; +} +static irqreturn_t lpass_platform_rxtxif_irq(int irq, void *data) +{ + struct lpass_data *drvdata = data; + 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; + struct lpass_variant *v = drvdata->variant; + unsigned int irqs; + irqreturn_t rv; + int chan; + + rv = regmap_read(drvdata->va_lpaif_map, + LPAIF_VA_IRQSTAT_REG(v, LPAIF_IRQ_PORT_HOST), &irqs); + + /* Handle per channel interrupts */ + for (chan = 0; chan < LPASS_MAX_VA_CDC_DMA_CHANNELS; chan++) { + if (irqs & LPAIF_IRQ_ALL(chan) && drvdata->va_substream[chan]) { + rv = lpass_dma_interrupt_handler( + drvdata->va_substream[chan], + drvdata, chan, irqs); + if (rv != IRQ_HANDLED) + return rv; + } + } return IRQ_HANDLED; } +static int lpass_platform_prealloc_cdc_dma_buffer(struct snd_soc_component *component, + struct snd_pcm *pcm, int dai_id) +{ + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + else + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + + buf = &substream->dma_buffer; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + + /* Assign Codec DMA buffer pointers */ + buf->dev.type = SNDRV_DMA_TYPE_CONTINUOUS; + + switch (dai_id) { + case LPASS_CDC_DMA_RX0 ... LPASS_CDC_DMA_RX9: + buf->bytes = lpass_platform_rxtx_hardware.buffer_bytes_max; + buf->addr = drvdata->rxtx_cdc_dma_lpm_buf; + break; + case LPASS_CDC_DMA_TX0 ... LPASS_CDC_DMA_TX8: + buf->bytes = lpass_platform_rxtx_hardware.buffer_bytes_max; + buf->addr = drvdata->rxtx_cdc_dma_lpm_buf + LPASS_RXTX_CDC_DMA_LPM_BUFF_SIZE; + break; + case LPASS_CDC_DMA_VA_TX0 ... LPASS_CDC_DMA_VA_TX8: + buf->bytes = lpass_platform_va_hardware.buffer_bytes_max; + buf->addr = drvdata->va_cdc_dma_lpm_buf; + break; + default: + break; + } + + buf->area = (unsigned char * __force)memremap(buf->addr, buf->bytes, MEMREMAP_WC); + + return 0; +} + static int lpass_platform_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *soc_runtime) { struct snd_pcm *pcm = soc_runtime->pcm; + struct snd_soc_dai *cpu_dai = asoc_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_DEV, component->dev, size); } @@ -813,6 +1217,35 @@ static int lpass_platform_pcmops_resume(struct snd_soc_component *component) return regcache_sync(map); } +static int lpass_platform_copy(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int channel, + unsigned long pos, void __user *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)) { + ret = copy_from_user_toio(dma_buf, buf, bytes); + } else { + if (copy_from_user((void __force *)dma_buf, buf, bytes)) + ret = -EFAULT; + } + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (is_cdc_dma_port(dai_id)) { + ret = copy_to_user_fromio(buf, dma_buf, bytes); + } else { + if (copy_to_user(buf, (void __force *)dma_buf, bytes)) + ret = -EFAULT; + } + } + + return ret; +} static const struct snd_soc_component_driver lpass_component_driver = { .name = DRV_NAME, @@ -823,9 +1256,11 @@ static const struct snd_soc_component_driver lpass_component_driver = { .prepare = lpass_platform_pcmops_prepare, .trigger = lpass_platform_pcmops_trigger, .pointer = lpass_platform_pcmops_pointer, + .mmap = lpass_platform_pcmops_mmap, .pcm_construct = lpass_platform_pcm_new, .suspend = lpass_platform_pcmops_suspend, .resume = lpass_platform_pcmops_resume, + .copy_user = lpass_platform_copy, }; @@ -863,6 +1298,58 @@ int asoc_qcom_lpass_platform_register(struct platform_device *pdev) 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) diff --git a/sound/soc/qcom/lpass-sc7280.c b/sound/soc/qcom/lpass-sc7280.c new file mode 100644 index 000000000000..70c4df87d957 --- /dev/null +++ b/sound/soc/qcom/lpass-sc7280.c @@ -0,0 +1,438 @@ +// 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_runtime.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, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .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, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .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) +{ + 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); + 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 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), + }, + .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 67ef497166af..dd78600fc7b0 100644 --- a/sound/soc/qcom/lpass.h +++ b/sound/soc/qcom/lpass.h @@ -16,9 +16,20 @@ #include "lpass-hdmi.h" #define LPASS_AHBIX_CLOCK_FREQUENCY 131072000 +#define LPASS_MAX_PORTS (LPASS_CDC_DMA_VA_TX8 + 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 { \ @@ -27,6 +38,27 @@ 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; @@ -50,6 +82,12 @@ struct lpaif_dmactl { 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 */ @@ -64,6 +102,11 @@ 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]; @@ -72,28 +115,43 @@ struct lpass_data { 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; /* 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; @@ -104,6 +162,12 @@ struct lpass_data { 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; @@ -130,6 +194,24 @@ struct lpass_variant { 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; @@ -233,12 +315,66 @@ struct lpass_variant { struct reg_field wrdma_enable; struct reg_field wrdma_dyncclk; + /* CDC RXTX RD_DMA */ + struct reg_field rxtx_rdma_intf; + struct reg_field rxtx_rdma_bursten; + struct reg_field rxtx_rdma_wpscnt; + struct reg_field rxtx_rdma_fifowm; + struct reg_field rxtx_rdma_enable; + struct reg_field rxtx_rdma_dyncclk; + struct reg_field rxtx_rdma_burst8; + struct reg_field rxtx_rdma_burst16; + struct reg_field rxtx_rdma_dynburst; + struct reg_field rxtx_rdma_codec_enable; + struct reg_field rxtx_rdma_codec_pack; + struct reg_field rxtx_rdma_codec_intf; + struct reg_field rxtx_rdma_codec_fs_sel; + struct reg_field rxtx_rdma_codec_ch; + struct reg_field rxtx_rdma_codec_fs_delay; + + /* CDC RXTX WR_DMA */ + struct reg_field rxtx_wrdma_intf; + struct reg_field rxtx_wrdma_bursten; + struct reg_field rxtx_wrdma_wpscnt; + struct reg_field rxtx_wrdma_fifowm; + struct reg_field rxtx_wrdma_enable; + struct reg_field rxtx_wrdma_dyncclk; + struct reg_field rxtx_wrdma_burst8; + struct reg_field rxtx_wrdma_burst16; + struct reg_field rxtx_wrdma_dynburst; + struct reg_field rxtx_wrdma_codec_enable; + struct reg_field rxtx_wrdma_codec_pack; + struct reg_field rxtx_wrdma_codec_intf; + struct reg_field rxtx_wrdma_codec_fs_sel; + struct reg_field rxtx_wrdma_codec_ch; + struct reg_field rxtx_wrdma_codec_fs_delay; + + /* CDC VA WR_DMA */ + struct reg_field va_wrdma_intf; + struct reg_field va_wrdma_bursten; + struct reg_field va_wrdma_wpscnt; + struct reg_field va_wrdma_fifowm; + struct reg_field va_wrdma_enable; + struct reg_field va_wrdma_dyncclk; + struct reg_field va_wrdma_burst8; + struct reg_field va_wrdma_burst16; + struct reg_field va_wrdma_dynburst; + struct reg_field va_wrdma_codec_enable; + struct reg_field va_wrdma_codec_pack; + struct reg_field va_wrdma_codec_intf; + struct reg_field va_wrdma_codec_fs_sel; + struct reg_field va_wrdma_codec_ch; + struct reg_field va_wrdma_codec_fs_delay; + /** * on SOCs like APQ8016 the channel control bits start * at different offset to ipq806x **/ u32 dmactl_audif_start; u32 wrdma_channel_start; + u32 rxtx_wrdma_channel_start; + u32 va_wrdma_channel_start; + /* SOC specific initialization like clocks */ int (*init)(struct platform_device *pdev); int (*exit)(struct platform_device *pdev); @@ -256,6 +392,11 @@ struct lpass_variant { 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); @@ -265,5 +406,6 @@ 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; int lpass_cpu_pcm_new(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); +extern const struct snd_soc_dai_ops asoc_qcom_lpass_cdc_dma_dai_ops; #endif /* __LPASS_H__ */ diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index 98c0efa1d0fe..01dac32c50fd 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -732,10 +732,10 @@ static int audioreach_i2s_set_media_format(struct q6apm_graph *graph, intf_cfg->cfg.sd_line_idx = module->sd_line_idx; switch (cfg->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { - case SND_SOC_DAIFMT_CBC_CFC: + case SND_SOC_DAIFMT_BP_FP: intf_cfg->cfg.ws_src = CONFIG_I2S_WS_SRC_INTERNAL; break; - case SND_SOC_DAIFMT_CBP_CFP: + case SND_SOC_DAIFMT_BC_FC: /* CPU is slave */ intf_cfg->cfg.ws_src = CONFIG_I2S_WS_SRC_EXTERNAL; break; diff --git a/sound/soc/qcom/qdsp6/q6adm.c b/sound/soc/qcom/qdsp6/q6adm.c index 72c5719f1d25..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; diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c index 625724852a7f..919e326b9462 100644 --- a/sound/soc/qcom/qdsp6/q6afe.c +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -1328,11 +1328,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; diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index eb1c3aec479b..ee59ef36b85a 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -147,6 +147,12 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, cfg.num_channels = runtime->channels; cfg.bit_width = prtd->bits_per_sample; + 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); prtd->pos = 0; /* rate and channels are sent to audio driver */ @@ -308,8 +314,11 @@ static int q6apm_dai_close(struct snd_soc_component *component, struct snd_pcm_runtime *runtime = substream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; - q6apm_graph_stop(prtd->graph); - q6apm_unmap_memory_regions(prtd->graph, substream->stream); + 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); diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 13598ef5bacb..794019286c70 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -75,6 +75,7 @@ static struct audioreach_graph *q6apm_get_audioreach_graph(struct q6apm *apm, ui 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); @@ -615,7 +616,7 @@ struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb, graph = kzalloc(sizeof(*graph), GFP_KERNEL); if (!graph) { ret = -ENOMEM; - goto err; + goto put_ar_graph; } graph->apm = apm; @@ -630,14 +631,16 @@ struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb, init_waitqueue_head(&graph->cmd_wait); graph->port = gpr_alloc_port(apm->gdev, dev, graph_callback, graph); - if (!graph->port) { - kfree(graph); - ret = -ENOMEM; - goto err; + if (IS_ERR(graph->port)) { + ret = PTR_ERR(graph->port); + goto free_graph; } return graph; -err: + +free_graph: + kfree(graph); +put_ar_graph: kref_put(&ar_graph->refcount, q6apm_put_audioreach_graph); return ERR_PTR(ret); } diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c index b74b67720ef4..5fc8088e63c8 100644 --- a/sound/soc/qcom/qdsp6/q6asm-dai.c +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -1205,17 +1205,18 @@ static const struct snd_soc_dapm_widget q6asm_dapm_widgets[] = { }; 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, - .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), + .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[] = { diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c index 9251d8548965..195780f75d05 100644 --- a/sound/soc/qcom/qdsp6/q6asm.c +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -513,7 +513,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; diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c index a26cda5140c1..73b0cbac73d4 100644 --- a/sound/soc/qcom/qdsp6/q6prm-clocks.c +++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c @@ -50,6 +50,15 @@ static const struct q6dsp_clk_init q6prm_clks[] = { 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, diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h index fea4d1954bc1..a988a32086fe 100644 --- a/sound/soc/qcom/qdsp6/q6prm.h +++ b/sound/soc/qcom/qdsp6/q6prm.h @@ -64,6 +64,25 @@ #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 diff --git a/sound/soc/qcom/sc7180.c b/sound/soc/qcom/sc7180.c index 768566bb57a5..f5f7c64b23a2 100644 --- a/sound/soc/qcom/sc7180.c +++ b/sound/soc/qcom/sc7180.c @@ -17,6 +17,7 @@ #include <uapi/linux/input-event-codes.h> #include "../codecs/rt5682.h" +#include "../codecs/rt5682s.h" #include "common.h" #include "lpass.h" @@ -56,7 +57,7 @@ static int sc7180_headset_init(struct snd_soc_pcm_runtime *rtd) SND_JACK_HEADPHONE | SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3, - &pdata->hs_jack, NULL, 0); + &pdata->hs_jack); if (rval < 0) { dev_err(card->dev, "Unable to add Headset Jack\n"); @@ -88,7 +89,7 @@ static int sc7180_hdmi_init(struct snd_soc_pcm_runtime *rtd) rval = snd_soc_card_jack_new( card, "HDMI Jack", SND_JACK_LINEOUT, - &pdata->hdmi_jack, NULL, 0); + &pdata->hdmi_jack); if (rval < 0) { dev_err(card->dev, "Unable to add HDMI Jack\n"); @@ -128,7 +129,21 @@ static int sc7180_snd_startup(struct snd_pcm_substream *substream) struct sc7180_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); - int ret; + 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; + } switch (cpu_dai->id) { case MI2S_PRIMARY: @@ -140,21 +155,20 @@ static int sc7180_snd_startup(struct snd_pcm_substream *substream) } snd_soc_dai_set_fmt(codec_dai, - SND_SOC_DAIFMT_CBS_CFS | + 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, 0, RT5682_PLL1_S_MCLK, - DEFAULT_MCLK_RATE, RT5682_PLL1_FREQ); + 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, RT5682_SCLK_S_PLL1, - RT5682_PLL1_FREQ, + 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", diff --git a/sound/soc/qcom/sc7280.c b/sound/soc/qcom/sc7280.c new file mode 100644 index 000000000000..da7469a6a267 --- /dev/null +++ b/sound/soc/qcom/sc7280.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. +// +// ALSA SoC Machine driver for sc7280 + +#include <linux/input.h> +#include <linux/module.h> +#include <linux/of_device.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 "../codecs/rt5682.h" +#include "../codecs/rt5682s.h" +#include "common.h" +#include "lpass.h" +#include "qdsp6/q6afe.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 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 = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = asoc_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(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); + + 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 = asoc_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 = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_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 = asoc_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 = substream->private_data; + struct snd_soc_dai *codec_dai; + const struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct sc7280_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card); + struct sdw_stream_runtime *sruntime; + int i; + + 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 = substream->private_data; + const struct snd_soc_dai *cpu_dai = asoc_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 = substream->private_data; + const struct snd_soc_dai *cpu_dai = asoc_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 = substream->private_data; + struct sc7280_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + const struct snd_soc_dai *cpu_dai = asoc_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 = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct sc7280_snd_data *data = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + + switch (cpu_dai->id) { + case MI2S_PRIMARY: + if (--data->pri_mi2s_clk_count == 0) { + snd_soc_dai_set_sysclk(cpu_dai, + LPASS_MCLK0, + 0, + SNDRV_PCM_STREAM_PLAYBACK); + } + break; + case SECONDARY_MI2S_RX: + snd_soc_dai_set_sysclk(cpu_dai, Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT, + 0, SNDRV_PCM_STREAM_PLAYBACK); + break; + default: + break; + } +} + +static int sc7280_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 = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret = 0; + + switch (cpu_dai->id) { + case MI2S_PRIMARY: + ret = sc7280_rt5682_init(rtd); + 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 ret; +} + +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 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); + + 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; + } + + 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..ade44ad7c585 --- /dev/null +++ b/sound/soc/qcom/sc8280xp.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022, Linaro Limited + +#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> +#include <linux/soundwire/sdw.h> +#include <sound/jack.h> +#include <linux/input-event-codes.h> +#include "qdsp6/q6afe.h" +#include "common.h" + +#define DRIVER_NAME "sc8280xp" + +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; + 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); + + return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); +} + +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 = asoc_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 = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_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 = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_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 = substream->private_data; + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = asoc_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 = { + .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 = DRIVER_NAME; + 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,sc8280xp-sndcard",}, + {} +}; + +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 v2"); diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c index 0adfc5708949..d8d35563af00 100644 --- a/sound/soc/qcom/sdm845.c +++ b/sound/soc/qcom/sdm845.c @@ -33,6 +33,7 @@ struct sdm845_snd_data { struct snd_soc_jack jack; bool jack_setup; + bool slim_port_setup; bool stream_prepared[AFE_PORT_MAX]; struct snd_soc_card *card; uint32_t pri_mi2s_clk_count; @@ -56,8 +57,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; @@ -224,6 +225,7 @@ static int sdm845_dai_init(struct snd_soc_pcm_runtime *rtd) 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 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 @@ -245,7 +247,7 @@ static int sdm845_dai_init(struct snd_soc_pcm_runtime *rtd) SND_JACK_HEADPHONE | SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3, - &pdata->jack, NULL, 0); + &pdata->jack); if (rval < 0) { dev_err(card->dev, "Unable to add Headphone Jack\n"); @@ -276,6 +278,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), @@ -295,8 +301,10 @@ static int sdm845_dai_init(struct snd_soc_pcm_runtime *rtd) dev_warn(card->dev, "Failed to set jack: %d\n", rval); return rval; } - } + + pdata->slim_port_setup = true; + break; default: break; @@ -308,8 +316,8 @@ 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; + unsigned int fmt = SND_SOC_DAIFMT_BP_FP; + unsigned int codec_dai_fmt = SND_SOC_DAIFMT_BC_FC; struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_card *card = rtd->card; struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card); @@ -348,7 +356,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; diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c index b2ca2579810b..8dbe9ef41b1c 100644 --- a/sound/soc/qcom/sm8250.c +++ b/sound/soc/qcom/sm8250.c @@ -27,57 +27,8 @@ struct sm8250_snd_data { 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 = asoc_rtd_to_cpu(rtd, 0); - struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); - struct snd_soc_card *card = rtd->card; - int rval, i; - - if (!data->jack_setup) { - struct snd_jack *jack; - - rval = snd_soc_card_jack_new(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, - &data->jack, NULL, 0); - - if (rval < 0) { - dev_err(card->dev, "Unable to add Headphone Jack\n"); - return rval; - } - - jack = data->jack.jack; - - snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_MEDIA); - 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); - data->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, - &data->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; + return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); } static int sm8250_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, @@ -96,8 +47,8 @@ static int sm8250_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, static int sm8250_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; + unsigned int fmt = SND_SOC_DAIFMT_BP_FP; + unsigned int codec_dai_fmt = SND_SOC_DAIFMT_BC_FC; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); @@ -121,92 +72,21 @@ static int sm8250_snd_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai; struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct sm8250_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card); - 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_sdw_stream(codec_dai, - substream->stream); - if (sruntime != ERR_PTR(-ENOTSUPP)) - pdata->sruntime[cpu_dai->id] = sruntime; - } - break; - } - - return 0; + return qcom_snd_sdw_hw_params(substream, params, &pdata->sruntime[cpu_dai->id]); } -static int sm8250_snd_wsa_dma_prepare(struct snd_pcm_substream *substream) +static int sm8250_snd_prepare(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = asoc_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]; - 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; - - /** - * 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; - } - data->stream_prepared[cpu_dai->id] = true; - - return ret; -} - -static int sm8250_snd_prepare(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *cpu_dai = asoc_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: - return sm8250_snd_wsa_dma_prepare(substream); - default: - break; - } - - return 0; + return qcom_snd_sdw_prepare(substream, sruntime, + &data->stream_prepared[cpu_dai->id]); } static int sm8250_snd_hw_free(struct snd_pcm_substream *substream) @@ -216,26 +96,8 @@ static int sm8250_snd_hw_free(struct snd_pcm_substream *substream) struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id]; - 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 && 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; + return qcom_snd_sdw_hw_free(substream, sruntime, + &data->stream_prepared[cpu_dai->id]); } static const struct snd_soc_ops sm8250_be_ops = { @@ -270,6 +132,7 @@ static int sm8250_platform_probe(struct platform_device *pdev) if (!card) return -ENOMEM; + card->owner = THIS_MODULE; /* Allocate the private data */ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) |