From 7d3ad2f202f127136a0aba1fa4158b4e74d798ff Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Mon, 25 Aug 2025 23:08:33 +0100 Subject: [PATCH 01/14] ASoC: qcom: sm8250: set capture channels correctly Signed-off-by: Srinivas Kandagatla --- sound/soc/qcom/sm8250.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c index f193d0ba63d00..3857594963113 100644 --- a/sound/soc/qcom/sm8250.c +++ b/sound/soc/qcom/sm8250.c @@ -58,6 +58,7 @@ static void sm8250_snd_exit(struct snd_soc_pcm_runtime *rtd) static int sm8250_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); struct snd_interval *channels = hw_param_interval(params, @@ -68,6 +69,18 @@ static int sm8250_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, channels->min = channels->max = 2; snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + 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; + channels->min = channels->max = 1; + break; + default: + break; + } + return 0; } From 571989ec44cd0679e71936a8aa10cb0427a44e5f Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 12 Sep 2025 18:18:31 +0100 Subject: [PATCH 02/14] ASoC: qcom: sm8250: set i2s format needs Signed-off-by: Srinivas Kandagatla --- sound/soc/qcom/sm8250.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c index 3857594963113..8f6e1b03ad003 100644 --- a/sound/soc/qcom/sm8250.c +++ b/sound/soc/qcom/sm8250.c @@ -16,7 +16,7 @@ #include "usb_offload_utils.h" #include "sdw.h" -#define MI2S_BCLK_RATE 1536000 +#define MI2S_BCLK_RATE 3072000 struct sm8250_snd_data { bool stream_prepared[AFE_PORT_MAX]; @@ -77,6 +77,14 @@ static int sm8250_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, channels->min = 1; channels->min = channels->max = 1; break; + case PRIMARY_MI2S_RX: + case SECONDARY_MI2S_RX: + case TERTIARY_MI2S_RX: + /* clean any param mask before setting new param */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), + 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + break; default: break; } From fa795189ea243c3e98a2bef3a3a4ff64861f5d2c Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 12 Sep 2025 18:19:05 +0100 Subject: [PATCH 03/14] ASoC: qcom: q6dsp: add 32 bit support for i2s backend dais Signed-off-by: Srinivas Kandagatla --- sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 14 +++++++++----- sound/soc/qcom/qdsp6/q6routing.c | 3 +++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c index e5cd82f77b552..507009dea60a8 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c @@ -389,7 +389,7 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, .channels_min = 1, .channels_max = 8, .rate_min = 8000, @@ -416,7 +416,8 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { .stream_name = "Secondary MI2S Playback", .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, .channels_min = 1, .channels_max = 8, .rate_min = 8000, @@ -443,7 +444,8 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { .stream_name = "Tertiary MI2S Playback", .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, .channels_min = 1, .channels_max = 8, .rate_min = 8000, @@ -470,7 +472,8 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { .stream_name = "Quaternary MI2S Playback", .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, .channels_min = 1, .channels_max = 8, .rate_min = 8000, @@ -498,7 +501,8 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, .channels_min = 1, .channels_max = 8, .rate_min = 8000, diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c index 7386226046fae..bbaa47303456f 100644 --- a/sound/soc/qcom/qdsp6/q6routing.c +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -1083,6 +1083,9 @@ static int routing_hw_params(struct snd_soc_component *component, case SNDRV_PCM_FORMAT_S24_LE: session->bits_per_sample = 24; break; + case SNDRV_PCM_FORMAT_S32_LE: + session->bits_per_sample = 32; + break; default: break; } From f817f9780ce9cf29c0c5cdc53ee7f0e9011b7d98 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 12 Sep 2025 18:20:20 +0100 Subject: [PATCH 04/14] anx7625: allow 32 bit format The chip requires 32 bit frame, with 16-24 bits of valid data left aligned TODO: check that the output format is correct Signed-off-by: Srinivas Kandagatla --- drivers/gpu/drm/bridge/analogix/anx7625.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 54b02242d6db4..8c2046c156d51 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -2044,6 +2044,7 @@ static int anx7625_audio_hw_params(struct device *dev, void *data, wl = AUDIO_W_LEN_20_20MAX; break; case 24: + case 32: wl = AUDIO_W_LEN_24_24MAX; break; default: From 69d81a17505b2296ca76a5f3c182e1ff91dcf327 Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Tue, 14 Apr 2026 18:19:54 +0200 Subject: [PATCH 05/14] [DONOTUPSTREAM] drm: anx7625: set I2S input stream as right justified Limitiations of Qualcomm DSP make it very hard to left justify (chip's default) Change-Id: I9ec695bf8dee36d7061499d6d8f060d7361eabf8 --- drivers/gpu/drm/bridge/analogix/anx7625.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 8c2046c156d51..9c1095e065f79 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -2084,6 +2084,11 @@ static int anx7625_audio_hw_params(struct device *dev, void *data, ret |= anx7625_write_and(ctx, ctx->i2c.tx_p2_client, AUDIO_CHANNEL_STATUS_6, ~AUDIO_LAYOUT); + + /* Right justified for Qualcomm DSP limitations */ + ret |= anx7625_write_or(ctx, ctx->i2c.tx_p2_client, + AUDIO_CONTROL_REGISTER, 1); + /* FS */ switch (params->sample_rate) { case 32000: From 4fc8ffb156e561f2d3e5f090fc0cf7aab3c76796 Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Thu, 16 Apr 2026 14:53:29 +0200 Subject: [PATCH 06/14] ASoC: qcom: qdsp6: fix microphone stream re-enable --- sound/soc/qcom/qdsp6/q6asm-dai.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c index 4f09fdd409058..d6f2e80375da6 100644 --- a/sound/soc/qcom/qdsp6/q6asm-dai.c +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -222,8 +222,12 @@ static int q6asm_dai_prepare(struct snd_soc_component *component, } prtd->pcm_count = snd_pcm_lib_period_bytes(substream); - /* rate and channels are sent to audio driver */ - if (prtd->state == Q6ASM_STREAM_RUNNING) { + /* + * Re-prepare can be called after STOP without closing the previous + * stream session. Ensure any non-idle session is torn down before + * issuing a new OPEN command. + */ + if (prtd->state != Q6ASM_STREAM_IDLE) { /* clear the previous setup if any */ ret = q6asm_cmd(prtd->audio_client, prtd->stream_id, CMD_CLOSE); if (ret < 0) { @@ -240,7 +244,7 @@ static int q6asm_dai_prepare(struct snd_soc_component *component, q6routing_stream_close(soc_prtd->dai_link->id, substream->stream); - prtd->state = Q6ASM_STREAM_STOPPED; + prtd->state = Q6ASM_STREAM_IDLE; } ret = q6asm_map_memory_regions(substream->stream, prtd->audio_client, @@ -265,7 +269,7 @@ static int q6asm_dai_prepare(struct snd_soc_component *component, } if (ret < 0) { - dev_err(dev, "%s: q6asm_open_write failed\n", __func__); + dev_err(dev, "%s: q6asm_open failed\n", __func__); goto open_err; } From f1c26570a12cd47b98721d60381721daea464227 Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Wed, 1 Apr 2026 09:49:15 +0200 Subject: [PATCH 07/14] ASoC: codecs: pm4125: harden swr discovery path Completes ASoC: codecs: pm4125: fix array-out-of-bounds by adding extra checks. This avoids a race condition crash on dapm_power_widgets(), where for_each_card_dapms(card, d) returns a corrupted pointer Change-Id: Ie95312c9cb3fb7ee83dc3f1bffbdb1f2881f1026 Signed-off-by: Martino Facchin --- sound/soc/codecs/pm4125.c | 82 +++++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/pm4125.c b/sound/soc/codecs/pm4125.c index 1f0a3f5389f1b..6de37a358e02e 100644 --- a/sound/soc/codecs/pm4125.c +++ b/sound/soc/codecs/pm4125.c @@ -842,13 +842,32 @@ static int pm4125_codec_enable_micbias_pullup(struct snd_soc_dapm_widget *w, static int pm4125_connect_port(struct pm4125_sdw_priv *sdw_priv, u8 port_idx, u8 ch_id, bool enable) { - struct sdw_port_config *port_config = &sdw_priv->port_config[port_idx - 1]; - const struct wcd_sdw_ch_info *ch_info = &sdw_priv->ch_info[ch_id]; + struct sdw_port_config *port_config; + const struct wcd_sdw_ch_info *ch_info; struct sdw_slave *sdev = sdw_priv->sdev; - u8 port_num = ch_info->port_num; - u8 ch_mask = ch_info->ch_mask; + u8 port_num, ch_mask; u8 mstr_port_num, mstr_ch_mask; + if (!port_idx) /* Invalid port index */ + return -EINVAL; + + if (sdw_priv->is_tx) { + if (ch_id > PM4125_ADC2) + return -EINVAL; + } else { + if (ch_id > PM4125_COMP_R) + return -EINVAL; + } + + ch_info = &sdw_priv->ch_info[ch_id]; + port_num = ch_info->port_num; + ch_mask = ch_info->ch_mask; + + if (!port_num || port_num > PM4125_MAX_SWR_PORTS) + return -EINVAL; + + port_config = &sdw_priv->port_config[port_idx - 1]; + port_config->num = port_num; mstr_port_num = sdev->m_port_map[port_num]; @@ -888,10 +907,18 @@ static int pm4125_set_compander(struct snd_kcontrol *kcontrol, struct snd_ctl_el struct soc_mixer_control *mc; int portidx; bool hphr; + int ch_idx; mc = (struct soc_mixer_control *)(kcontrol->private_value); + ch_idx = mc->reg; hphr = mc->shift; + if (!sdw_priv) + return -EINVAL; + + if (ch_idx < 0 || ch_idx > PM4125_COMP_R) + return -EINVAL; + if (hphr) { if (value == pm4125->comp2_enable) return 0; @@ -904,9 +931,11 @@ static int pm4125_set_compander(struct snd_kcontrol *kcontrol, struct snd_ctl_el pm4125->comp1_enable = value; } - portidx = sdw_priv->ch_info[mc->reg].port_num; + portidx = sdw_priv->ch_info[ch_idx].port_num; + if (!portidx) + return 0; - pm4125_connect_port(sdw_priv, portidx, mc->reg, value ? true : false); + pm4125_connect_port(sdw_priv, portidx, ch_idx, value ? true : false); return 1; } @@ -921,8 +950,24 @@ static int pm4125_get_swr_port(struct snd_kcontrol *kcontrol, struct snd_ctl_ele int ch_idx = mixer->reg; int portidx; + if (dai_id < 0 || dai_id >= NUM_CODEC_DAIS) + return -EINVAL; + sdw_priv = pm4125->sdw_priv[dai_id]; + if (!sdw_priv) + return -EINVAL; + + if (sdw_priv->is_tx) { + if (ch_idx < 0 || ch_idx > PM4125_ADC2) + return -EINVAL; + } else { + if (ch_idx < 0 || ch_idx > PM4125_HPH_R) + return -EINVAL; + } + portidx = sdw_priv->ch_info[ch_idx].port_num; + if (!portidx) /* Invalid port index */ + return 0; ucontrol->value.integer.value[0] = sdw_priv->port_enable[portidx]; @@ -940,9 +985,24 @@ static int pm4125_set_swr_port(struct snd_kcontrol *kcontrol, struct snd_ctl_ele int portidx; bool enable; + if (dai_id < 0 || dai_id >= NUM_CODEC_DAIS) + return -EINVAL; + sdw_priv = pm4125->sdw_priv[dai_id]; + if (!sdw_priv) + return -EINVAL; + + if (sdw_priv->is_tx) { + if (ch_idx < 0 || ch_idx > PM4125_ADC2) + return -EINVAL; + } else { + if (ch_idx < 0 || ch_idx > PM4125_HPH_R) + return -EINVAL; + } portidx = sdw_priv->ch_info[ch_idx].port_num; + if (!portidx) /* Invalid port index */ + return 0; enable = ucontrol->value.integer.value[0]; @@ -1411,8 +1471,16 @@ static int pm4125_codec_free(struct snd_pcm_substream *substream, struct snd_soc { struct pm4125_priv *pm4125 = dev_get_drvdata(dai->dev); struct pm4125_sdw_priv *sdw_priv = pm4125->sdw_priv[dai->id]; + int ret; + + /* hw_free() can be invoked again on a DAPM-driven route change; avoid a stale stream UAF */ + if (!sdw_priv->sruntime) + return 0; - return sdw_stream_remove_slave(sdw_priv->sdev, sdw_priv->sruntime); + ret = sdw_stream_remove_slave(sdw_priv->sdev, sdw_priv->sruntime); + sdw_priv->sruntime = NULL; + + return ret; } static int pm4125_codec_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) From fdaf66d01bad95c451c57afa1b976241876d771d Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Mon, 16 Mar 2026 13:55:53 +0000 Subject: [PATCH 08/14] ASoC: qcom: common: setup fe dais before be dais On Elite DSP architecuture q6routing setup depends on some of the pcm dais to be probed to setup the routing. For some reason if the devicetree entires are incorrectly ordered, specially in overlays, this could result in failures like below q6routing ab00000.remoteproc:glink-edge:apr:service@8:routing: ASoC: Failed to add route MM_DL1(*) -> [MultiMedia1] -> HDMI Mixer q6routing ab00000.remoteproc:glink-edge:apr:service@8:routing: ASoC: Failed to add route MM_DL2(*) -> [MultiMedia2] -> HDMI Mixer q6routing ab00000.remoteproc:glink-edge:apr:service@8:routing: ASoC: Failed to add route MM_DL3(*) -> [MultiMedia3] -> HDMI Mixer q6routing ab00000.remoteproc:glink-edge:apr:service@8:routing: ASoC: Failed to add route MM_DL4(*) -> [MultiMedia4] -> HDMI Mixer q6routing ab00000.remoteproc:glink-edge:apr:service@8:routing: ASoC: Failed to add route MM_DL5(*) -> [MultiMedia5] -> HDMI Mixer q6routing ab00000.remoteproc:glink-edge:apr:service@8:routing: ASoC: Failed to add route MM_DL6(*) -> [MultiMedia6] -> HDMI Mixer q6routing ab00000.remoteproc:glink-edge:apr:service@8:routing: ASoC: Failed to add route MM_DL7(*) -> [MultiMedia7] -> HDMI Mixer q6routing ab00000.remoteproc:glink-edge:apr:service@8:routing: ASoC: Failed to add route MM_DL8(*) -> [MultiMedia8] -> HDMI Mixer Fix this by setting up the pcm dais first and then the backend dais after. Signed-off-by: Srinivas Kandagatla --- sound/soc/qcom/common.c | 203 ++++++++++++++++++++++------------------ 1 file changed, 114 insertions(+), 89 deletions(-) diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index cf1f3a767ceef..4f0c421359a47 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -23,16 +23,110 @@ static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { SND_SOC_DAPM_SPK("DP7 Jack", NULL), }; -int qcom_snd_parse_of(struct snd_soc_card *card) +static int qcom_snd_setup_dai_links(struct snd_soc_card *card, struct snd_soc_dai_link *link, + struct device_node *np) { - struct device_node *np; struct device_node *codec = NULL; struct device_node *platform = NULL; struct device_node *cpu = NULL; struct device *dev = card->dev; - struct snd_soc_dai_link *link; struct of_phandle_args args; struct snd_soc_dai_link_component *dlc; + int ret; + + dlc = devm_kcalloc(dev, 2, sizeof(*dlc), GFP_KERNEL); + if (!dlc) { + ret = -ENOMEM; + goto err_put_np; + } + link->cpus = &dlc[0]; + link->platforms = &dlc[1]; + + link->num_cpus = 1; + link->num_platforms = 1; + + ret = of_property_read_string(np, "link-name", &link->name); + if (ret) { + dev_err(card->dev, "error getting codec dai_link name\n"); + goto err_put_np; + } + + cpu = of_get_child_by_name(np, "cpu"); + platform = of_get_child_by_name(np, "platform"); + codec = of_get_child_by_name(np, "codec"); + + if (!cpu) { + dev_err(dev, "%s: Can't find cpu DT node\n", link->name); + ret = -EINVAL; + goto err; + } + + ret = snd_soc_of_get_dlc(cpu, &args, link->cpus, 0); + if (ret) { + dev_err_probe(card->dev, ret, + "%s: error getting cpu dai name\n", link->name); + goto err; + } + + link->id = args.args[0]; + + if (platform) { + link->platforms->of_node = of_parse_phandle(platform, + "sound-dai", + 0); + if (!link->platforms->of_node) { + dev_err(card->dev, "%s: platform dai not found\n", link->name); + ret = -EINVAL; + goto err; + } + } else { + link->platforms->of_node = link->cpus->of_node; + } + + if (codec) { + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + if (ret < 0) { + dev_err_probe(card->dev, ret, + "%s: codec dai not found\n", link->name); + goto err; + } + + if (platform) { + /* DPCM backend */ + link->no_pcm = 1; + link->ignore_pmdown_time = 1; + } + } else { + /* DPCM frontend */ + link->codecs = &snd_soc_dummy_dlc; + link->num_codecs = 1; + link->dynamic = 1; + } + + if (platform || !codec) { + /* DPCM */ + link->ignore_suspend = 1; + link->nonatomic = 1; + } + link->stream_name = link->name; + + ret = 0; +err: + of_node_put(cpu); + of_node_put(codec); + of_node_put(platform); +err_put_np: + of_node_put(np); + + return ret; +} + +int qcom_snd_parse_of(struct snd_soc_card *card) +{ + struct device_node *np; + struct device_node *codec = NULL; + struct device *dev = card->dev; + struct snd_soc_dai_link *link; int ret, num_links; ret = snd_soc_of_parse_card_name(card, "model"); @@ -82,95 +176,33 @@ int qcom_snd_parse_of(struct snd_soc_card *card) card->num_links = num_links; link = card->dai_link; + /* setup pcm dais first */ for_each_available_child_of_node(dev->of_node, np) { - dlc = devm_kcalloc(dev, 2, sizeof(*dlc), GFP_KERNEL); - if (!dlc) { - ret = -ENOMEM; - goto err_put_np; - } - - link->cpus = &dlc[0]; - link->platforms = &dlc[1]; - - link->num_cpus = 1; - link->num_platforms = 1; - - ret = of_property_read_string(np, "link-name", &link->name); - if (ret) { - dev_err(card->dev, "error getting codec dai_link name\n"); - goto err_put_np; - } - - cpu = of_get_child_by_name(np, "cpu"); - platform = of_get_child_by_name(np, "platform"); codec = of_get_child_by_name(np, "codec"); - if (!cpu) { - dev_err(dev, "%s: Can't find cpu DT node\n", link->name); - ret = -EINVAL; - goto err; - } - - ret = snd_soc_of_get_dlc(cpu, &args, link->cpus, 0); - if (ret) { - dev_err_probe(card->dev, ret, - "%s: error getting cpu dai name\n", link->name); - goto err; - } - - link->id = args.args[0]; - - if (link->id >= LPASS_MAX_PORT) { - dev_err(dev, "%s: Invalid cpu dai id %d\n", link->name, link->id); - ret = -EINVAL; - goto err; + if (codec) { + of_node_put(codec); + continue; } - if (platform) { - link->platforms->of_node = of_parse_phandle(platform, - "sound-dai", - 0); - if (!link->platforms->of_node) { - dev_err(card->dev, "%s: platform dai not found\n", link->name); - ret = -EINVAL; - goto err; - } - } else { - link->platforms->of_node = link->cpus->of_node; - } + ret = qcom_snd_setup_dai_links(card, link, np); + if (ret) + return ret; - if (codec) { - ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); - if (ret < 0) { - dev_err_probe(card->dev, ret, - "%s: codec dai not found\n", link->name); - goto err; - } + link++; + } - if (platform) { - /* DPCM backend */ - link->no_pcm = 1; - link->ignore_pmdown_time = 1; - } - } else { - /* DPCM frontend */ - link->codecs = &snd_soc_dummy_dlc; - link->num_codecs = 1; - link->dynamic = 1; - } + /* setup backend dais */ + for_each_available_child_of_node(dev->of_node, np) { + codec = of_get_child_by_name(np, "codec"); + if (!codec) + continue; - if (platform || !codec) { - /* DPCM */ - link->ignore_suspend = 1; - link->nonatomic = 1; - } + ret = qcom_snd_setup_dai_links(card, link, np); + if (ret) + return ret; - link->stream_name = link->name; link++; - - of_node_put(cpu); - of_node_put(codec); - of_node_put(platform); } if (!card->dapm_widgets) { @@ -178,13 +210,6 @@ int qcom_snd_parse_of(struct snd_soc_card *card) card->num_dapm_widgets = ARRAY_SIZE(qcom_jack_snd_widgets); } - return 0; -err: - of_node_put(cpu); - of_node_put(codec); - of_node_put(platform); -err_put_np: - of_node_put(np); return ret; } EXPORT_SYMBOL_GPL(qcom_snd_parse_of); From 70cbdf3e56877c804df05be816a07a0e4e528749 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Thu, 10 Jul 2025 16:56:54 +0200 Subject: [PATCH 09/14] [DONOTUPSTREAM] anx7625: Support USB if no display If there is no DP or eDP link, we shoudl still able to handle USB-C operation, such as host/device detection. Change-Id: Iad533b2a012072d19bd07d03eb4b572b4893e9f5 Signed-off-by: Loic Poulain --- drivers/gpu/drm/bridge/analogix/anx7625.c | 17 +++++++++++++---- drivers/gpu/drm/bridge/analogix/anx7625.h | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 9c1095e065f79..1abfc8ec80b83 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -1796,6 +1796,9 @@ static void anx7625_work_func(struct work_struct *work) struct anx7625_data *ctx = container_of(work, struct anx7625_data, work); + if (!ctx->display) + return; + mutex_lock(&ctx->lock); if (pm_runtime_suspended(ctx->dev)) { @@ -2873,6 +2876,7 @@ static int anx7625_i2c_probe(struct i2c_client *client) } } + platform->display = true; platform->aux.name = "anx7625-aux"; platform->aux.dev = dev; platform->aux.transfer = anx7625_aux_transfer; @@ -2880,13 +2884,16 @@ static int anx7625_i2c_probe(struct i2c_client *client) drm_dp_aux_init(&platform->aux); ret = anx7625_parse_dt(dev, pdata); - if (ret) { + if (ret == -ENODEV) { + /* Not using the display function, but we want USB-C function */ + platform->display = false; + } else if (ret) { if (ret != -EPROBE_DEFER) DRM_DEV_ERROR(dev, "fail to parse DT : %d\n", ret); goto free_wq; } - if (!platform->pdata.is_dpi) { + if (platform->display && !platform->pdata.is_dpi) { ret = anx7625_setup_dsi_device(platform); if (ret < 0) goto free_wq; @@ -2915,7 +2922,8 @@ static int anx7625_i2c_probe(struct i2c_client *client) * be done after calls that might return EPROBE_DEFER, otherwise we can * get an infinite loop. */ - ret = devm_of_dp_aux_populate_bus(&platform->aux, anx7625_link_bridge); + if (platform->display) + ret = devm_of_dp_aux_populate_bus(&platform->aux, anx7625_link_bridge); if (ret) { if (ret != -ENODEV) { DRM_DEV_ERROR(dev, "failed to populate aux bus : %d\n", ret); @@ -2980,7 +2988,8 @@ static void anx7625_i2c_remove(struct i2c_client *client) anx7625_typec_unregister(platform); - drm_bridge_remove(&platform->bridge); + if (platform->display) + drm_bridge_remove(&platform->bridge); if (platform->pdata.intp_irq) destroy_workqueue(platform->workqueue); diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.h b/drivers/gpu/drm/bridge/analogix/anx7625.h index 957d234ec07c8..5370406bd2936 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.h +++ b/drivers/gpu/drm/bridge/analogix/anx7625.h @@ -509,6 +509,7 @@ struct anx7625_data { struct mipi_dsi_device *dsi; struct drm_dp_aux aux; struct fw_msg send_msg; + bool display; }; #endif /* __ANX7625_H__ */ From b7910baec4593457af6d43afdc71ae356a2ca789 Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Mon, 8 Jun 2026 14:57:11 +0200 Subject: [PATCH 10/14] [DONOTUPSTREAM] anx7625: workaround for display broken on media carrier Change-Id: I3f9a130d6709a71fa93a6ff2ff3a77847b16912b --- drivers/gpu/drm/bridge/analogix/anx7625.c | 47 +++++++++++++++++++++++ drivers/gpu/drm/bridge/analogix/anx7625.h | 2 + 2 files changed, 49 insertions(+) diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 1abfc8ec80b83..0aa03f33811f8 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -606,6 +606,16 @@ static int anx7625_api_dsi_config(struct anx7625_data *ctx) return ret; } + for (int i = 0; i < 5; i++) { + /* Set MIPI RX termination to 75ohm */ + ret = anx7625_reg_write(ctx, ctx->i2c.rx_p1_client, + MIPI_ANALOG_CTRL_0 + i, 0xf8); + if (ret < 0) { + DRM_DEV_ERROR(dev, "IO error : set lane %d termination fail.\n", i); + return ret; + } + } + /* DSI clock settings */ val = (0 << MIPI_HS_PWD_CLK) | (0 << MIPI_HS_RT_CLK) | @@ -1326,6 +1336,39 @@ static int anx7625_read_hpd_gpio_config_status(struct anx7625_data *ctx) return anx7625_reg_read(ctx, ctx->i2c.rx_p0_client, GPIO_CTRL_2); } +static ssize_t mipi_check_sum_err_hs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct anx7625_data *ctx = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + mutex_lock(&ctx->lock); + ret = anx7625_reg_read(ctx, ctx->i2c.rx_p1_client, 0x19); + mutex_unlock(&ctx->lock); + + pm_runtime_put_autosuspend(dev); + + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%u\n", !!(ret & BIT(5))); +} +static DEVICE_ATTR_RO(mipi_check_sum_err_hs); + +static struct attribute *anx7625_attrs[] = { + &dev_attr_mipi_check_sum_err_hs.attr, + NULL, +}; + +static const struct attribute_group anx7625_attr_group = { + .attrs = anx7625_attrs, +}; + static void anx7625_disable_pd_protocol(struct anx7625_data *ctx) { struct device *dev = ctx->dev; @@ -2917,6 +2960,10 @@ static int anx7625_i2c_probe(struct i2c_client *client) if (ret) goto free_wq; + ret = devm_device_add_group(dev, &anx7625_attr_group); + if (ret) + goto free_wq; + /* * Populating the aux bus will retrigger deferred probe, so it needs to * be done after calls that might return EPROBE_DEFER, otherwise we can diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.h b/drivers/gpu/drm/bridge/analogix/anx7625.h index 5370406bd2936..72f16eacc0e68 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.h +++ b/drivers/gpu/drm/bridge/analogix/anx7625.h @@ -293,6 +293,8 @@ #define MIPI_LANE_CTRL_0 0x05 #define MIPI_TIME_HS_PRPR 0x08 +#define MIPI_ANALOG_CTRL_0 0x10 + /* * After MIPI RX protocol layer received video frames, * Protocol layer starts to reconstruct video stream from PHY From 0a5090da2993120a865853240d254de518563184 Mon Sep 17 00:00:00 2001 From: Ibrahim Abdelkader Date: Wed, 19 Aug 2026 14:54:25 +0200 Subject: [PATCH 11/14] Bluetooth: hci_qca: Do not write to the serial port after it is closed hci_uart_close() closes the serdev port if HCI_QUIRK_NON_PERSISTENT_SETUP is set (for example, for the WCN399x family). A failed hci_dev_open_sync() following a successful qca_setup() calls hdev->close() but not hdev->shutdown(), so the port is closed while power->vregs_on is left true. qca_serdev_remove() then passes its power->vregs_on test and calls qca_power_off(), which writes to the closed port unconditionally. Seen on a WCN3988 by unbinding the driver after a controller failure. The trace below is from a 7.0.0 based kernel, where qca_power_off() was still named qca_power_shutdown(): Unable to handle kernel NULL pointer dereference at virtual address 0000000000000038 Call trace: tty_set_termios+0x50/0x238 (P) ttyport_set_baudrate+0x84/0xc0 serdev_device_set_baudrate+0x24/0x40 qca_power_shutdown+0x158/0x1fc [hci_uart] qca_serdev_remove+0x54/0x68 [hci_uart] serdev_drv_remove+0x1c/0x2c device_remove+0x4c/0x80 device_release_driver_internal+0x1cc/0x224 device_driver_detach+0x18/0x24 unbind_store+0xb4/0xc0 Check HCI_UART_PROTO_READY, which hci_uart_close() clears in the same place it closes the port, before writing to it. The regulator disable is left unconditional so the controller is still powered down. The dangling serport->tty that turns this into a use-after-free is addressed in a separate patch. Fixes: fa9ad876b8e0 ("Bluetooth: hci_qca: Add support for Qualcomm Bluetooth chip wcn3990") Signed-off-by: Ibrahim Abdelkader Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/bluetooth/hci_qca.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index 9b2cf6429c855..f595a0f0aee2a 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -2226,8 +2226,8 @@ static void qca_power_off(struct hci_uart *hu) bool sw_ctrl_state; struct qca_power *power; - /* From this point we go into power off state. But serial port is - * still open, stop queueing the IBS data and flush all the buffered + /* From this point we go into power off state. But serial port may + * still be open, stop queueing the IBS data and flush all the buffered * data in skb's. */ spin_lock_irqsave(&qca->hci_ibs_lock, flags); @@ -2249,8 +2249,14 @@ static void qca_power_off(struct hci_uart *hu) case QCA_WCN3990: case QCA_WCN3991: case QCA_WCN3998: - host_set_baudrate(hu, 2400); - qca_send_power_pulse(hu, false); + /* Both of these write to the serial port which may have + * already been closed by hci_uart_close(), which closes + * the port if HCI_QUIRK_NON_PERSISTENT_SETUP is set. + */ + if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) { + host_set_baudrate(hu, 2400); + qca_send_power_pulse(hu, false); + } break; default: break; From 4d24b54d8662f78da607a7fa86cdbb97934def30 Mon Sep 17 00:00:00 2001 From: Ibrahim Abdelkader Date: Wed, 19 Aug 2026 14:57:48 +0200 Subject: [PATCH 12/14] serdev: ttyport: Clear serport->tty after freeing Both error paths in ttyport_open(), and ttyport_close(), release the tty with tty_release_struct() and leave serport->tty pointing at freed memory. The serdev core itself never dereferences it afterwards. However, a buggy driver could easily trigger a use-after-free by calling a ttyport operation on a port that is not open, or by calling close() twice. While those drivers should be fixed, clearing the pointer, makes them fail deterministically instead of touching freed memory. Note that a driver which currently double closes gets away with it only by chance, depending on whether the freed tty has been reused. After this change such a driver oopses immediately instead, which is the intended outcome, but it may surface latent bugs elsewhere. Signed-off-by: Ibrahim Abdelkader Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Reviewed-by: Markus Probst --- drivers/tty/serdev/serdev-ttyport.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/tty/serdev/serdev-ttyport.c b/drivers/tty/serdev/serdev-ttyport.c index bab1b143b8a68..b6638f9f40d2d 100644 --- a/drivers/tty/serdev/serdev-ttyport.c +++ b/drivers/tty/serdev/serdev-ttyport.c @@ -137,6 +137,7 @@ static int ttyport_open(struct serdev_controller *ctrl) err_unlock: tty_unlock(tty); tty_release_struct(tty, serport->tty_idx); + serport->tty = NULL; return ret; } @@ -154,6 +155,7 @@ static void ttyport_close(struct serdev_controller *ctrl) tty_unlock(tty); tty_release_struct(tty, serport->tty_idx); + serport->tty = NULL; } static unsigned int ttyport_set_baudrate(struct serdev_controller *ctrl, unsigned int speed) From 032f98ceddf4d17989c5119228a21e50d29f4804 Mon Sep 17 00:00:00 2001 From: Ibrahim Abdelkader Date: Mon, 17 Aug 2026 22:50:16 +0200 Subject: [PATCH 13/14] Bluetooth: hci_core: Return -ENOMEM when the sent_cmd clone fails hci_send_cmd_sync() returns -EINVAL when skb_clone() fails for sent_cmd, which describes an invalid argument rather than an allocation failure. Return -ENOMEM instead. The only caller, hci_cmd_work(), tests the result for zero, so there is no functional change. Signed-off-by: Ibrahim Abdelkader Signed-off-by: Hans de Goede --- net/bluetooth/hci_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 28d7929dc5937..b47b9d9400381 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -4074,7 +4074,7 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) if (!hdev->sent_cmd) { skb_queue_head(&hdev->cmd_q, skb); queue_work(hdev->workqueue, &hdev->cmd_work); - return -EINVAL; + return -ENOMEM; } if (hci_skb_opcode(skb) != HCI_OP_NOP) { From a824f97ff9ad0d8b2b20add12ba2c3724b12b068 Mon Sep 17 00:00:00 2001 From: Ibrahim Abdelkader Date: Tue, 11 Aug 2026 10:37:29 +0200 Subject: [PATCH 14/14] Bluetooth: hci_sync: Clear HCI_CMD_PENDING when dropping the last request A synchronous HCI command that never receives a response leaves HCI_CMD_PENDING set: hci_req_cmd_complete() is the only place that clears it, and it only runs when a response matching the last command sent arrives. hci_send_cmd_sync() populates hdev->req_skb only when the flag transitions from clear to set, while hci_dev_open_sync() and hci_dev_close_sync() drop req_skb without clearing the flag. After a timeout followed by either, the two disagree: the flag claims a request is outstanding while req_skb is NULL. Subsequent synchronous commands are then sent with no req_skb, so hci_event_packet() has nothing to match an arriving event against, and the caller times out even though the controller answered. Commands answered by Command Complete recover on their own, since hci_req_cmd_complete() clears the flag as a side effect. Drivers using __hci_cmd_sync_ev() with a custom event do not, because a vendor event never reaches that path. On a WCN3988 (hci_qca over UART) this makes a controller firmware hang unrecoverable: the driver injects a hardware error and re-runs qca_setup(), qca_read_soc_version() waits for HCI_EV_VENDOR, the reply arrives within 4 ms and is discarded, and every retry fails the same way. The adapter is left down until the driver is unbound and rebound, or power is removed. Clear the flag wherever the last request is dropped, restoring the invariant that req_skb is non-NULL exactly when HCI_CMD_PENDING is set. Verified on hardware by forcing a command timeout: without this change setup fails on every attempt, with it setup succeeds on the first. Fixes: 2615fd9a7c25 ("Bluetooth: hci_sync: Fix overwriting request callback") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Abdelkader Signed-off-by: Hans de Goede Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 89075a36b27a7..88f86c241690a 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -5283,6 +5283,7 @@ int hci_dev_open_sync(struct hci_dev *hdev) if (hdev->req_skb) { kfree_skb(hdev->req_skb); hdev->req_skb = NULL; + hci_dev_clear_flag(hdev, HCI_CMD_PENDING); } clear_bit(HCI_RUNNING, &hdev->flags); @@ -5467,6 +5468,7 @@ int hci_dev_close_sync(struct hci_dev *hdev) if (hdev->req_skb) { kfree_skb(hdev->req_skb); hdev->req_skb = NULL; + hci_dev_clear_flag(hdev, HCI_CMD_PENDING); } clear_bit(HCI_RUNNING, &hdev->flags);