From 937a46a7ba2d20aa94b7477a985be34862cb7544 Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Wed, 8 Jul 2026 15:05:53 +0530 Subject: [PATCH 1/3] audioreach-driver: q6apm-lpass-dummy-dais: Add MI2S clock control Add support for MI2S clock control within q6apm-lpass DAIs, including handling of MCLK, BCLK, and ECLK via the DAI .set_sysclk callback. Each MI2S port now retrieves its clock handles from the device tree, allowing per-port clock configuration and proper enable/disable during startup and shutdown. Signed-off-by: Mohammad Rafi Shaik --- audioreach-driver/q6apm_lpass_dummy_dais.c | 153 ++++++++++++++++++++- audioreach-driver/q6prm_audioreach.h | 4 + 2 files changed, 152 insertions(+), 5 deletions(-) diff --git a/audioreach-driver/q6apm_lpass_dummy_dais.c b/audioreach-driver/q6apm_lpass_dummy_dais.c index 0bd7292..c88cb63 100644 --- a/audioreach-driver/q6apm_lpass_dummy_dais.c +++ b/audioreach-driver/q6apm_lpass_dummy_dais.c @@ -609,10 +609,18 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_7), }; +struct q6apm_dai_priv_data { + struct clk *mclk; + struct clk *bclk; + struct clk *eclk; + bool mclk_enabled, bclk_enabled, eclk_enabled; +}; + struct q6apm_lpass_dai_data { struct q6apm_graph *graph[APM_PORT_MAX]; bool is_port_started[APM_PORT_MAX]; struct audioreach_module_config module_config[APM_PORT_MAX]; + struct q6apm_dai_priv_data priv[APM_PORT_MAX]; }; static const struct snd_pcm_hardware q6apm_dummy_dma_hardware = { @@ -685,23 +693,87 @@ q6dsp_audio_ports_set_config(struct device *dev, return q6dsp_audio_fe_dais; } -static int q6apm_lpass_dai_dummy_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { snd_soc_set_runtime_hwparams(substream, &q6apm_dummy_dma_hardware); return 0; } +static void q6i2s_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + + if (dai_data->priv[dai->id].mclk_enabled) { + clk_disable_unprepare(dai_data->priv[dai->id].mclk); + dai_data->priv[dai->id].mclk_enabled = false; + } + + if (dai_data->priv[dai->id].bclk_enabled) { + clk_disable_unprepare(dai_data->priv[dai->id].bclk); + dai_data->priv[dai->id].bclk_enabled = false; + } + + if (dai_data->priv[dai->id].eclk_enabled) { + clk_disable_unprepare(dai_data->priv[dai->id].eclk); + dai_data->priv[dai->id].eclk_enabled = false; + } + q6apm_lpass_dai_shutdown(substream, dai); +} + +static int q6i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct clk *sysclk = NULL; + bool *enabled = NULL; + int ret = 0; + + switch (clk_id) { + case LPAIF_MI2S_MCLK: + sysclk = dai_data->priv[dai->id].mclk; + enabled = &dai_data->priv[dai->id].mclk_enabled; + break; + case LPAIF_MI2S_BCLK: + sysclk = dai_data->priv[dai->id].bclk; + enabled = &dai_data->priv[dai->id].bclk_enabled; + break; + case LPAIF_MI2S_ECLK: + sysclk = dai_data->priv[dai->id].eclk; + enabled = &dai_data->priv[dai->id].eclk_enabled; + break; + default: + return -EINVAL; + } + + if (sysclk) { + if (*enabled) + return 0; + + clk_set_rate(sysclk, freq); + ret = clk_prepare_enable(sysclk); + if (ret) { + dev_err(dai->dev, "Error, Unable to prepare (%d) sysclk\n", clk_id); + return ret; + } + + *enabled = true; + } + + return ret; +} + static const struct snd_soc_dai_ops q6dummy_ops = { - .startup = q6apm_lpass_dai_dummy_startup, + .startup = q6apm_lpass_dai_startup, }; static const struct snd_soc_dai_ops q6i2sdummy_ops = { - .startup = q6apm_lpass_dai_dummy_startup, + .startup = q6apm_lpass_dai_startup, + .set_sysclk = q6i2s_set_sysclk, + .shutdown = q6i2s_lpass_dai_shutdown, }; static const struct snd_soc_dai_ops q6tdmdummy_ops = { - .startup = q6apm_lpass_dai_dummy_startup, + .startup = q6apm_lpass_dai_startup, }; static const struct snd_soc_component_driver q6apm_lpass_dummy_dai_component = { @@ -711,6 +783,73 @@ static const struct snd_soc_component_driver q6apm_lpass_dummy_dai_component = { .use_dai_pcm_id = false, }; +static int of_q6apm_parse_dai_data(struct device *dev, + struct q6apm_lpass_dai_data *data) +{ + int ret; + + for_each_child_of_node_scoped(dev->of_node, node) { + struct q6apm_dai_priv_data *priv; + int id; + + ret = of_property_read_u32(node, "reg", &id); + if (ret || id < 0 || id >= APM_PORT_MAX) { + dev_err(dev, "valid dai id not found:%d\n", ret); + continue; + } + + switch (id) { + /* MI2S specific properties */ + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + priv = &data->priv[id]; + priv->mclk = of_clk_get_by_name(node, "mclk"); + if (IS_ERR(priv->mclk)) { + if (PTR_ERR(priv->mclk) == -EPROBE_DEFER) + return dev_err_probe(dev, PTR_ERR(priv->mclk), + "unable to get mi2s mclk\n"); + priv->mclk = NULL; + } + + priv->bclk = of_clk_get_by_name(node, "bclk"); + if (IS_ERR(priv->bclk)) { + if (PTR_ERR(priv->bclk) == -EPROBE_DEFER) { + if (priv->mclk) { + clk_put(priv->mclk); + priv->mclk = NULL; + } + return dev_err_probe(dev, PTR_ERR(priv->bclk), + "unable to get mi2s bclk\n"); + } + priv->bclk = NULL; + } + + priv->eclk = of_clk_get_by_name(node, "eclk"); + if (IS_ERR(priv->eclk)) { + if (PTR_ERR(priv->eclk) == -EPROBE_DEFER) { + if (priv->mclk) { + clk_put(priv->mclk); + priv->mclk = NULL; + } + if (priv->bclk) { + clk_put(priv->bclk); + priv->bclk = NULL; + } + return dev_err_probe(dev, PTR_ERR(priv->eclk), + "unable to get mi2s eclk\n"); + } + priv->eclk = NULL; + } + break; + default: + break; + } + } + + return 0; +} + static int q6apm_lpass_dummy_dai_dev_probe(struct platform_device *pdev) { const struct snd_soc_component_driver *q6apm_lpass_component = NULL; @@ -719,12 +858,16 @@ static int q6apm_lpass_dummy_dai_dev_probe(struct platform_device *pdev) struct snd_soc_dai_driver *dais; struct device *dev = &pdev->dev; int num_dais; + int ret; dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); if (!dai_data) return -ENOMEM; dev_set_drvdata(dev, dai_data); + ret = of_q6apm_parse_dai_data(dev, dai_data); + if (ret) + return ret; memset(&cfg, 0, sizeof(cfg)); diff --git a/audioreach-driver/q6prm_audioreach.h b/audioreach-driver/q6prm_audioreach.h index 51fb155..4917abb 100644 --- a/audioreach-driver/q6prm_audioreach.h +++ b/audioreach-driver/q6prm_audioreach.h @@ -6,6 +6,10 @@ #include +#define LPAIF_MI2S_MCLK 1 +#define LPAIF_MI2S_BCLK 2 +#define LPAIF_MI2S_ECLK 3 + /* Clock ID for Primary I2S IBIT */ #define Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100 /* Clock ID for Primary I2S EBIT */ From 76a97f0c2447ff7750c5fb27622f5d46a2b5517b Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Wed, 8 Jul 2026 15:21:51 +0530 Subject: [PATCH 2/3] audioreach-driver: Enhance machine driver for board-specific config The sc8280xp machine driver is currently written with a largely SoC-centric view and assumes a uniform audio topology across all boards. In practice, multiple products based on the same SoC use different board designs and external audio components, which require board-specific configuration to function correctly. Several Qualcomm platforms like talos integrate third-party audio codecs or use different external audio paths. These designs often require additional configuration such as explicit MI2S MCLK settings for audio to work. This change enhances the sc8280xp machine driver to support board-specific configuration such as allowing each board variant to provide its own DAPM widgets and routes, reflecting the actual audio components and connectors present and enabling MI2S MCLK programming for boards that use external codecs requiring a stable master clock. Signed-off-by: Mohammad Rafi Shaik --- audioreach-driver/audioreach_common.c | 289 ++++++++++++++++++++++---- 1 file changed, 252 insertions(+), 37 deletions(-) diff --git a/audioreach-driver/audioreach_common.c b/audioreach-driver/audioreach_common.c index 523b8bd..c195fdd 100644 --- a/audioreach-driver/audioreach_common.c +++ b/audioreach-driver/audioreach_common.c @@ -17,26 +17,14 @@ #define AFE_PORT_MAX 137 #define NAME_SIZE 32 -struct qcs6490_snd_data { - bool stream_prepared[AFE_PORT_MAX]; - struct snd_soc_card *card; - struct sdw_stream_runtime *sruntime[AFE_PORT_MAX]; - struct snd_soc_jack jack; - struct snd_soc_jack dp_jack[8]; - bool jack_setup; -}; +#define I2S_MCLKFS 256 -struct qcom_snd_dailink_data { - u32 mclk_fs; - u32 mclk_id; - u32 clk_direction; -}; +#define I2S_MCLK_RATE(rate) \ + ((rate) * (I2S_MCLKFS)) +#define I2S_BIT_RATE(rate, channels, format) \ + ((rate) * (channels) * (format)) -struct qcom_snd_common_data { - struct qcom_snd_dailink_data *link_data; -}; - -static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { +static struct snd_soc_dapm_widget qcs6490_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("Mic Jack", NULL), SND_SOC_DAPM_SPK("DP0 Jack", NULL), @@ -49,6 +37,35 @@ static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { SND_SOC_DAPM_SPK("DP7 Jack", NULL), }; +struct snd_soc_common { + const char *driver_name; + const struct snd_soc_dapm_widget *dapm_widgets; + int num_dapm_widgets; + const struct snd_soc_dapm_route *dapm_routes; + int num_dapm_routes; + const struct snd_kcontrol_new *controls; + int num_controls; + unsigned int codec_dai_fmt; + int num_wsa_spkr; + bool codec_sysclk_set; + bool mi2s_mclk_enable; + bool mi2s_bclk_enable; + bool wcd_jack; +}; + +struct qcs6490_snd_data { + bool stream_prepared[AFE_PORT_MAX]; + struct snd_soc_card *card; + struct sdw_stream_runtime *sruntime[AFE_PORT_MAX]; + struct snd_soc_jack jack; + struct snd_soc_jack dp_jack[8]; + struct snd_soc_common *snd_soc_common_priv; + bool jack_setup; +}; + +struct qcom_snd_common_data { + struct qcom_snd_dailink_data *link_data; +}; static struct snd_soc_jack_pin qcs6490_headset_jack_pins[] = { /* Headset */ @@ -62,6 +79,29 @@ static struct snd_soc_jack_pin qcs6490_headset_jack_pins[] = { }, }; +static inline int qcs6490_get_mclk_freq(struct snd_pcm_hw_params *params) +{ + int rate = params_rate(params); + + switch (rate) { + case 11025: + case 44100: + case 88200: + return I2S_MCLK_RATE(44100); + default: + break; + } + + return I2S_MCLK_RATE(rate); +} + +static inline int qcs6490_get_bclk_freq(struct snd_pcm_hw_params *params) +{ + return I2S_BIT_RATE(params_rate(params), + params_channels(params), + snd_pcm_format_width(params_format(params))); +} + static int qcs6490_snd_sdw_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); @@ -564,10 +604,24 @@ static int qcs6490_snd_init(struct snd_soc_pcm_runtime *rtd) * to reduce the risk of speaker damage until we have active * speaker protection in place. */ - snd_soc_limit_volume(card, "WSA_RX0 Digital Volume", 81); - snd_soc_limit_volume(card, "WSA_RX1 Digital Volume", 81); - snd_soc_limit_volume(card, "SpkrLeft PA Volume", 17); - snd_soc_limit_volume(card, "SpkrRight PA Volume", 17); + if (data->snd_soc_common_priv->num_wsa_spkr == 4) { + snd_soc_limit_volume(card, "WSA WSA_RX0 Digital Volume", 81); + snd_soc_limit_volume(card, "WSA WSA_RX1 Digital Volume", 81); + snd_soc_limit_volume(card, "WSA2 WSA_RX0 Digital Volume", 81); + snd_soc_limit_volume(card, "WSA2 WSA_RX1 Digital Volume", 81); + snd_soc_limit_volume(card, "SpkrLeft PA Volume", 6); + snd_soc_limit_volume(card, "SpkrRight PA Volume", 6); + snd_soc_limit_volume(card, "WooferLeft PA Volume", 6); + snd_soc_limit_volume(card, "TweeterLeft PA Volume", 6); + snd_soc_limit_volume(card, "WooferRight PA Volume", 6); + snd_soc_limit_volume(card, "TweeterRight PA Volume", 6); + } else { + snd_soc_limit_volume(card, "WSA_RX0 Digital Volume", 81); + snd_soc_limit_volume(card, "WSA_RX1 Digital Volume", 81); + snd_soc_limit_volume(card, "SpkrLeft PA Volume", 17); + snd_soc_limit_volume(card, "SpkrRight PA Volume", 17); + } + break; case DISPLAY_PORT_RX_0: /* DISPLAY_PORT dai ids are not contiguous */ @@ -585,7 +639,10 @@ static int qcs6490_snd_init(struct snd_soc_pcm_runtime *rtd) if (dp_jack) return qcs6490_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id); - return qcs6490_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); + if (data->snd_soc_common_priv->wcd_jack) + return qcs6490_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); + + return 0; } static int qcs6490_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, @@ -598,8 +655,6 @@ static int qcs6490_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, 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: @@ -619,8 +674,54 @@ static int qcs6490_snd_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); struct qcs6490_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card); + int mclk_freq = qcs6490_get_mclk_freq(params); + int bclk_freq = qcs6490_get_bclk_freq(params); + + switch (cpu_dai->id) { + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); + if (ret && ret != -ENOTSUPP) + return ret; + + if (pdata->snd_soc_common_priv->codec_dai_fmt) { + ret = snd_soc_dai_set_fmt(codec_dai, + pdata->snd_soc_common_priv->codec_dai_fmt); + if (ret && ret != -ENOTSUPP) + return ret; + } + + if (pdata->snd_soc_common_priv->mi2s_mclk_enable) { + ret = snd_soc_dai_set_sysclk(cpu_dai, + LPAIF_MI2S_MCLK, mclk_freq, + SND_SOC_CLOCK_OUT); + if (ret) + return ret; + } + + if (pdata->snd_soc_common_priv->mi2s_bclk_enable) { + ret = snd_soc_dai_set_sysclk(cpu_dai, + LPAIF_MI2S_BCLK, bclk_freq, + SND_SOC_CLOCK_OUT); + if (ret) + return ret; + } + + if (pdata->snd_soc_common_priv->codec_sysclk_set) { + ret = snd_soc_dai_set_sysclk(codec_dai, + 0, mclk_freq, + SND_SOC_CLOCK_IN); + if (ret) + return ret; + } + break; + default: + break; + } return qcs6490_snd_sdw_hw_params(substream, params, &pdata->sruntime[cpu_dai->id]); } @@ -677,35 +778,149 @@ static int qcs6490_platform_probe(struct platform_device *pdev) 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; + data->snd_soc_common_priv = (struct snd_soc_common *)of_device_get_match_data(dev); + if (!data->snd_soc_common_priv) + return -ENODEV; + + card->owner = THIS_MODULE; card->dev = dev; dev_set_drvdata(dev, card); snd_soc_card_set_drvdata(card, data); + card->dapm_widgets = data->snd_soc_common_priv->dapm_widgets; + card->num_dapm_widgets = data->snd_soc_common_priv->num_dapm_widgets; + card->dapm_routes = data->snd_soc_common_priv->dapm_routes; + card->num_dapm_routes = data->snd_soc_common_priv->num_dapm_routes; + card->controls = data->snd_soc_common_priv->controls; + card->num_controls = data->snd_soc_common_priv->num_controls; + ret = qcs6490_snd_parse_of(card); if (ret) return ret; - card->driver_name = of_device_get_match_data(dev); + card->driver_name = data->snd_soc_common_priv->driver_name; qcs6490_add_be_ops(card); return devm_snd_soc_register_card(dev, card); } +static struct snd_soc_common glymur_priv_data = { + .driver_name = "glymur", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .num_wsa_spkr = 4; + .wcd_jack = true, +}; + +static struct snd_soc_common kaanapali_priv_data = { + .driver_name = "kaanapali", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .num_wsa_spkr = 2; + .wcd_jack = true, +}; + +static struct snd_soc_common qcs9100_priv_data = { + .driver_name = "sa8775p", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), +}; + +static struct snd_soc_common qcs615_priv_data = { + .driver_name = "qcs615", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .mi2s_mclk_enable = true, +}; + +static struct snd_soc_common qcm6490_priv_data = { + .driver_name = "qcm6490", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .num_wsa_spkr = 2; + .wcd_jack = true, +}; + +static struct snd_soc_common qcs6490_priv_data = { + .driver_name = "qcs6490", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .num_wsa_spkr = 2; + .wcd_jack = true, +}; + +static struct snd_soc_common qcs8275_priv_data = { + .driver_name = "qcs8300", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), +}; + +static struct snd_soc_common sc8280xp_priv_data = { + .driver_name = "sc8280xp", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .num_wsa_spkr = 2; + .wcd_jack = true, +}; + +static struct snd_soc_common sm8450_priv_data = { + .driver_name = "sm8450", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .num_wsa_spkr = 2; + .wcd_jack = true, +}; + +static struct snd_soc_common sm8550_priv_data = { + .driver_name = "sm8550", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .num_wsa_spkr = 2; + .wcd_jack = true, +}; + +static struct snd_soc_common sm8650_priv_data = { + .driver_name = "sm8650", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .num_wsa_spkr = 2; + .wcd_jack = true, +}; + +static struct snd_soc_common sm8750_priv_data = { + .driver_name = "sm8750", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .num_wsa_spkr = 2; + .wcd_jack = true, +}; + +static struct snd_soc_common x1e80100_priv_data = { + .driver_name = "x1e80100", + .dapm_widgets = qcs6490_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .num_wsa_spkr = 4; + .wcd_jack = true, +}; + static const struct of_device_id snd_qcs6490_dt_match[] = { - {.compatible = "qcom,qcm6490-idp-sndcard", "qcm6490"}, - {.compatible = "qcom,qcs615-sndcard", "qcs615"}, - {.compatible = "qcom,qcs6490-rb3gen2-sndcard", "qcs6490"}, - {.compatible = "qcom,qcs8275-sndcard", "qcs8275"}, - {.compatible = "qcom,qcs8300-sndcard", "qcs8300"}, - {.compatible = "qcom,qcs9075-sndcard", "qcs9075"}, - {.compatible = "qcom,qcs9100-sndcard", "qcs9100"}, - {.compatible = "qcom,sm8750-sndcard", "sm8750"}, - {.compatible = "qcom,x1e80100-sndcard", "x1e80100"}, - {.compatible = "qcom,glymur-sndcard", "glymur"}, + {.compatible = "qcom,glymur-sndcard", .data = &glymur_priv_data}, + {.compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data}, + {.compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data}, + {.compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data}, + {.compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data}, + {.compatible = "qcom,qcs8275-sndcard", .data = &qcs8275_priv_data}, + {.compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data}, + {.compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data}, + {.compatible = "qcom,sc8280xp-sndcard", .data = &sc8280xp_priv_data}, + {.compatible = "qcom,sm8450-sndcard", .data = &sm8450_priv_data}, + {.compatible = "qcom,sm8550-sndcard", .data = &sm8550_priv_data}, + {.compatible = "qcom,sm8650-sndcard", .data = &sm8650_priv_data}, + {.compatible = "qcom,sm8750-sndcard", .data = &sm8750_priv_data}, + {.compatible = "qcom,x1e80100-sndcard", .data = &x1e80100_priv_data}, {} }; From bc901df02135599743396cfbebf0bd13c35cc979 Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Thu, 9 Jul 2026 13:07:23 +0530 Subject: [PATCH 3/3] audioreach-driver: Fix Glymur playback mute/unmute handling Fix a back-to-back playback issue on Glymur where the speaker stream remains muted after a previous playback session. The existing mute handling is not synchronized with the DAPM power state transitions, which can leave the audio path in an incorrect state when streams are stopped and restarted in quick succession. Resolve this by moving the mute/unmute stream control to DAPM event handlers, ensuring the stream is unmuted when the playback path is powered up and muted when the path is powered down. This restores correct audio playback across consecutive playback. Signed-off-by: Mohammad Rafi Shaik --- audioreach-driver/audioreach_common.c | 242 +++++++++++++++------ audioreach-driver/q6apm_audio.h | 4 + audioreach-driver/q6apm_lpass_dummy_dais.c | 2 - 3 files changed, 184 insertions(+), 64 deletions(-) diff --git a/audioreach-driver/audioreach_common.c b/audioreach-driver/audioreach_common.c index c195fdd..aa37289 100644 --- a/audioreach-driver/audioreach_common.c +++ b/audioreach-driver/audioreach_common.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -24,19 +25,6 @@ #define I2S_BIT_RATE(rate, channels, format) \ ((rate) * (channels) * (format)) -static struct snd_soc_dapm_widget qcs6490_dapm_widgets[] = { - SND_SOC_DAPM_HP("Headphone Jack", NULL), - SND_SOC_DAPM_MIC("Mic Jack", NULL), - SND_SOC_DAPM_SPK("DP0 Jack", NULL), - SND_SOC_DAPM_SPK("DP1 Jack", NULL), - SND_SOC_DAPM_SPK("DP2 Jack", NULL), - SND_SOC_DAPM_SPK("DP3 Jack", NULL), - SND_SOC_DAPM_SPK("DP4 Jack", NULL), - SND_SOC_DAPM_SPK("DP5 Jack", NULL), - SND_SOC_DAPM_SPK("DP6 Jack", NULL), - SND_SOC_DAPM_SPK("DP7 Jack", NULL), -}; - struct snd_soc_common { const char *driver_name; const struct snd_soc_dapm_widget *dapm_widgets; @@ -79,6 +67,141 @@ static struct snd_soc_jack_pin qcs6490_headset_jack_pins[] = { }, }; +static char *qcs6490_wsa_spk_to_prefix(const char *spk_name) +{ + if (!strcmp(spk_name, "WooferLeft Speaker")) + return "WooferLeft"; + if (!strcmp(spk_name, "TweeterLeft Speaker")) + return "TweeterLeft"; + if (!strcmp(spk_name, "WooferRight Speaker")) + return "WooferRight"; + if (!strcmp(spk_name, "TweeterRight Speaker")) + return "TweeterRight"; + if (!strcmp(spk_name, "SpkrLeft Speaker")) + return "SpkrLeft"; + if (!strcmp(spk_name, "SpkrRight Speaker")) + return "SpkrRight"; + + return NULL; +} + +static void qcs6490_wsa_mute_stream(struct snd_soc_card *card, + const char *prefix, int mute) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *cpu_dai, *codec_dai; + int i, ret; + + for_each_card_rtds(card, rtd) { + cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + if (cpu_dai->id != WSA_CODEC_DMA_RX_0 && + cpu_dai->id != WSA_CODEC_DMA_RX_1) + continue; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + const char *name_prefix; + + if (!codec_dai->component || !codec_dai->driver || + !codec_dai->driver->ops || + !codec_dai->driver->ops->mute_stream) + continue; + + name_prefix = codec_dai->component->name_prefix; + if (!name_prefix || strcmp(name_prefix, prefix)) + continue; + + ret = codec_dai->driver->ops->mute_stream(codec_dai, mute, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret && ret != -ENOTSUPP) + dev_warn(card->dev, + "mute_stream failed for %s on %s: %d\n", + prefix, codec_dai->name, ret); + return; + } + } +} + +static int qcs6490_wsa_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + char *prefix = qcs6490_wsa_spk_to_prefix(w->name); + + if (!prefix) + return 0; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + qcs6490_wsa_mute_stream(snd_soc_dapm_to_card(w->dapm), prefix, 0); + break; + case SND_SOC_DAPM_PRE_PMD: + qcs6490_wsa_mute_stream(snd_soc_dapm_to_card(w->dapm), prefix, 1); + break; + default: + break; + } + + return 0; +} + +static struct snd_soc_dapm_widget qcs6490_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("DP0 Jack", NULL), + SND_SOC_DAPM_SPK("DP1 Jack", NULL), + SND_SOC_DAPM_SPK("DP2 Jack", NULL), + SND_SOC_DAPM_SPK("DP3 Jack", NULL), + SND_SOC_DAPM_SPK("DP4 Jack", NULL), + SND_SOC_DAPM_SPK("DP5 Jack", NULL), + SND_SOC_DAPM_SPK("DP6 Jack", NULL), + SND_SOC_DAPM_SPK("DP7 Jack", NULL), + SND_SOC_DAPM_SPK("SpkrLeft Speaker", qcs6490_wsa_spk_event), + SND_SOC_DAPM_SPK("SpkrRight Speaker", qcs6490_wsa_spk_event), + +}; + +static const struct snd_soc_dapm_route qcs6490_dapm_routes[] = { + { "SpkrLeft Speaker", NULL, "SpkrLeft SPKR" }, + { "SpkrRight Speaker", NULL, "SpkrRight SPKR" }, +}; + +static struct snd_soc_dapm_widget glymur_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("DP0 Jack", NULL), + SND_SOC_DAPM_SPK("DP1 Jack", NULL), + SND_SOC_DAPM_SPK("DP2 Jack", NULL), + SND_SOC_DAPM_SPK("DP3 Jack", NULL), + SND_SOC_DAPM_SPK("DP4 Jack", NULL), + SND_SOC_DAPM_SPK("DP5 Jack", NULL), + SND_SOC_DAPM_SPK("DP6 Jack", NULL), + SND_SOC_DAPM_SPK("DP7 Jack", NULL), + SND_SOC_DAPM_SPK("WooferLeft Speaker", qcs6490_wsa_spk_event), + SND_SOC_DAPM_SPK("TweeterLeft Speaker", qcs6490_wsa_spk_event), + SND_SOC_DAPM_SPK("WooferRight Speaker", qcs6490_wsa_spk_event), + SND_SOC_DAPM_SPK("TweeterRight Speaker", qcs6490_wsa_spk_event), +}; + +static const struct snd_soc_dapm_route glymur_dapm_routes[] = { + { "WooferLeft Speaker", NULL, "WooferLeft SPKR" }, + { "TweeterLeft Speaker", NULL, "TweeterLeft SPKR" }, + { "WooferRight Speaker", NULL, "WooferRight SPKR" }, + { "TweeterRight Speaker", NULL, "TweeterRight SPKR" }, +}; + +static struct snd_soc_dapm_widget sa8775p_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("DP0 Jack", NULL), + SND_SOC_DAPM_SPK("DP1 Jack", NULL), + SND_SOC_DAPM_SPK("DP2 Jack", NULL), + SND_SOC_DAPM_SPK("DP3 Jack", NULL), + SND_SOC_DAPM_SPK("DP4 Jack", NULL), + SND_SOC_DAPM_SPK("DP5 Jack", NULL), + SND_SOC_DAPM_SPK("DP6 Jack", NULL), + SND_SOC_DAPM_SPK("DP7 Jack", NULL), +}; + static inline int qcs6490_get_mclk_freq(struct snd_pcm_hw_params *params) { int rate = params_rate(params); @@ -167,7 +290,7 @@ static int qcs6490_snd_sdw_prepare(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); struct snd_soc_dai *codec_dai; - int ret, i; + int ret; if (!sruntime) return 0; @@ -208,17 +331,6 @@ static int qcs6490_snd_sdw_prepare(struct snd_pcm_substream *substream, } *stream_prepared = true; - switch (cpu_dai->id) { - case WSA_CODEC_DMA_RX_0: - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - for_each_rtd_codec_dais(rtd, i, codec_dai) - snd_soc_dai_digital_mute(codec_dai, 0, substream->stream); - } - break; - default: - break; - } - return ret; } @@ -286,19 +398,6 @@ static void qcs6490_snd_shutdown(struct snd_pcm_substream *substream) struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); struct qcs6490_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card); struct sdw_stream_runtime *sruntime = pdata->sruntime[cpu_dai->id]; - struct snd_soc_dai *codec_dai; - int i; - - switch (cpu_dai->id) { - case WSA_CODEC_DMA_RX_0: - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - for_each_rtd_codec_dais(rtd, i, codec_dai) - snd_soc_dai_digital_mute(codec_dai, 1, substream->stream); - } - break; - default: - break; - } pdata->sruntime[cpu_dai->id] = NULL; sdw_release_stream(sruntime); @@ -574,8 +673,8 @@ static int qcs6490_snd_parse_of(struct snd_soc_card *card) } if (!card->dapm_widgets) { - card->dapm_widgets = qcom_jack_snd_widgets; - card->num_dapm_widgets = ARRAY_SIZE(qcom_jack_snd_widgets); + card->dapm_widgets = qcs6490_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets); } return 0; @@ -679,11 +778,11 @@ static int qcs6490_snd_hw_params(struct snd_pcm_substream *substream, struct qcs6490_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card); int mclk_freq = qcs6490_get_mclk_freq(params); int bclk_freq = qcs6490_get_bclk_freq(params); + int ret = 0; switch (cpu_dai->id) { case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: - case SENARY_MI2S_RX ... SENARY_MI2S_TX: ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); if (ret && ret != -ENOTSUPP) return ret; @@ -809,9 +908,11 @@ static int qcs6490_platform_probe(struct platform_device *pdev) static struct snd_soc_common glymur_priv_data = { .driver_name = "glymur", - .dapm_widgets = qcs6490_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), - .num_wsa_spkr = 4; + .dapm_widgets = glymur_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(glymur_dapm_widgets), + .dapm_routes = glymur_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(glymur_dapm_routes), + .num_wsa_spkr = 4, .wcd_jack = true, }; @@ -819,28 +920,31 @@ static struct snd_soc_common kaanapali_priv_data = { .driver_name = "kaanapali", .dapm_widgets = qcs6490_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), - .num_wsa_spkr = 2; + .dapm_routes = qcs6490_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(qcs6490_dapm_routes), + .num_wsa_spkr = 2, .wcd_jack = true, }; static struct snd_soc_common qcs9100_priv_data = { .driver_name = "sa8775p", - .dapm_widgets = qcs6490_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .dapm_widgets = sa8775p_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sa8775p_dapm_widgets), }; static struct snd_soc_common qcs615_priv_data = { .driver_name = "qcs615", - .dapm_widgets = qcs6490_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), - .mi2s_mclk_enable = true, + .dapm_widgets = sa8775p_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sa8775p_dapm_widgets), }; static struct snd_soc_common qcm6490_priv_data = { .driver_name = "qcm6490", .dapm_widgets = qcs6490_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), - .num_wsa_spkr = 2; + .dapm_routes = qcs6490_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(qcs6490_dapm_routes), + .num_wsa_spkr = 2, .wcd_jack = true, }; @@ -848,21 +952,25 @@ static struct snd_soc_common qcs6490_priv_data = { .driver_name = "qcs6490", .dapm_widgets = qcs6490_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), - .num_wsa_spkr = 2; + .dapm_routes = qcs6490_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(qcs6490_dapm_routes), + .num_wsa_spkr = 2, .wcd_jack = true, }; static struct snd_soc_common qcs8275_priv_data = { .driver_name = "qcs8300", - .dapm_widgets = qcs6490_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), + .dapm_widgets = sa8775p_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sa8775p_dapm_widgets), }; static struct snd_soc_common sc8280xp_priv_data = { .driver_name = "sc8280xp", .dapm_widgets = qcs6490_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), - .num_wsa_spkr = 2; + .dapm_routes = qcs6490_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(qcs6490_dapm_routes), + .num_wsa_spkr = 2, .wcd_jack = true, }; @@ -870,7 +978,9 @@ static struct snd_soc_common sm8450_priv_data = { .driver_name = "sm8450", .dapm_widgets = qcs6490_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), - .num_wsa_spkr = 2; + .dapm_routes = qcs6490_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(qcs6490_dapm_routes), + .num_wsa_spkr = 2, .wcd_jack = true, }; @@ -878,7 +988,9 @@ static struct snd_soc_common sm8550_priv_data = { .driver_name = "sm8550", .dapm_widgets = qcs6490_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), - .num_wsa_spkr = 2; + .dapm_routes = qcs6490_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(qcs6490_dapm_routes), + .num_wsa_spkr = 2, .wcd_jack = true, }; @@ -886,7 +998,9 @@ static struct snd_soc_common sm8650_priv_data = { .driver_name = "sm8650", .dapm_widgets = qcs6490_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), - .num_wsa_spkr = 2; + .dapm_routes = qcs6490_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(qcs6490_dapm_routes), + .num_wsa_spkr = 2, .wcd_jack = true, }; @@ -894,15 +1008,19 @@ static struct snd_soc_common sm8750_priv_data = { .driver_name = "sm8750", .dapm_widgets = qcs6490_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), - .num_wsa_spkr = 2; + .dapm_routes = qcs6490_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(qcs6490_dapm_routes), + .num_wsa_spkr = 2, .wcd_jack = true, }; static struct snd_soc_common x1e80100_priv_data = { .driver_name = "x1e80100", - .dapm_widgets = qcs6490_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(qcs6490_dapm_widgets), - .num_wsa_spkr = 4; + .dapm_widgets = glymur_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(glymur_dapm_widgets), + .dapm_routes = glymur_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(glymur_dapm_routes), + .num_wsa_spkr = 4, .wcd_jack = true, }; diff --git a/audioreach-driver/q6apm_audio.h b/audioreach-driver/q6apm_audio.h index 9503955..13199c0 100644 --- a/audioreach-driver/q6apm_audio.h +++ b/audioreach-driver/q6apm_audio.h @@ -53,4 +53,8 @@ struct apm_module_param_data { #define APM_MODULE_PARAM_DATA_SIZE sizeof(struct apm_module_param_data) +struct snd_soc_dai_driver * + q6dsp_audio_ports_set_config(struct device *dev, + struct q6dsp_audio_port_dai_driver_config *cfg, + int *num_dais); #endif diff --git a/audioreach-driver/q6apm_lpass_dummy_dais.c b/audioreach-driver/q6apm_lpass_dummy_dais.c index c88cb63..44c34f9 100644 --- a/audioreach-driver/q6apm_lpass_dummy_dais.c +++ b/audioreach-driver/q6apm_lpass_dummy_dais.c @@ -718,7 +718,6 @@ static void q6i2s_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct clk_disable_unprepare(dai_data->priv[dai->id].eclk); dai_data->priv[dai->id].eclk_enabled = false; } - q6apm_lpass_dai_shutdown(substream, dai); } static int q6i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) @@ -802,7 +801,6 @@ static int of_q6apm_parse_dai_data(struct device *dev, /* MI2S specific properties */ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: - case SENARY_MI2S_RX ... SENARY_MI2S_TX: priv = &data->priv[id]; priv->mclk = of_clk_get_by_name(node, "mclk"); if (IS_ERR(priv->mclk)) {