From 6e117e403d996fceedb3d9df5cd9e6599d98b829 Mon Sep 17 00:00:00 2001 From: Pratyush Meduri Date: Tue, 26 May 2026 12:36:19 +0530 Subject: [PATCH] ASoC: qcom: sc8280xp: add Shikra machine support Register the Shikra machine compatible in the sc8280xp machine driver and add BE handling for Shikra LPASS TDM links, including optional AUD_INTF bit-clock setup and TDM slot configuration from DT properties. Signed-off-by: Pratyush Meduri --- sound/soc/qcom/sc8280xp.c | 160 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 155 insertions(+), 5 deletions(-) diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 7925aa3f63ba0..45b1a75cd11cb 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -2,16 +2,19 @@ // Copyright (c) 2022, Linaro Limited #include +#include +#include +#include #include #include -#include -#include -#include -#include #include #include -#include +#include +#include +#include +#include #include "qdsp6/q6afe.h" +#include "qdsp6/q6prm.h" #include "common.h" #include "sdw.h" @@ -21,6 +24,11 @@ struct sc8280xp_snd_data { struct snd_soc_jack jack; struct snd_soc_jack dp_jack[8]; bool jack_setup; + /* TDM slot config per DAI id, read from DT */ + unsigned int tdm_slot_width[AFE_PORT_MAX]; + unsigned int tdm_nslots[AFE_PORT_MAX]; + /* AUD_INTF CCF clocks, one per TDM interface (0=PRI..4=QUIN) */ + struct clk *tdm_clk[5]; }; static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) @@ -36,6 +44,20 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) case QUINARY_MI2S_RX...QUINARY_MI2S_TX: snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); break; + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: { + unsigned int nslots = 2, slot_width = 32; + + of_property_read_u32(rtd->card->dev->of_node, + "qcom,tdm-slots", &nslots); + of_property_read_u32(rtd->card->dev->of_node, + "qcom,tdm-slot-width", &slot_width); + + data->tdm_nslots[cpu_dai->id] = nslots; + data->tdm_slot_width[cpu_dai->id] = slot_width; + + snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); + break; + } case WSA_CODEC_DMA_RX_0: case WSA_CODEC_DMA_RX_1: /* @@ -82,6 +104,7 @@ static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, channels->min = 2; channels->max = 2; switch (cpu_dai->id) { + case VA_CODEC_DMA_TX_0: case TX_CODEC_DMA_TX_0: case TX_CODEC_DMA_TX_1: case TX_CODEC_DMA_TX_2: @@ -92,6 +115,102 @@ static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, break; } + return 0; +} + +/* + * Map TDM DAI id to the corresponding LPASS AUD_INTF IBIT clock ID. + * Hawi uses AUD_INTF{N}_IBIT clocks (0x500+) instead of legacy PRI_TDM_IBIT. + * Each TDM interface spans 16 DAI IDs (8 RX + 8 TX). + */ +static int sc8280xp_tdm_get_clk_id(int dai_id) +{ + if (dai_id >= PRIMARY_TDM_RX_0 && dai_id <= PRIMARY_TDM_RX_0 + 15) + return Q6PRM_LPASS_CLK_ID_AUD_INTF0_IBIT; + if (dai_id >= SECONDARY_TDM_RX_0 && dai_id <= SECONDARY_TDM_RX_0 + 15) + return Q6PRM_LPASS_CLK_ID_AUD_INTF1_IBIT; + if (dai_id >= TERTIARY_TDM_RX_0 && dai_id <= TERTIARY_TDM_RX_0 + 15) + return Q6PRM_LPASS_CLK_ID_AUD_INTF2_IBIT; + if (dai_id >= QUATERNARY_TDM_RX_0 && dai_id <= QUATERNARY_TDM_RX_0 + 15) + return Q6PRM_LPASS_CLK_ID_AUD_INTF3_IBIT; + if (dai_id >= QUINARY_TDM_RX_0 && dai_id <= QUINARY_TDM_RX_0 + 15) + return Q6PRM_LPASS_CLK_ID_AUD_INTF4_IBIT; + return -EINVAL; +} + +static int sc8280xp_tdm_snd_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + unsigned int rate = substream->runtime->rate; + unsigned int slot_width, nslots, clk_freq; + int intf_idx; + int ret; + + intf_idx = sc8280xp_tdm_get_clk_id(cpu_dai->id); + if (intf_idx < 0) + return intf_idx; + + /* Map AUD_INTF index from clk_id offset */ + intf_idx = (intf_idx - Q6PRM_LPASS_CLK_ID_AUD_INTF0_IBIT) / 2; + if (intf_idx < 0 || intf_idx >= ARRAY_SIZE(data->tdm_clk)) + return -EINVAL; + + slot_width = data->tdm_slot_width[cpu_dai->id] ? + data->tdm_slot_width[cpu_dai->id] : 32; + nslots = data->tdm_nslots[cpu_dai->id] ? + data->tdm_nslots[cpu_dai->id] : 8; + + intf_idx = 2; + /* clk_freq = sample_rate * slot_width * nslots_per_frame */ + clk_freq = rate * slot_width * nslots; + + if (!data->tdm_clk[intf_idx]) { + dev_dbg(rtd->dev, "no AUD_INTF clock for intf%d, skipping\n", + intf_idx); + return 0; + } + + ret = clk_set_rate(data->tdm_clk[intf_idx], clk_freq); + if (ret) { + dev_err(rtd->dev, "Failed to set TDM clock rate %u: %d\n", + clk_freq, ret); + return ret; + } + + ret = clk_prepare_enable(data->tdm_clk[intf_idx]); + if (ret) + return ret; + + if (codec_dai && strnstr(codec_dai->name, "wsa885x", strlen(codec_dai->name))) { + unsigned int tdm_clk = rate * 0x02 * slot_width; + + snd_soc_dai_set_tdm_slot(cpu_dai, 0x0f, 0b11, 0x02, slot_width); + snd_soc_dai_set_sysclk(codec_dai, 0, tdm_clk, 0); + } + + return 0; +} + +static int sc8280xp_tdm_snd_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + int intf_idx; + + intf_idx = sc8280xp_tdm_get_clk_id(cpu_dai->id); + if (intf_idx < 0) + return intf_idx; + + intf_idx = (intf_idx - Q6PRM_LPASS_CLK_ID_AUD_INTF0_IBIT) / 2; + if (intf_idx < 0 || intf_idx >= ARRAY_SIZE(data->tdm_clk)) + return -EINVAL; + + if (data->tdm_clk[intf_idx]) + clk_disable_unprepare(data->tdm_clk[intf_idx]); return 0; } @@ -102,6 +221,16 @@ static int sc8280xp_snd_prepare(struct snd_pcm_substream *substream) struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + /* + * For TDM DAI IDs, enable the AUD_INTF IBIT clock at the computed + * bit-clock frequency (sample_rate * slot_width * nslots). + * For SoundWire DAI IDs, delegate to the SDW helper which handles + * stream prepare/enable. The SDW helper already guards itself with + * qcom_snd_is_sdw_dai(), so it is safe to call for any DAI ID. + */ + if (cpu_dai->id >= PRIMARY_TDM_RX_0 && cpu_dai->id <= QUINARY_TDM_TX_7) + return sc8280xp_tdm_snd_prepare(substream); + return qcom_snd_sdw_prepare(substream, &data->stream_prepared[cpu_dai->id]); } @@ -111,6 +240,10 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream) struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + /* TDM: disable the AUD_INTF IBIT clock */ + if (cpu_dai->id >= PRIMARY_TDM_RX_0 && cpu_dai->id <= QUINARY_TDM_TX_7) + return sc8280xp_tdm_snd_hw_free(substream); + return qcom_snd_sdw_hw_free(substream, &data->stream_prepared[cpu_dai->id]); } @@ -159,6 +292,22 @@ static int sc8280xp_platform_probe(struct platform_device *pdev) return ret; card->driver_name = of_device_get_match_data(dev); + + /* Acquire AUD_INTF IBIT clocks for TDM interfaces (optional, Hawi) */ + { + int i; + static const char * const clk_names[] = { + "aud-intf0-ibit", "aud-intf1-ibit", "aud-intf2-ibit", + "aud-intf3-ibit", "aud-intf4-ibit", + }; + + for (i = 0; i < ARRAY_SIZE(data->tdm_clk); i++) { + data->tdm_clk[i] = devm_clk_get_optional(dev, clk_names[i]); + if (IS_ERR(data->tdm_clk[i])) + return PTR_ERR(data->tdm_clk[i]); + } + } + sc8280xp_add_be_ops(card); return devm_snd_soc_register_card(dev, card); } @@ -176,6 +325,7 @@ static const struct of_device_id snd_sc8280xp_dt_match[] = { {.compatible = "qcom,sm8550-sndcard", "sm8550"}, {.compatible = "qcom,sm8650-sndcard", "sm8650"}, {.compatible = "qcom,sm8750-sndcard", "sm8750"}, + {.compatible = "qcom,shikra-sndcard", "shikra"}, {} };