From f4f99e1b4f880e708bd9c3709538eecf2678a79d Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Tue, 18 Nov 2025 12:15:24 +0530 Subject: [PATCH 1/2] ASoC: qcom: sc8280xp: Add mclk support for mi2s interface Signed-off-by: Mohammad Rafi Shaik --- sound/soc/qcom/qdsp6/q6apm-lpass-dais.c | 149 +++++++++++++++++++++++- sound/soc/qcom/sc8280xp.c | 25 ++++ 2 files changed, 172 insertions(+), 2 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index 528756f1332bcf..6c8531677a6d8b 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -15,13 +17,23 @@ #include "q6dsp-common.h" #include "audioreach.h" #include "q6apm.h" +#include "q6prm.h" #define AUDIOREACH_BE_PCM_BASE 16 +struct q6apm_dai_priv_data { + struct clk *mclk; + struct clk *bclk; + 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_lpass_clk_data *clk_data[APM_PORT_MAX]; + struct q6apm_dai_priv_data priv[APM_PORT_MAX]; }; static int q6dma_set_channel_map(struct snd_soc_dai *dai, @@ -159,6 +171,27 @@ static void q6apm_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct } } +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 q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); @@ -238,6 +271,11 @@ static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct s return 0; } +static int q6i2s_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + return q6apm_lpass_dai_startup(substream, dai); +} + static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); @@ -248,6 +286,52 @@ static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; } +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; + bool *enabled; + int ret = 0; + + switch (clk_id) { + case Q6PRM_LPASS_CLK_ID_MCLK_1...Q6PRM_LPASS_CLK_ID_MCLK_5: + sysclk = dai_data->priv[dai->id].mclk; + enabled = &dai_data->priv[dai->id].mclk_enabled; + break; + case Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT: + case Q6PRM_LPASS_CLK_ID_SEC_MI2S_IBIT: + case Q6PRM_LPASS_CLK_ID_TER_MI2S_IBIT: + case Q6PRM_LPASS_CLK_ID_QUAD_MI2S_IBIT: + case Q6PRM_LPASS_CLK_ID_QUI_MI2S_IBIT: + sysclk = dai_data->priv[dai->id].bclk; + enabled = &dai_data->priv[dai->id].bclk_enabled; + break; + case Q6PRM_LPASS_CLK_ID_PRI_MI2S_EBIT: + case Q6PRM_LPASS_CLK_ID_SEC_MI2S_EBIT: + case Q6PRM_LPASS_CLK_ID_TER_MI2S_EBIT: + case Q6PRM_LPASS_CLK_ID_QUAD_MI2S_EBIT: + case Q6PRM_LPASS_CLK_ID_QUI_MI2S_EBIT: + sysclk = dai_data->priv[dai->id].eclk; + enabled = &dai_data->priv[dai->id].eclk_enabled; + break; + default: + break; + } + + if (sysclk) { + 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 q6dma_ops = { .prepare = q6apm_lpass_dai_prepare, .startup = q6apm_lpass_dai_startup, @@ -258,11 +342,12 @@ static const struct snd_soc_dai_ops q6dma_ops = { static const struct snd_soc_dai_ops q6i2s_ops = { .prepare = q6apm_lpass_dai_prepare, - .startup = q6apm_lpass_dai_startup, - .shutdown = q6apm_lpass_dai_shutdown, + .startup = q6i2s_dai_startup, + .shutdown = q6i2s_lpass_dai_shutdown, .set_channel_map = q6dma_set_channel_map, .hw_params = q6dma_hw_params, .set_fmt = q6i2s_set_fmt, + .set_sysclk = q6i2s_set_sysclk, }; static const struct snd_soc_dai_ops q6hdmi_ops = { @@ -279,7 +364,63 @@ static const struct snd_soc_component_driver q6apm_lpass_dai_component = { .be_pcm_base = AUDIOREACH_BE_PCM_BASE, .use_dai_pcm_id = true, }; +static int of_q6apm_parse_dai_data(struct device *dev, + struct q6apm_lpass_dai_data *data) +{ + struct device_node *node; + int ret; + for_each_child_of_node(dev->of_node, node) { + struct q6apm_dai_priv_data *priv; + int id, i; + + 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: + priv = &data->priv[id]; + priv->mclk = of_clk_get_by_name(node, "mclk"); + if (IS_ERR(priv->mclk)) { + if(PTR_ERR(priv->mclk) == -ENOENT) { + dev_err_probe(dev, PTR_ERR(priv->mclk), "unable to get mi2s mclk\n"); + return -EPROBE_DEFER; + } + priv->mclk = NULL; + } + + priv->bclk = of_clk_get_by_name(node, "bclk"); + if (IS_ERR(priv->bclk)) { + if(PTR_ERR(priv->bclk) == -ENOENT) { + dev_err_probe(dev, PTR_ERR(priv->bclk), "unable to get mi2s bclk\n"); + return -EPROBE_DEFER; + } + + priv->bclk = NULL; + } + + priv->eclk = of_clk_get_by_name(node, "eclk"); + if (IS_ERR(priv->eclk)) { + if(PTR_ERR(priv->eclk) == -ENOENT) { + dev_err_probe(dev, PTR_ERR(priv->eclk), "unable to get mi2s eclk\n"); + return -EPROBE_DEFER; + } + + priv->eclk = NULL; + + } + break; + default: + break; + } + } + return 0; +} static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) { struct q6dsp_audio_port_dai_driver_config cfg; @@ -287,12 +428,16 @@ static int q6apm_lpass_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)); cfg.q6i2s_ops = &q6i2s_ops; diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 187f37ffe32837..19f4e1c48978a1 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -12,6 +12,7 @@ #include #include #include "qdsp6/q6afe.h" +#include "qdsp6/q6prm.h" #include "common.h" #include "sdw.h" @@ -114,6 +115,30 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); struct sc8280xp_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card); + int ret = 0; + + switch (cpu_dai->id) { + case PRIMARY_MI2S_RX...PRIMARY_MI2S_TX: + ret = snd_soc_dai_set_sysclk(cpu_dai, Q6PRM_LPASS_CLK_ID_MCLK_1, 12288000, SND_SOC_CLOCK_IN); + break; + case SECONDARY_MI2S_RX...SECONDARY_MI2S_TX: + ret = snd_soc_dai_set_sysclk(cpu_dai, Q6PRM_LPASS_CLK_ID_MCLK_2, 12288000, SND_SOC_CLOCK_IN); + break; + case TERTIARY_MI2S_RX...TERTIARY_MI2S_TX: + ret = snd_soc_dai_set_sysclk(cpu_dai, Q6PRM_LPASS_CLK_ID_MCLK_3, 12288000, SND_SOC_CLOCK_IN); + break; + case QUATERNARY_MI2S_RX...QUATERNARY_MI2S_TX: + ret = snd_soc_dai_set_sysclk(cpu_dai, Q6PRM_LPASS_CLK_ID_MCLK_4, 12288000, SND_SOC_CLOCK_IN); + break; + case QUINARY_MI2S_RX...QUINARY_MI2S_TX: + ret = snd_soc_dai_set_sysclk(cpu_dai, Q6PRM_LPASS_CLK_ID_MCLK_5, 12288000, SND_SOC_CLOCK_IN); + break; + default: + break; + } + + if (ret < 0) + dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); return qcom_snd_sdw_hw_params(substream, params, &pdata->sruntime[cpu_dai->id]); } From 8c9b918a502f1277c84e9f32dd803321a3b308ae Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Tue, 18 Nov 2025 12:16:43 +0530 Subject: [PATCH 2/2] arm64: dts: qcom: lemans-evk: Enable MI2S clocks for audio Signed-off-by: Mohammad Rafi Shaik --- arch/arm64/boot/dts/qcom/lemans-evk.dts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/lemans-evk.dts b/arch/arm64/boot/dts/qcom/lemans-evk.dts index c7dc9b8f445787..f7a7c3d11cc704 100644 --- a/arch/arm64/boot/dts/qcom/lemans-evk.dts +++ b/arch/arm64/boot/dts/qcom/lemans-evk.dts @@ -587,6 +587,24 @@ status = "okay"; }; +&q6apmbedai { + dai@16 { + reg = ; + clocks = <&q6prmcc LPASS_CLK_ID_MCLK_1 LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_CLK_ID_PRI_MI2S_IBIT LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_CLK_ID_PRI_MI2S_EBIT LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "mclk", "bclk", "eclk"; + }; + + dai@21 { + reg = ; + clocks = <&q6prmcc LPASS_CLK_ID_MCLK_1 LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_CLK_ID_TER_MI2S_IBIT LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_CLK_ID_TER_MI2S_EBIT LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "mclk", "bclk", "eclk"; + }; +}; + &qupv3_id_0 { status = "okay"; };