From 420e2f8c69d110d34da2464dd53a24f546458e51 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Sat, 15 Aug 2026 10:55:10 +0800 Subject: [PATCH 001/924] ASoC: tas2781: add support for TAS2573 DSP functionality This patch extends the existing tas2781 codec driver to add full support for the TAS2573 integrated DSP feature. Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20260815025510.1815-1-shenghao-ding@ti.com Signed-off-by: Mark Brown --- include/sound/tas2781-dsp.h | 4 ++++ sound/soc/codecs/tas2781-fmwlib.c | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/sound/tas2781-dsp.h b/include/sound/tas2781-dsp.h index e845687decf9..de087f4ca13f 100644 --- a/include/sound/tas2781-dsp.h +++ b/include/sound/tas2781-dsp.h @@ -56,6 +56,10 @@ enum tasdevice_dsp_dev_idx { TASDEVICE_DSP_TAS_2781_QUAD, TASDEVICE_DSP_TAS_5825_MONO, TASDEVICE_DSP_TAS_5825_DUAL, + TASDEVICE_DSP_TAS_2573_MONO, + TASDEVICE_DSP_TAS_2573_DUAL, + TASDEVICE_DSP_TAS_2573_21, + TASDEVICE_DSP_TAS_2573_QUAD, TASDEVICE_DSP_TAS_MAX_DEVICE }; diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c index 2b9efccf1a0b..dc1c552206f8 100644 --- a/sound/soc/codecs/tas2781-fmwlib.c +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -110,7 +110,7 @@ struct tas2781_cali_specific { }; static const char deviceNumber[TASDEVICE_DSP_TAS_MAX_DEVICE] = { - 1, 2, 1, 2, 1, 1, 0, 2, 4, 3, 1, 2, 3, 4, 1, 2 + 1, 2, 1, 2, 1, 1, 0, 2, 4, 3, 1, 2, 3, 4, 1, 2, 1, 2, 3, 4, }; /* fixed m68k compiling issue: mapping table can save code field */ @@ -1959,6 +1959,8 @@ static void dspbin_type_check(struct tasdevice_priv *tas_priv, else tas_priv->dspbin_typ = TASDEV_ALPHA; } + if (tas_priv->chip_id == TAS2573) + return; if ((tas_priv->dspbin_typ != TASDEV_BASIC) && (ppcver < PPC3_VERSION_TAS5825_BASE)) tas_priv->fw_parse_fct_param_address = From a3521b02ff0e4470fd7fbdc72b3a3f6830bc1007 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 14 Aug 2026 13:45:36 -0700 Subject: [PATCH 002/924] ASoC: uniphier: aio-dma: pass chip to irq handler The irq handler takes the platform device and calls platform_get_drvdata() to recover the chip. Pass the chip directly as the devm_request_irq() argument instead, dropping the platform_get_drvdata() call. Assisted-by: opencode:deepseek-v4-flash-free Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260814204536.38010-1-rosenp@gmail.com Signed-off-by: Mark Brown --- sound/soc/uniphier/aio-dma.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sound/soc/uniphier/aio-dma.c b/sound/soc/uniphier/aio-dma.c index c01eae55d4fc..389a45319dfc 100644 --- a/sound/soc/uniphier/aio-dma.c +++ b/sound/soc/uniphier/aio-dma.c @@ -66,8 +66,7 @@ static void aiodma_compr_irq(struct uniphier_aio_sub *sub) static irqreturn_t aiodma_irq(int irq, void *p) { - struct platform_device *pdev = p; - struct uniphier_aio_chip *chip = platform_get_drvdata(pdev); + struct uniphier_aio_chip *chip = p; irqreturn_t ret = IRQ_NONE; int i, j; @@ -263,7 +262,7 @@ int uniphier_aiodma_soc_register_platform(struct platform_device *pdev) return irq; ret = devm_request_irq(dev, irq, aiodma_irq, - IRQF_SHARED, dev_name(dev), pdev); + IRQF_SHARED, dev_name(dev), chip); if (ret) return ret; From bfaefd8aef4adeff4dbab86313bee788df5f2b19 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 12 Aug 2026 15:51:28 +0700 Subject: [PATCH 003/924] ASoC: samsung: aries_wm8994: Preserve gpiod_to_irq() error codes gpiod_to_irq() can currently return several error codes, including -ENXIO, -ENODEV, -EPROBE_DEFER, and -EINVAL. Return the original error code instead of overwriting it with -EINVAL, so that the actual failure reason is preserved. Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260812085133.29658-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/samsung/aries_wm8994.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/samsung/aries_wm8994.c b/sound/soc/samsung/aries_wm8994.c index d8659973540f..482e50104cd3 100644 --- a/sound/soc/samsung/aries_wm8994.c +++ b/sound/soc/samsung/aries_wm8994.c @@ -374,7 +374,7 @@ static int aries_late_probe(struct snd_soc_card *card) irq = gpiod_to_irq(priv->gpio_headset_detect); if (irq < 0) { dev_err(card->dev, "Failed to map headset detect gpio to irq"); - return -EINVAL; + return irq; } ret = devm_request_threaded_irq(card->dev, irq, NULL, From 445c3c59affef477ef3d8ec35277a71903c29e0c Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 12 Aug 2026 15:51:29 +0700 Subject: [PATCH 004/924] ASoC: samsung: aries_wm8994: Drop redundant probe error messages The functions called here don't log the error themselves, but the error is already reported deeper in the call chain, so the dev_err() calls are redundant and can be removed. Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260812085133.29658-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/samsung/aries_wm8994.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/sound/soc/samsung/aries_wm8994.c b/sound/soc/samsung/aries_wm8994.c index 482e50104cd3..1a5b53f47571 100644 --- a/sound/soc/samsung/aries_wm8994.c +++ b/sound/soc/samsung/aries_wm8994.c @@ -381,10 +381,8 @@ static int aries_late_probe(struct snd_soc_card *card) headset_det_irq_thread, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "headset_detect", priv); - if (ret) { - dev_err(card->dev, "Failed to request headset detect irq"); + if (ret) return ret; - } headset_button_gpio[0].data = priv; headset_button_gpio[0].desc = priv->gpio_headset_key; @@ -623,10 +621,8 @@ static int aries_audio_probe(struct platform_device *pdev) if (ret < 0) { /* Backwards compatible way */ ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing"); - if (ret < 0) { - dev_err(dev, "Audio routing invalid/unspecified\n"); + if (ret < 0) return ret; - } } aries_dai[1].dai_fmt = priv->variant->modem_dai_fmt; @@ -671,10 +667,8 @@ static int aries_audio_probe(struct platform_device *pdev) ret = devm_snd_soc_register_component(dev, &aries_component, aries_ext_dai, ARRAY_SIZE(aries_ext_dai)); - if (ret < 0) { - dev_err(dev, "Failed to register component: %d\n", ret); + if (ret < 0) goto out; - } ret = devm_snd_soc_register_card(dev, card); if (ret) From 87874c260c1a0a14dc877ab2432397fc95896e48 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 12 Aug 2026 15:51:30 +0700 Subject: [PATCH 005/924] ASoC: samsung: pcm: Use dev_err_probe() for error handling Replace dev_err() with dev_err_probe() to prevent log spam when probe returns -EPROBE_DEFER. Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260812085133.29658-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/samsung/pcm.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c index 309f024bf2a4..7e7284125c3e 100644 --- a/sound/soc/samsung/pcm.c +++ b/sound/soc/samsung/pcm.c @@ -499,10 +499,9 @@ static int s3c_pcm_dev_probe(struct platform_device *pdev) return PTR_ERR(pcm->regs); pcm->cclk = devm_clk_get(&pdev->dev, "audio-bus"); - if (IS_ERR(pcm->cclk)) { - dev_err(&pdev->dev, "failed to get audio-bus clock\n"); - return PTR_ERR(pcm->cclk); - } + if (IS_ERR(pcm->cclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pcm->cclk), + "failed to get audio-bus clock\n"); ret = clk_prepare_enable(pcm->cclk); if (ret) return ret; @@ -512,8 +511,8 @@ static int s3c_pcm_dev_probe(struct platform_device *pdev) pcm->pclk = devm_clk_get(&pdev->dev, "pcm"); if (IS_ERR(pcm->pclk)) { - dev_err(&pdev->dev, "failed to get pcm clock\n"); - ret = PTR_ERR(pcm->pclk); + ret = dev_err_probe(&pdev->dev, PTR_ERR(pcm->pclk), + "failed to get pcm clock\n"); goto err_dis_cclk; } ret = clk_prepare_enable(pcm->pclk); From 9615017689947252d20f5a9c52447cd88febedc0 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 12 Aug 2026 15:51:31 +0700 Subject: [PATCH 006/924] ASoC: samsung: pcm: Drop redundant probe error messages The functions called here don't log the error themselves, but the error is already reported deeper in the call chain, so the dev_err() calls are redundant and can be removed. Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260812085133.29658-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/samsung/pcm.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c index 7e7284125c3e..8430686760d7 100644 --- a/sound/soc/samsung/pcm.c +++ b/sound/soc/samsung/pcm.c @@ -534,19 +534,15 @@ static int s3c_pcm_dev_probe(struct platform_device *pdev) ret = samsung_asoc_dma_platform_register(&pdev->dev, filter, NULL, NULL, NULL); - if (ret) { - dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret); + if (ret) goto err_dis_pclk; - } pm_runtime_enable(&pdev->dev); ret = devm_snd_soc_register_component(&pdev->dev, &s3c_pcm_component, &s3c_pcm_dai[pdev->id], 1); - if (ret != 0) { - dev_err(&pdev->dev, "failed to get register DAI: %d\n", ret); + if (ret != 0) goto err_dis_pm; - } return 0; From 84234ee45fe2d3018c2d0f14c09e5740f6d64b96 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 12 Aug 2026 15:51:32 +0700 Subject: [PATCH 007/924] ASoC: samsung: spdif: Drop redundant probe error messages The functions called here don't log the error themselves, but the error is already reported deeper in the call chain, so the dev_err() calls are redundant and can be removed. Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260812085133.29658-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/samsung/spdif.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c index 53eaabaf8956..719083e8e89d 100644 --- a/sound/soc/samsung/spdif.c +++ b/sound/soc/samsung/spdif.c @@ -415,19 +415,15 @@ static int spdif_probe(struct platform_device *pdev) ret = samsung_asoc_dma_platform_register(&pdev->dev, filter, NULL, NULL, NULL); - if (ret) { - dev_err(&pdev->dev, "failed to register DMA: %d\n", ret); + if (ret) goto err2; - } dev_set_drvdata(&pdev->dev, spdif); ret = devm_snd_soc_register_component(&pdev->dev, &samsung_spdif_component, &samsung_spdif_dai, 1); - if (ret != 0) { - dev_err(&pdev->dev, "fail to register dai\n"); + if (ret != 0) goto err2; - } return 0; err2: From 29b8bc168a25fc823c0ba72a19f9ede9b2482495 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 12 Aug 2026 15:51:33 +0700 Subject: [PATCH 008/924] ASoC: samsung: tm2_wm5110: Drop redundant probe error messages The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() calls are redundant and can be removed. Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260812085133.29658-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/samsung/tm2_wm5110.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/sound/soc/samsung/tm2_wm5110.c b/sound/soc/samsung/tm2_wm5110.c index f1f59e059f5d..f4912826dfd4 100644 --- a/sound/soc/samsung/tm2_wm5110.c +++ b/sound/soc/samsung/tm2_wm5110.c @@ -518,19 +518,15 @@ static int tm2_probe(struct platform_device *pdev) } ret = snd_soc_of_parse_card_name(card, "model"); - if (ret < 0) { - dev_err(dev, "Card name is not specified\n"); + if (ret < 0) return ret; - } ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); if (ret < 0) { /* Backwards compatible way */ ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing"); - if (ret < 0) { - dev_err(dev, "Audio routing is not specified or invalid\n"); + if (ret < 0) return ret; - } } card->aux_dev[0].dlc.of_node = of_parse_phandle(dev->of_node, @@ -609,10 +605,8 @@ static int tm2_probe(struct platform_device *pdev) ret = devm_snd_soc_register_component(dev, &tm2_component, tm2_ext_dai, ARRAY_SIZE(tm2_ext_dai)); - if (ret < 0) { - dev_err(dev, "Failed to register component: %d\n", ret); + if (ret < 0) goto dai_node_put; - } ret = devm_snd_soc_register_card(dev, card); if (ret < 0) { From b0ac345bad82ddeeaa02daf4dafd9c930ffea86b Mon Sep 17 00:00:00 2001 From: Eduard Bostina Date: Tue, 11 Aug 2026 09:17:03 +0000 Subject: [PATCH 009/924] ASoC: dt-bindings: Convert TI DA830 EVM audio to DT schema Convert the Texas Instruments DA830 EVM audio complex bindings to DT schema. Signed-off-by: Eduard Bostina Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260811091703.685487-1-egbostina@gmail.com Signed-off-by: Mark Brown --- .../bindings/sound/davinci-evm-audio.txt | 49 ----------- .../bindings/sound/ti,da830-evm-audio.yaml | 83 +++++++++++++++++++ 2 files changed, 83 insertions(+), 49 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/davinci-evm-audio.txt create mode 100644 Documentation/devicetree/bindings/sound/ti,da830-evm-audio.yaml diff --git a/Documentation/devicetree/bindings/sound/davinci-evm-audio.txt b/Documentation/devicetree/bindings/sound/davinci-evm-audio.txt deleted file mode 100644 index 963e100514c2..000000000000 --- a/Documentation/devicetree/bindings/sound/davinci-evm-audio.txt +++ /dev/null @@ -1,49 +0,0 @@ -* Texas Instruments SoC audio setups with TLV320AIC3X Codec - -Required properties: -- compatible : "ti,da830-evm-audio" : forDM365/DA8xx/OMAPL1x/AM33xx -- ti,model : The user-visible name of this sound complex. -- ti,audio-codec : The phandle of the TLV320AIC3x audio codec -- ti,mcasp-controller : The phandle of the McASP controller -- ti,audio-routing : A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the codec's pins, and the jacks on the board: - -Optional properties: -- ti,codec-clock-rate : The Codec Clock rate (in Hz) applied to the Codec. -- clocks : Reference to the master clock -- clock-names : The clock should be named "mclk" -- Either codec-clock-rate or the codec-clock reference has to be defined. If - the both are defined the driver attempts to set referenced clock to the - defined rate and takes the rate from the clock reference. - - Board connectors: - - * Headphone Jack - * Line Out - * Mic Jack - * Line In - - -Example: - -sound { - compatible = "ti,da830-evm-audio"; - ti,model = "DA830 EVM"; - ti,audio-codec = <&tlv320aic3x>; - ti,mcasp-controller = <&mcasp1>; - ti,codec-clock-rate = <12000000>; - ti,audio-routing = - "Headphone Jack", "HPLOUT", - "Headphone Jack", "HPROUT", - "Line Out", "LLOUT", - "Line Out", "RLOUT", - "MIC3L", "Mic Bias 2V", - "MIC3R", "Mic Bias 2V", - "Mic Bias 2V", "Mic Jack", - "LINE1L", "Line In", - "LINE2L", "Line In", - "LINE1R", "Line In", - "LINE2R", "Line In"; -}; diff --git a/Documentation/devicetree/bindings/sound/ti,da830-evm-audio.yaml b/Documentation/devicetree/bindings/sound/ti,da830-evm-audio.yaml new file mode 100644 index 000000000000..b4a1cf67582b --- /dev/null +++ b/Documentation/devicetree/bindings/sound/ti,da830-evm-audio.yaml @@ -0,0 +1,83 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/ti,da830-evm-audio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments SoC audio setups with TLV320AIC3X Codec + +maintainers: + - Eduard Bostina + +properties: + compatible: + const: ti,da830-evm-audio + + ti,model: + $ref: /schemas/types.yaml#/definitions/string + description: The user-visible name of this sound complex. + + ti,audio-codec: + $ref: /schemas/types.yaml#/definitions/phandle + description: The phandle of the TLV320AIC3x audio codec + + ti,mcasp-controller: + $ref: /schemas/types.yaml#/definitions/phandle + description: The phandle of the McASP controller + + ti,audio-routing: + $ref: /schemas/types.yaml#/definitions/non-unique-string-array + description: + A list of the connections between audio components. Each entry is a + pair of strings, the first being the connection's sink, the second + being the connection's source. Valid names for sources and sinks + are the codec's pins, and the jacks on the board (Headphone Jack, + Line Out, Mic Jack, Line In). + + ti,codec-clock-rate: + $ref: /schemas/types.yaml#/definitions/uint32 + description: The Codec Clock rate (in Hz) applied to the Codec. + + clocks: + maxItems: 1 + description: Reference to the master clock + + clock-names: + const: mclk + +required: + - compatible + - ti,model + - ti,audio-codec + - ti,mcasp-controller + - ti,audio-routing + +anyOf: + - required: + - ti,codec-clock-rate + - required: + - clocks + +additionalProperties: false + +examples: + - | + sound { + compatible = "ti,da830-evm-audio"; + ti,model = "DA830 EVM"; + ti,audio-codec = <&tlv320aic3x>; + ti,mcasp-controller = <&mcasp1>; + ti,codec-clock-rate = <12000000>; + ti,audio-routing = + "Headphone Jack", "HPLOUT", + "Headphone Jack", "HPROUT", + "Line Out", "LLOUT", + "Line Out", "RLOUT", + "MIC3L", "Mic Bias 2V", + "MIC3R", "Mic Bias 2V", + "Mic Bias 2V", "Mic Jack", + "LINE1L", "Line In", + "LINE2L", "Line In", + "LINE1R", "Line In", + "LINE2R", "Line In"; + }; From 7635a23cd6e7417a981c398cc5196dfac4afec6c Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 18 Aug 2026 17:43:42 +0700 Subject: [PATCH 010/924] ASoC: mediatek: mt8173: Fix clock error handling The error handling paths in mt8173_afe_runtime_resume() use incorrect labels, specifically err_i2s1_m and err_i2s2_m. When clk_prepare_enable() fails, the code jumps to a label that calls clk_disable_unprepare() on the clock that failed to enable. Instead, it should call clk_disable_unprepare() on the clock that was successfully enabled before the failure. Fix the labels and clk_disable_unprepare() calls to ensure the error cleanup is handled correctly. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260818104344.37824-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8173/mt8173-afe-pcm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c index 69cadc91c97f..b4c6ef6f0845 100644 --- a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -997,10 +997,10 @@ static int mt8173_afe_runtime_resume(struct device *dev) goto err_bck0; ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_I2S1_M]); if (ret) - goto err_i2s1_m; + goto err_bck1; ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_I2S2_M]); if (ret) - goto err_i2s2_m; + goto err_i2s1_m; /* enable AFE clk */ regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, AUD_TCON0_PDN_AFE, 0); @@ -1018,8 +1018,8 @@ static int mt8173_afe_runtime_resume(struct device *dev) err_i2s1_m: clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S1_M]); -err_i2s2_m: - clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S2_M]); +err_bck1: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK1]); err_bck0: clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK0]); err_top_aud: From 7bcdd38eb5021f957341301898391d4dfd7e6084 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 18 Aug 2026 17:43:43 +0700 Subject: [PATCH 011/924] ASoC: mediatek: mt8173: Use dev_err_probe() for error handling Replace dev_err() with dev_err_probe() to prevent log spam when probe returns -EPROBE_DEFER. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260818104344.37824-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8173/mt8173-afe-pcm.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c index b4c6ef6f0845..b7c4b24a9cad 100644 --- a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -1038,11 +1038,9 @@ static int mt8173_afe_init_audio_clk(struct mtk_base_afe *afe) for (i = 0; i < ARRAY_SIZE(aud_clks); i++) { afe_priv->clocks[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clocks[i])) { - dev_err(afe->dev, "%s devm_clk_get %s fail\n", - __func__, aud_clks[i]); - return PTR_ERR(afe_priv->clocks[i]); - } + if (IS_ERR(afe_priv->clocks[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clocks[i]), + "failed to get clock %s\n", aud_clks[i]); } clk_set_rate(afe_priv->clocks[MT8173_CLK_BCK0], 22579200); /* 22M */ clk_set_rate(afe_priv->clocks[MT8173_CLK_BCK1], 24576000); /* 24M */ From 15b975f173f18ad5f5097d94c005e6fe57deb040 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 18 Aug 2026 17:43:44 +0700 Subject: [PATCH 012/924] ASoC: mediatek: mt8173: Drop redundant probe error messages The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() calls are redundant and can be removed. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260818104344.37824-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8173/mt8173-afe-pcm.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c index b7c4b24a9cad..c2421f17375b 100644 --- a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -1091,10 +1091,8 @@ static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt8173_afe_init_audio_clk(afe); - if (ret) { - dev_err(dev, "mt8173_afe_init_audio_clk fail\n"); + if (ret) return ret; - } /* memif % irq initialize*/ afe->memif_size = MT8173_AFE_MEMIF_NUM; @@ -1154,10 +1152,8 @@ static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt8173_afe_irq_handler, 0, "Afe_ISR_Handle", (void *)afe); - if (ret) { - dev_err(dev, "could not request_irq\n"); + if (ret) goto err_cleanup_components; - } dev_info(dev, "MT8173 AFE driver initialized.\n"); return 0; From 040a7beba8bf9457db82dd84a021eb54dbfcb9a7 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Thu, 20 Aug 2026 11:14:53 +0900 Subject: [PATCH 013/924] ASoC: fsl_mqs: Add DAPM output widgets for MQS_L/MQS_R pins Provide DAPM output widgets for the MQS_L/MQS_R pins and route them from the "Playback" stream. This allows external amplifiers connected to the MQS outputs to be properly linked into the DAPM graph, so their power state can follow the playback stream. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260820021454.2229094-1-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_mqs.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sound/soc/fsl/fsl_mqs.c b/sound/soc/fsl/fsl_mqs.c index 901f840df904..5b00488253bb 100644 --- a/sound/soc/fsl/fsl_mqs.c +++ b/sound/soc/fsl/fsl_mqs.c @@ -183,7 +183,21 @@ static void fsl_mqs_shutdown(struct snd_pcm_substream *substream, mqs_priv->soc->en_mask, 0); } +static const struct snd_soc_dapm_widget fsl_mqs_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("MQS_L"), + SND_SOC_DAPM_OUTPUT("MQS_R"), +}; + +static const struct snd_soc_dapm_route fsl_mqs_dapm_routes[] = { + { "MQS_L", NULL, "Playback" }, + { "MQS_R", NULL, "Playback" }, +}; + static const struct snd_soc_component_driver soc_codec_fsl_mqs = { + .dapm_widgets = fsl_mqs_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(fsl_mqs_dapm_widgets), + .dapm_routes = fsl_mqs_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(fsl_mqs_dapm_routes), .idle_bias_on = 1, }; From e1c41b5395a1f3b04681ffa38fbc72ef14b4d8b7 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 17 Aug 2026 17:49:27 +0700 Subject: [PATCH 014/924] ASoC: mediatek: mt2701: Use devm_clk_get_optional() for audio_mrgif_pd The audio_mrgif_pd clock is optional as some platforms may support the BT path while others do not. Use devm_clk_get_optional() to reflect this. Propagate other errors to the caller. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260817104929.63152-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c index d217f9320ad2..18f0f27bd8c0 100644 --- a/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c +++ b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c @@ -89,13 +89,9 @@ int mt2701_init_clock(struct mtk_base_afe *afe) } /* Some platforms may support BT path */ - afe_priv->mrgif_ck = devm_clk_get(afe->dev, "audio_mrgif_pd"); - if (IS_ERR(afe_priv->mrgif_ck)) { - if (PTR_ERR(afe_priv->mrgif_ck) == -EPROBE_DEFER) - return -EPROBE_DEFER; - - afe_priv->mrgif_ck = NULL; - } + afe_priv->mrgif_ck = devm_clk_get_optional(afe->dev, "audio_mrgif_pd"); + if (IS_ERR(afe_priv->mrgif_ck)) + return PTR_ERR(afe_priv->mrgif_ck); /* * Optional HDMI audio clocks. Platforms that do not wire up the From d468c1c89e7d777d1fbf35618f9d8b98d88ffd6d Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 17 Aug 2026 17:49:28 +0700 Subject: [PATCH 015/924] ASoC: mediatek: mt2701: Use dev_err_probe() for error handling Replace dev_err() with dev_err_probe() to prevent log spam when probe returns -EPROBE_DEFER. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260817104929.63152-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- .../mediatek/mt2701/mt2701-afe-clock-ctrl.c | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c index 18f0f27bd8c0..1cf66ca44a33 100644 --- a/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c +++ b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c @@ -30,10 +30,9 @@ int mt2701_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT2701_BASE_CLK_NUM; i++) { afe_priv->base_ck[i] = devm_clk_get(afe->dev, base_clks[i]); - if (IS_ERR(afe_priv->base_ck[i])) { - dev_err(afe->dev, "failed to get %s\n", base_clks[i]); - return PTR_ERR(afe_priv->base_ck[i]); - } + if (IS_ERR(afe_priv->base_ck[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->base_ck[i]), + "failed to get %s\n", base_clks[i]); } i2s_num = min(afe_priv->soc->i2s_num, MT2701_BASE_CLK_NUM); @@ -45,47 +44,43 @@ int mt2701_init_clock(struct mtk_base_afe *afe) snprintf(name, sizeof(name), "i2s%d_src_sel", i); i2s_path->sel_ck = devm_clk_get(afe->dev, name); - if (IS_ERR(i2s_path->sel_ck)) { - dev_err(afe->dev, "failed to get %s\n", name); - return PTR_ERR(i2s_path->sel_ck); - } + if (IS_ERR(i2s_path->sel_ck)) + return dev_err_probe(afe->dev, PTR_ERR(i2s_path->sel_ck), + "failed to get %s\n", name); snprintf(name, sizeof(name), "i2s%d_src_div", i); i2s_path->div_ck = devm_clk_get(afe->dev, name); - if (IS_ERR(i2s_path->div_ck)) { - dev_err(afe->dev, "failed to get %s\n", name); - return PTR_ERR(i2s_path->div_ck); - } + if (IS_ERR(i2s_path->div_ck)) + return dev_err_probe(afe->dev, PTR_ERR(i2s_path->div_ck), + "failed to get %s\n", name); snprintf(name, sizeof(name), "i2s%d_mclk_en", i); i2s_path->mclk_ck = devm_clk_get(afe->dev, name); - if (IS_ERR(i2s_path->mclk_ck)) { - dev_err(afe->dev, "failed to get %s\n", name); - return PTR_ERR(i2s_path->mclk_ck); - } + if (IS_ERR(i2s_path->mclk_ck)) + return dev_err_probe(afe->dev, PTR_ERR(i2s_path->mclk_ck), + "failed to get %s\n", name); snprintf(name, sizeof(name), "i2so%d_hop_ck", i); i2s_ck = devm_clk_get(afe->dev, name); - if (IS_ERR(i2s_ck)) { - dev_err(afe->dev, "failed to get %s\n", name); - return PTR_ERR(i2s_ck); - } + if (IS_ERR(i2s_ck)) + return dev_err_probe(afe->dev, PTR_ERR(i2s_ck), + "failed to get %s\n", name); + i2s_path->hop_ck[SNDRV_PCM_STREAM_PLAYBACK] = i2s_ck; snprintf(name, sizeof(name), "i2si%d_hop_ck", i); i2s_ck = devm_clk_get(afe->dev, name); - if (IS_ERR(i2s_ck)) { - dev_err(afe->dev, "failed to get %s\n", name); - return PTR_ERR(i2s_ck); - } + if (IS_ERR(i2s_ck)) + return dev_err_probe(afe->dev, PTR_ERR(i2s_ck), + "failed to get %s\n", name); + i2s_path->hop_ck[SNDRV_PCM_STREAM_CAPTURE] = i2s_ck; snprintf(name, sizeof(name), "asrc%d_out_ck", i); i2s_path->asrco_ck = devm_clk_get(afe->dev, name); - if (IS_ERR(i2s_path->asrco_ck)) { - dev_err(afe->dev, "failed to get %s\n", name); - return PTR_ERR(i2s_path->asrco_ck); - } + if (IS_ERR(i2s_path->asrco_ck)) + return dev_err_probe(afe->dev, PTR_ERR(i2s_path->asrco_ck), + "failed to get %s\n", name); } /* Some platforms may support BT path */ From fd97db580cea937d447dce7f5ac7f3d185fe0038 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 17 Aug 2026 17:49:29 +0700 Subject: [PATCH 016/924] ASoC: mediatek: mt2701: Drop redundant probe error messages The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() calls are redundant and can be removed. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260817104929.63152-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt2701/mt2701-afe-pcm.c | 4 +--- sound/soc/mediatek/mt2701/mt2701-cs42448.c | 4 +--- sound/soc/mediatek/mt2701/mt2701-wm8960.c | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c index d56b498e8c0c..3c5a76591ea7 100644 --- a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c +++ b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c @@ -1622,10 +1622,8 @@ static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt2701_asys_isr, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); - if (ret) { - dev_err(dev, "could not request_irq for asys-isr\n"); + if (ret) return ret; - } afe->regmap = syscon_node_to_regmap(dev->parent->of_node); if (IS_ERR(afe->regmap)) { diff --git a/sound/soc/mediatek/mt2701/mt2701-cs42448.c b/sound/soc/mediatek/mt2701/mt2701-cs42448.c index 778a9dccfcaa..af833a8daa2a 100644 --- a/sound/soc/mediatek/mt2701/mt2701-cs42448.c +++ b/sound/soc/mediatek/mt2701/mt2701-cs42448.c @@ -366,10 +366,8 @@ static int mt2701_cs42448_machine_probe(struct platform_device *pdev) = codec_node_bt_mrg; ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); - if (ret) { - dev_err(dev, "failed to parse audio-routing: %d\n", ret); + if (ret) return ret; - } priv->i2s1_in_mux_sel_1 = devm_gpiod_get_optional(dev, "i2s1-in-sel-gpio1", GPIOD_OUT_LOW); diff --git a/sound/soc/mediatek/mt2701/mt2701-wm8960.c b/sound/soc/mediatek/mt2701/mt2701-wm8960.c index 84b3d6cd77a5..25ea3e274aa8 100644 --- a/sound/soc/mediatek/mt2701/mt2701-wm8960.c +++ b/sound/soc/mediatek/mt2701/mt2701-wm8960.c @@ -137,10 +137,8 @@ static int mt2701_wm8960_machine_probe(struct platform_device *pdev) } ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); - if (ret) { - dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret); + if (ret) goto put_codec_node; - } ret = devm_snd_soc_register_card(&pdev->dev, card); if (ret) From 384a0f7b5adb86260aaef91963fbc25ef2fa72af Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Tue, 25 Aug 2026 10:16:43 +0800 Subject: [PATCH 017/924] ASoC: dt-bindings: nuvoton,nau8360: Add NAU83G60 Add device tree bindings documentation for the Nuvoton NAU83G60 audio amplifier. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Neo Chang Link: https://patch.msgid.link/20260825021644.1203076-2-YLCHANG2@nuvoton.com Signed-off-by: Mark Brown --- .../bindings/sound/nuvoton,nau8360.yaml | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/nuvoton,nau8360.yaml diff --git a/Documentation/devicetree/bindings/sound/nuvoton,nau8360.yaml b/Documentation/devicetree/bindings/sound/nuvoton,nau8360.yaml new file mode 100644 index 000000000000..3edab944a4e9 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nuvoton,nau8360.yaml @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nuvoton,nau8360.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton NAU83G60 Stereo Class-D Amplifier with DSP + +description: + Stereo Class-D Amplifier with DSP and I/V-sense. + This device supports I2C. + +maintainers: + - Neo Chang + +properties: + compatible: + enum: + - nuvoton,nau8360 + + reg: + description: The I2C address is determined by the hardware pins. + enum: [0x1a, 0x1b, 0x4a, 0x4b] + + "#sound-dai-cells": + const: 0 + + clocks: + maxItems: 1 + + clock-names: + const: mclk + + firmware-name: + description: + List of DSP core firmware filenames. Both Left and Right DSP firmware + must be provided and loaded. In PBTL mode, the Left DSP must use the + same binary file as the Right DSP. + items: + - description: Left DSP core firmware filename. + - description: Right DSP core firmware filename. + + nuvoton,pbtl-enable: + type: boolean + description: NAU83G60 supports PBTL mode for mono output. + + nuvoton,dac-cur-enable: + type: boolean + description: + Adjust DAC output current to match speaker impedance and prevent + hardware damage. +3.2dB when present, 0dB by default. + + nuvoton,dsp-rx-slot-mapping: + description: + Configures the TDM slot routing for DSP RX functions. + Each integer assigns a specific DSP internal function to a TDM slot. + The indices of the array correspond to the following hardware functions + (0) DACL + (1) DACR + (2) ANCL + (3) ANCR + Assigned slots must be unique, except 255 which disables the function. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 4 + maxItems: 4 + items: + enum: [ 0, 1, 2, 3, 4, 5, 6, 7, 255 ] + + nuvoton,dsp-tx-slot-mapping: + description: + Configures the TDM slot routing for DSP TX functions. + Each integer assigns a specific DSP internal function to a TDM slot. + The indices of the array correspond to the following hardware functions + (0) AECL (Acoustic echo cancellation left) + (1) AECR (Acoustic echo cancellation right) + (2) ISNSL (Current sense left) + (3) ISNSR (Current sense right) + (4) VSNSL (Voltage sense left) + (5) VSNSR (Voltage sense right) + (6) TJ (Junction temperature) + (7) VBAT (Battery voltage) + Assigned slots must be unique, except 255 which disables the function. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 8 + maxItems: 8 + items: + enum: [ 0, 1, 2, 3, 4, 5, 6, 7, 255 ] + +required: + - compatible + - reg + - firmware-name + +allOf: + - $ref: dai-common.yaml# + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + codec@1a { + compatible = "nuvoton,nau8360"; + reg = <0x1a>; + #sound-dai-cells = <0>; + firmware-name = "NAU83G60.kcs.bin.l", "NAU83G60.kcs.bin.r"; + nuvoton,dac-cur-enable; + nuvoton,pbtl-enable; + nuvoton,dsp-rx-slot-mapping = <0 1 2 3>; + nuvoton,dsp-tx-slot-mapping = <0 1 2 3 4 5 6 7>; + }; + }; From be88847a8bb1a14194cf50116f8909d127861318 Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Tue, 25 Aug 2026 10:16:44 +0800 Subject: [PATCH 018/924] ASoC: codecs: nau8360: Add support for NAU83G60 amplifier Add support for the Nuvoton NAU83G60 audio codec. The NAU83G60 is a stereo 30W+30W smart amplifier with an integrated low-latency Advanced Audio DSP. Signed-off-by: Neo Chang Link: https://patch.msgid.link/20260825021644.1203076-3-YLCHANG2@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 11 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/nau8360-dsp.c | 634 +++++++++ sound/soc/codecs/nau8360-dsp.h | 122 ++ sound/soc/codecs/nau8360.c | 2300 ++++++++++++++++++++++++++++++++ sound/soc/codecs/nau8360.h | 911 +++++++++++++ 6 files changed, 3980 insertions(+) create mode 100644 sound/soc/codecs/nau8360-dsp.c create mode 100644 sound/soc/codecs/nau8360-dsp.h create mode 100644 sound/soc/codecs/nau8360.c create mode 100644 sound/soc/codecs/nau8360.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f9a47e262a77..3cdf0bcbfaa2 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -173,6 +173,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_MT6660 imply SND_SOC_NAU8315 imply SND_SOC_NAU8325 + imply SND_SOC_NAU8360 imply SND_SOC_NAU8540 imply SND_SOC_NAU8810 imply SND_SOC_NAU8821 @@ -2903,6 +2904,16 @@ config SND_SOC_NAU8825 tristate depends on I2C +config SND_SOC_NAU8360 + tristate "Nuvoton Technology Corporation NAU83G60 Stereo Smart Amplifier" + depends on I2C + select FW_LOADER + help + The NAU83G60 is a stereo 30W+30W smart amplifier with integrated + low-latency Advanced Audio DSP running the KCS (Klippel Controlled + Sound) speaker control algorithm. KCS provides mechanical and + thermal speaker protection with voltage and current sensing. + config SND_SOC_NTPFW tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 3d122ace75ad..05e5ae3d6683 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -198,6 +198,7 @@ snd-soc-mt6359-accdet-y := mt6359-accdet.o snd-soc-mt6660-y := mt6660.o snd-soc-nau8315-y := nau8315.o snd-soc-nau8325-y := nau8325.o +snd-soc-nau8360-y := nau8360.o nau8360-dsp.o snd-soc-nau8540-y := nau8540.o snd-soc-nau8810-y := nau8810.o snd-soc-nau8821-y := nau8821.o @@ -637,6 +638,7 @@ obj-$(CONFIG_SND_SOC_MT6359_ACCDET) += mt6359-accdet.o obj-$(CONFIG_SND_SOC_MT6660) += snd-soc-mt6660.o obj-$(CONFIG_SND_SOC_NAU8315) += snd-soc-nau8315.o obj-$(CONFIG_SND_SOC_NAU8325) += snd-soc-nau8325.o +obj-$(CONFIG_SND_SOC_NAU8360) += snd-soc-nau8360.o obj-$(CONFIG_SND_SOC_NAU8540) += snd-soc-nau8540.o obj-$(CONFIG_SND_SOC_NAU8810) += snd-soc-nau8810.o obj-$(CONFIG_SND_SOC_NAU8821) += snd-soc-nau8821.o diff --git a/sound/soc/codecs/nau8360-dsp.c b/sound/soc/codecs/nau8360-dsp.c new file mode 100644 index 000000000000..2d6bc2962229 --- /dev/null +++ b/sound/soc/codecs/nau8360-dsp.c @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// The NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver. +// +// Copyright (C) 2026 Nuvoton Technology Corp. +// Author: David Lin +// Seven Lee +// John Hsu +// Neo Chang + +#include +#include +#include +#include +#include +#include +#include + +#include "nau8360-dsp.h" +#include "nau8360.h" + +#define NAU8360_DSP_IDLE_RETRY 10 +const unsigned short nau8360_dsp_addr[NAU8360_DSP_FW_NUM] = { + NAU8360_RF000_DSP_COMM, NAU8360_RF002_DSP_COMM }; + +static int nau8360_dsp_chan_kcs_setup(struct snd_soc_component *cp, + const char *fw_name, int dsp_addr); + +#define NAU_DSP_CMD(_id, _msg, _setup, _reply) \ + [_id] = { \ + .cmd_id = _id, \ + .msg_param = _msg, \ + .setup_data = _setup, \ + .reply_data = _reply, \ + } + +#define NAU_DSP_CMD_ID(_id) \ + [_id] = { \ + .cmd_id = _id, \ + } + +/** + * Preamble Fragment Masks: + * FIELD | BITS | SHIFT | MASK | DETAILS + * ----------------|--------|-------|------|--------------------------------- + * length high | [31:24]| 24 | 0xff | Upper 8 bits of total length (LEN[9:2]) + * cmd_id | [23:18]| 18 | 0x3f | Command identifier (6 bits) + * length low | [17:16]| 16 | 0x03 | Lower 2 bits of total length (LEN[1:0]) + * preamble magic | [15:0] | 0 |0xffff| Preamble signature (0xB2A1) + */ +#define NAU8360_HOST_LEN_HIGH_MASK GENMASK(31, 24) +#define NAU8360_HOST_CMD_ID_MASK GENMASK(23, 18) +#define NAU8360_HOST_LEN_LOW_MASK GENMASK(17, 16) +#define NAU8360_HOST_PREAMBLE_MASK GENMASK(15, 0) + +/** + * Payload Fragment Masks: + * FIELD | BITS | SHIFT | MASK | DETAILS + * ----------------|--------|-------|------|--------------------------------- + * param_size | [31:16]| 16 |0xffff| Size of parameter data (16 bits) + * param_offset | [15:0] | 0 |0xffff| Starting offset (16 bits) + */ +#define NAU8360_HOST_PARAM_SIZE_MASK GENMASK(31, 16) +#define NAU8360_HOST_PARAM_OFFSET_MASK GENMASK(15, 0) + +/** + * Trailing Fragment Masks: + * FIELD | BITS | SHIFT | MASK | DETAILS + * ----------------|--------|-------|------|--------------------------------- + * length high | [15:14]| 14 | 0x03 | Upper 2 bits of total length + * padding | [13:12]| 12 | 0x03 | Padding bytes count + * length low | [7:0] | 0 | 0xff | Lower 8 bits of total length + * Note: Reassembled length = (length_high << 8) | length_low (Total 10 bits) + */ +#define NAU8360_TRAIL_LEN_LOW_MASK GENMASK(7, 0) +#define NAU8360_TRAIL_PAD_MASK GENMASK(13, 12) +#define NAU8360_TRAIL_LEN_HIGH_MASK GENMASK(15, 14) + +/** + * Reply Preamble Masks: + * FIELD | BITS | SHIFT | MASK | DETAILS + * ----------------|--------|-------|------|--------------------------------- + * length high | [31:24]| 24 | 0xff | Upper 8 bits of total length + * reply_id | [23:18]| 18 | 0x3f | Reply identifier (6 bits) + * length low | [17:16]| 16 | 0x03 | Lower 2 bits of total length + * Note: Reassembled length = (length_high << 2) | length_low (Total 10 bits) + */ +#define NAU8360_REPLY_LEN_HIGH_MASK GENMASK(31, 24) +#define NAU8360_REPLY_ID_MASK GENMASK(23, 18) +#define NAU8360_REPLY_LEN_LOW_MASK GENMASK(17, 16) + +static const struct nau8360_cmd_info nau8360_dsp_cmd_table[] = { + /* { cmd_id, msg_param, setup_data, reply_data} */ + NAU_DSP_CMD(NAU8360_DSP_CMD_GET_COUNTER, 0, 0, 1), + NAU_DSP_CMD(NAU8360_DSP_CMD_GET_FRAME_STATUS, 0, 0, 1), + NAU_DSP_CMD(NAU8360_DSP_CMD_GET_REVISION, 0, 0, 1), + NAU_DSP_CMD(NAU8360_DSP_CMD_GET_KCS_RSLTS, 1, 0, 1), + NAU_DSP_CMD(NAU8360_DSP_CMD_GET_KCS_SETUP, 1, 0, 1), + NAU_DSP_CMD(NAU8360_DSP_CMD_SET_KCS_SETUP, 1, 1, 0), + NAU_DSP_CMD_ID(NAU8360_DSP_CMD_CLK_STOP), + NAU_DSP_CMD_ID(NAU8360_DSP_CMD_CLK_RESTART), +}; + +static const char *const dsp_cmd_table[] = { + [NAU8360_DSP_CMD_GET_COUNTER] = "GET_COUNTER", + [NAU8360_DSP_CMD_GET_FRAME_STATUS] = "GET_FRAME_STATUS", + [NAU8360_DSP_CMD_GET_REVISION] = "GET_REVISION", + [NAU8360_DSP_CMD_GET_KCS_RSLTS] = "GET_KCS_RSLTS", + [NAU8360_DSP_CMD_GET_KCS_SETUP] = "GET_KCS_SETUP", + [NAU8360_DSP_CMD_SET_KCS_SETUP] = "SET_KCS_SETUP", + [NAU8360_DSP_CMD_CLK_STOP] = "CLK_STOP", + [NAU8360_DSP_CMD_CLK_RESTART] = "CLK_RESTART", +}; + +static int nau8360_dsp_idle(struct snd_soc_component *cp, unsigned short dsp_addr) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + unsigned int idle_pattern, timeout = NAU8360_DSP_IDLE_RETRY * USEC_PER_MSEC; + int ret = 0; + + ret = regmap_read_poll_timeout(nau8360->regmap, dsp_addr, idle_pattern, + idle_pattern == NAU8360_DSP_COMM_IDLE_WORD, USEC_PER_MSEC, timeout); + if (ret) + dev_err(nau8360->dev, "Timeout waiting for DSP idle state: %d", ret); + + return ret; +} + +/** + * nau8360_pack_preamble - Pack DSP preamble fragment + * @cmd_id: Command ID for the DSP message + * @frag_len: Total length of the message fragments + * + * Return: 32-bit packed payload in Little Endian format. + */ +static inline u32 nau8360_pack_preamble(u8 cmd_id, u16 frag_len) +{ + return FIELD_PREP(NAU8360_HOST_PREAMBLE_MASK, NAU8360_DSP_COMM_PREAMBLE) | + FIELD_PREP(NAU8360_HOST_CMD_ID_MASK, cmd_id) | + FIELD_PREP(NAU8360_HOST_LEN_LOW_MASK, frag_len) | + FIELD_PREP(NAU8360_HOST_LEN_HIGH_MASK, frag_len >> 2); +} + +/** + * nau8360_pack_param - Pack DSP parameter fragment + * @param_offset: Starting offset of the parameter data + * @param_size: Size of the parameter data in bytes + * + * Return: 32-bit packed payload in Little Endian format. + */ +static inline u32 nau8360_pack_param(u16 param_offset, u16 param_size) +{ + return FIELD_PREP(NAU8360_HOST_PARAM_OFFSET_MASK, param_offset) | + FIELD_PREP(NAU8360_HOST_PARAM_SIZE_MASK, param_size); +} + +/** + * nau8360_pack_trailing - Pack DSP trailing fragment + * @frag_cnt: Current fragment count + * @padding: Number of padding bytes added to the final data fragment + * + * Return: 32-bit packed payload in Little Endian format. + */ +static inline u32 nau8360_pack_trailing(u16 frag_cnt, u8 padding) +{ + return FIELD_PREP(NAU8360_TRAIL_LEN_LOW_MASK, frag_cnt) | + FIELD_PREP(NAU8360_TRAIL_PAD_MASK, padding) | + FIELD_PREP(NAU8360_TRAIL_LEN_HIGH_MASK, frag_cnt >> 8); +} + +static void nau8360_send_data_payload(struct snd_soc_component *cp, + unsigned short dsp_addr, const void *param_data, int param_size, + int *frag_cnt, int *padding) +{ + const u8 *data = (const u8 *)param_data; + u32 payload = 0; + int i, data_size = 0; + + for (i = 0; i < param_size; i++) { + payload |= data[i] << (data_size * 8); + data_size++; + + if (data_size == NAU8360_DSP_DATA_BYTE) { + snd_soc_component_write(cp, dsp_addr, payload); + data_size = 0; + payload = 0; + (*frag_cnt)++; + } + } + + if (data_size > 0) { + *padding = NAU8360_DSP_DATA_BYTE - data_size; + snd_soc_component_write(cp, dsp_addr, payload); + (*frag_cnt)++; + } +} + +static int nau8360_message_to_dsp(struct snd_soc_component *cp, + const struct nau8360_cmd_info *cmd_info, int frag_len, int param_offset, + int param_size, void *param_data, unsigned short dsp_addr) +{ + unsigned int payload; + int ret, padding = 0, frag_cnt = 0; + + ret = nau8360_dsp_idle(cp, dsp_addr); + if (ret) + return ret; + + /* sending preamble fragment */ + payload = nau8360_pack_preamble(cmd_info->cmd_id, frag_len); + snd_soc_component_write(cp, dsp_addr, payload); + + if (!cmd_info->msg_param) + return ret; + + /* sending payload + padding */ + payload = nau8360_pack_param(param_offset, param_size); + snd_soc_component_write(cp, dsp_addr, payload); + frag_cnt++; + + if (cmd_info->setup_data) + nau8360_send_data_payload(cp, dsp_addr, param_data, param_size, + &frag_cnt, &padding); + + /* sending trailing fragment */ + frag_cnt++; + payload = nau8360_pack_trailing(frag_cnt, padding); + snd_soc_component_write(cp, dsp_addr, payload); + + if (frag_cnt != frag_len) { + dev_err(cp->dev, "message error (CMD_ID 0x%x, LEN 0x%x) !!!", + cmd_info->cmd_id, frag_cnt); + return -EPROTO; + } + + return 0; +} + +static int nau8360_dsp_replied(struct nau8360 *nau8360, int *length, + unsigned short dsp_addr) +{ + unsigned int reply_preamble; + int ret, reply_id; + + ret = regmap_read_poll_timeout(nau8360->regmap, dsp_addr, reply_preamble, + (reply_preamble & 0xffff) == NAU8360_DSP_COMM_PREAMBLE, + USEC_PER_MSEC, NAU8360_DSP_IDLE_RETRY * USEC_PER_MSEC); + if (ret) { + dev_err(nau8360->dev, "timeout for reply preamble: %d", ret); + return ret; + } + + *length = FIELD_GET(NAU8360_REPLY_LEN_LOW_MASK, reply_preamble); + *length |= FIELD_GET(NAU8360_REPLY_LEN_HIGH_MASK, reply_preamble) << 2; + reply_id = FIELD_GET(NAU8360_REPLY_ID_MASK, reply_preamble); + + return (reply_id != NAU8360_DSP_REPLY_OK) ? -reply_id : 0; +} + +static int nau8360_validate_trailing(struct snd_soc_component *cp, + unsigned short dsp_addr, int frag_len, int pad_len_exp) +{ + int ret, len_pos, pad_len; + unsigned int payload; + struct device *dev = cp->dev; + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + + /* reading trailing fragment */ + ret = regmap_read(nau8360->regmap, dsp_addr, &payload); + if (ret) { + dev_err(dev, "failed to read trailing fragment"); + return ret; + } + + len_pos = FIELD_GET(NAU8360_TRAIL_LEN_LOW_MASK, payload); + len_pos |= FIELD_GET(NAU8360_TRAIL_LEN_HIGH_MASK, payload) << 8; + + if (len_pos != frag_len) { + dev_err(dev, "LEN_POST %02X, expect %02X", len_pos, frag_len); + return -EPROTO; + } + + pad_len = FIELD_GET(NAU8360_TRAIL_PAD_MASK, payload); + if (pad_len != pad_len_exp) { + dev_err(dev, "PAD_LEN %02X, expect %02X", pad_len, pad_len_exp); + return -EPROTO; + } + + return 0; +} + +static int nau8360_read_data_payload(struct snd_soc_component *cp, + unsigned short dsp_addr, int frag_len, bool msg_param, + void *data, int data_size, int *data_count) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct device *dev = cp->dev; + unsigned int payload; + u32 *data_buf = data; + int i, j, ret; + + *data_count = (msg_param) ? data_size : 0; + for (i = 0; i < frag_len - 1; i++) { + ret = regmap_read(nau8360->regmap, dsp_addr, &payload); + if (ret) { + dev_err(dev, "failed to read payload of dsp"); + return ret; + } + + if (!msg_param) { + *data_buf++ = payload; + break; + } + + if (*data_count >= NAU8360_DSP_DATA_BYTE) { + *data_buf++ = payload; + *data_count -= NAU8360_DSP_DATA_BYTE; + } else if (*data_count > 0) { + for (j = 0; j < *data_count; j++) + ((u8 *)data_buf)[j] = (payload >> (j * 8)) & 0xff; + + *data_count = 0; + } + + if (*data_count == 0) + break; + } + + return 0; +} + +static int nau8360_reply_from_dsp(struct snd_soc_component *cp, + const struct nau8360_cmd_info *cmd_info, int data_size, + void *data, unsigned short dsp_addr) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct device *dev = cp->dev; + int ret, frag_len, pad_len_exp, data_count = 0; + + ret = nau8360_dsp_replied(nau8360, &frag_len, dsp_addr); + if (ret) + return ret; + if (!frag_len || !cmd_info->reply_data) + return 0; + if (!data || frag_len == 1) + return -EINVAL; + + ret = nau8360_read_data_payload(cp, dsp_addr, frag_len, + cmd_info->msg_param, data, data_size, &data_count); + if (ret) + return ret; + + /* check the reply length same as request */ + if (data_count && (cmd_info->cmd_id == NAU8360_DSP_CMD_GET_KCS_RSLTS || + cmd_info->cmd_id == NAU8360_DSP_CMD_GET_KCS_SETUP)) + dev_warn(dev, "payload_len %d, expected %d", + data_size - data_count, data_size); + + pad_len_exp = cmd_info->msg_param ? + (frag_len - 1) * NAU8360_DSP_DATA_BYTE - (data_size - data_count) : 0; + return nau8360_validate_trailing(cp, dsp_addr, frag_len, pad_len_exp); +} + +/** + * nau8360_send_dsp_command - Send command to DSP + * + * @component: component to register + * @cmd_id: DSP supported command ID + * @kcs_setup: KCS setup structure + * @dsp_addr: DSP address + * + * The communication protocol is a Master-Slave type protocol + * where the host processor is the master and DSP is the slave. + * The Master initiates the communication and can either write or + * read back from the slave. + * Transactions from the Master are called "Messages", + * and read-back data from the Slave is called a "Reply". + * + * The function sends command to DSP according to the command ID. + * These commands include getting the information of DSP, + * getting or setting KCS configuration, or making DSP control. + */ +static int nau8360_send_dsp_command(struct snd_soc_component *cp, int cmd_id, + struct nau8360_kcs_setup *kcs_setup, unsigned short dsp_addr) +{ + const struct nau8360_cmd_info *cmd_info; + int ret = 0, frag_len = 0; + + cmd_info = &nau8360_dsp_cmd_table[cmd_id]; + if ((cmd_info->msg_param && !kcs_setup->set_len) || + (cmd_info->setup_data && !kcs_setup->set_kcs_data) || + (cmd_info->reply_data && !kcs_setup->get_data)) + return -EFAULT; + + /* Read up to 1kB data because the LEN field to request data is 10-bits + * long; and not beyond 3kB offset. + */ + if (cmd_id == NAU8360_DSP_CMD_GET_KCS_SETUP && + (kcs_setup->set_len > NAU8360_DSP_KCS_DAT_LEN_MAX || + kcs_setup->set_kcs_offset > NAU8360_DSP_KCS_OFFSET_MAX)) + return -ERANGE; + + /* one fragment for offset and size parameters + * one fragment for a postamble fragment + */ + if (cmd_info->msg_param) + frag_len += 2; + + /* fragments for KCS setup writen */ + if (cmd_info->setup_data) + frag_len += DIV_ROUND_UP(kcs_setup->set_len, NAU8360_DSP_DATA_BYTE); + + ret = nau8360_message_to_dsp(cp, cmd_info, frag_len, + kcs_setup->set_kcs_offset, kcs_setup->set_len, + kcs_setup->set_kcs_data, dsp_addr); + if (ret) + return ret; + + return nau8360_reply_from_dsp(cp, cmd_info, kcs_setup->get_len, + kcs_setup->get_data, dsp_addr); +} + +static inline int nau8360_dsp_exec_command(struct snd_soc_component *cp, int cmd_id, + int offset, int set_len, void *set_data, int get_len, void *get_data, + int dsp_addr) +{ + struct nau8360_kcs_setup kcs_setup = { + .set_kcs_offset = offset, + .set_len = set_len, + .set_kcs_data = set_data, + .get_len = get_len, + .get_data = get_data, + }; + + return nau8360_send_dsp_command(cp, cmd_id, &kcs_setup, dsp_addr); +} + +static inline int nau8360_send_dsp_broadcast(struct snd_soc_component *cp, int cmd_id) +{ + int i, ret; + + for (i = 0; i < NAU8360_DSP_FW_NUM; i++) { + ret = nau8360_dsp_exec_command(cp, cmd_id, 0, 0, NULL, 0, NULL, + nau8360_dsp_addr[i]); + if (ret) { + dev_err(cp->dev, "DSP %x fail (%d)", nau8360_dsp_addr[i], ret); + return ret; + } + } + + return 0; +} + +/** + * nau8360_dsp_kcs_setup - Send KCS setup command to DSP + * + * @component: component to register + * @offset: address offset relative to KCS start + * @size: size of data writen to KCS + * @data: data writen to KCS setup + * @dsp_addr : DSP address + * + * The function sends KCS setup command to DSP for + * setting KCS configuration. The maximum size that you can transfer into + * the DSP is 96 bytes. Therefore, the driver has to split the data into + * 96 bytes chucks, if the setup configuration over the threshold. + */ +static int nau8360_dsp_kcs_setup(struct snd_soc_component *cp, int offset, int size, + const void *data, unsigned short dsp_addr) +{ + u8 *data_buf = (u8 *)data; + unsigned int kcs_rst = 0; + int retries = 0, ret, data_len, data_rem, addr_offset; + + /* Limit full load of KCS_SETUP data and not beyond 3kB offset. */ + if (!data || size > NAU8360_DSP_KCS_DAT_LEN_MAX || + offset > NAU8360_DSP_KCS_OFFSET_MAX) + return -EINVAL; + + /* sending fragments for KCS setup */ + addr_offset = offset; + data_rem = size; + + while (data_rem) { + data_len = min(data_rem, NAU8360_DSP_KCS_TX_MAX); + + ret = nau8360_dsp_exec_command(cp, NAU8360_DSP_CMD_SET_KCS_SETUP, + addr_offset, data_len, (void *)data_buf, 0, &kcs_rst, dsp_addr); + if (ret) { + if (retries++ < NAU8360_DSP_RETRY_MAX) + continue; + return ret; + } + + data_buf += (u8)data_len; + addr_offset += data_len; + data_rem -= data_len; + + /* checking KCS result */ + ret = nau8360_dsp_exec_command(cp, NAU8360_DSP_CMD_GET_KCS_RSLTS, + 0, NAU8360_DSP_DATA_BYTE, NULL, + NAU8360_DSP_DATA_BYTE, &kcs_rst, dsp_addr); + if (ret) + return ret; + if (kcs_rst != NAU8360_DSP_KCS_RSLTS_SUCCESS) + return -EINVAL; + } + + return 0; +} + +static int nau8360_dsp_get_cmd_put(struct snd_soc_component *cp, + int dsp_addr, int cmd, int *value) +{ + struct device *dev = cp->dev; + int ret; + + dev_dbg(dev, "send DSP %x command %s", dsp_addr, dsp_cmd_table[cmd]); + + ret = nau8360_dsp_exec_command(cp, cmd, 0, sizeof(int), NULL, + sizeof(int), value, dsp_addr); + if (ret) { + dev_err(dev, "do command fail (%d)", ret); + return ret; + } + + return 0; +} + +static int nau8360_dsp_clock_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cp = snd_soc_dapm_to_component(w->dapm); + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + int ret = 0; + + mutex_lock(&nau8360->lock); + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = nau8360_send_dsp_broadcast(cp, NAU8360_DSP_CMD_CLK_RESTART); + else if (SND_SOC_DAPM_EVENT_OFF(event)) + ret = nau8360_send_dsp_broadcast(cp, NAU8360_DSP_CMD_CLK_STOP); + + mutex_unlock(&nau8360->lock); + + return ret; +} + +static const struct snd_soc_dapm_widget nau8360_dsp_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("DSP Clock", SND_SOC_NOPM, 0, 0, nau8360_dsp_clock_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route nau8360_dsp_dapm_routes[] = { + { "DSP", NULL, "HW3 Engine" }, + { "DSP", NULL, "DSP Clock" }, +}; + +static int nau8360_dsp_chan_kcs_setup(struct snd_soc_component *cp, + const char *fw_name, int dsp_addr) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + const struct firmware *fw; + int buf_off, buf_len; + int ret = 0, status = 0; + + mutex_lock(&nau8360->lock); + ret = nau8360_dsp_get_cmd_put(cp, dsp_addr, + NAU8360_DSP_CMD_GET_FRAME_STATUS, &status); + mutex_unlock(&nau8360->lock); + if (ret || !(status & NAU8360_DSP_ALGO_OK)) { + dev_err(cp->dev, "DSP %x is not ready", dsp_addr); + return -EIO; + } + + dev_info(cp->dev, "DSP %x is ready to load firmware %s, status %x", + dsp_addr, fw_name, status); + + ret = request_firmware(&fw, fw_name, cp->dev); + if (ret) { + dev_err(cp->dev, "failed to load firmware (%d)", ret); + return ret; + } + + buf_off = 0; + buf_len = nau8360->kcs_setup_size = fw->size; + mutex_lock(&nau8360->lock); + ret = nau8360_dsp_kcs_setup(cp, buf_off, buf_len, fw->data, dsp_addr); + mutex_unlock(&nau8360->lock); + if (ret) { + dev_err(cp->dev, "send DSP command %s fail (%d)", + dsp_cmd_table[NAU8360_DSP_CMD_SET_KCS_SETUP], ret); + } + release_firmware(fw); + + return ret; +} + +int nau8360_dsp_init(struct snd_soc_component *cp) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + int i, ret; + + for (i = 0; i < NAU8360_DSP_FW_NUM; i++) { + ret = nau8360_dsp_chan_kcs_setup(cp, nau8360->dsp_firmware[i], nau8360_dsp_addr[i]); + if (ret) + return ret; + } + + return 0; +} + +int nau8360_dsp_setup_controls(struct snd_soc_component *cp) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct snd_soc_dapm_context *dapm = nau8360->dapm; + int ret; + + ret = snd_soc_dapm_new_controls(dapm, nau8360_dsp_dapm_widgets, + ARRAY_SIZE(nau8360_dsp_dapm_widgets)); + if (ret) { + dev_err(cp->dev, "add DSP widget fail (%d)", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(dapm, nau8360_dsp_dapm_routes, + ARRAY_SIZE(nau8360_dsp_dapm_routes)); + if (ret) { + dev_err(cp->dev, "add DSP route fail (%d)", ret); + return ret; + } + + return 0; +} diff --git a/sound/soc/codecs/nau8360-dsp.h b/sound/soc/codecs/nau8360-dsp.h new file mode 100644 index 000000000000..f8c2c97a25c4 --- /dev/null +++ b/sound/soc/codecs/nau8360-dsp.h @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// The NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver. +// +// Copyright (C) 2026 Nuvoton Technology Corp. +// Author: David Lin +// Seven Lee +// John Hsu +// Neo Chang + +#ifndef __NAU8360_DSP_H__ +#define __NAU8360_DSP_H__ + +#define NAU8360_DSP_COMM_IDLE_WORD 0xf4f3f2f1 +#define NAU8360_DSP_COMM_PREAMBLE 0xb2a1 +#define NAU8360_DSP_DATA_BYTE 4 +#define NAU8360_DSP_DATA_LEN (NAU8360_DSP_DATA_BYTE << 3) +/* max bytes of data to transfer into DSP each time during the KCS setup */ +#define NAU8360_DSP_KCS_TX_MAX 96 +#define NAU8360_DSP_RETRY_MAX 3 +#define NAU8360_DSP_KCS_DAT_LEN_MAX 1024 +#define NAU8360_DSP_KCS_OFFSET_MAX 3072 +#define NAU8360_DSP_KCS_RSLTS_SUCCESS 0x10 + +/* FRAME_STATUS (0x9) */ +#define NAU8360_DSP_SNS_OVF_SFT 31 +#define NAU8360_DSP_SNS_OVF (0x1 << NAU8360_DSP_SNS_OVF_SFT) +#define NAU8360_DSP_AUD_UVF_SFT 30 +#define NAU8360_DSP_AUD_UVF (0x1 << NAU8360_DSP_AUD_UVF_SFT) +#define NAU8360_DSP_AUD_OVF_SFT 29 +#define NAU8360_DSP_AUD_OVF (0x1 << NAU8360_DSP_AUD_OVF_SFT) +#define NAU8360_DSP_ALC_STS_SFT 28 +#define NAU8360_DSP_ALC_STS (0x1 << NAU8360_DSP_ALC_STS_SFT) +#define NAU8360_DSP_CLK_STOP_SFT 25 +#define NAU8360_DSP_CLK_STOP (0x1 << NAU8360_DSP_CLK_STOP_SFT) +#define NAU8360_DSP_OCP_OTP_SFT 24 +#define NAU8360_DSP_OCP_OTP (0x1 << NAU8360_DSP_OCP_OTP_SFT) +#define NAU8360_DSP_UVLO_SFT 22 +#define NAU8360_DSP_UVLO (0x1 << NAU8360_DSP_UVLO_SFT) +#define NAU8360_DSP_OVP_SFT 21 +#define NAU8360_DSP_OVP (0x1 << NAU8360_DSP_OVP_SFT) +#define NAU8360_DSP_APWR_DWN_SFT 20 +#define NAU8360_DSP_APWR_DWN (0x1 << NAU8360_DSP_APWR_DWN_SFT) +#define NAU8360_DSP_SNSR_RATE_SFT 12 +#define NAU8360_DSP_SNSR_RATE_MASK (0xff << NAU8360_DSP_SNSR_RATE_SFT) +#define NAU8360_DSP_AUD_RATE_SFT 4 +#define NAU8360_DSP_AUD_RATE_MASK (0xff << NAU8360_DSP_AUD_RATE_SFT) +#define NAU8360_DSP_FEED_TRU_SFT 1 +#define NAU8360_DSP_FEED_TRU (0x1 << NAU8360_DSP_FEED_TRU_SFT) +#define NAU8360_DSP_ALGO_OK 0x1 + +#define NAU8360_DSP_FIRMDIR "Nuvoton/" +#define NAU8360_DSP_FIRMWARE NAU8360_DSP_FIRMDIR"NAU83G60.kcs.bin" +#define NAU8360_DSP_FW_NUM NAU8360_DSP_CORE_NUM +#define NAU8360_DSP_FW_NAMELEN 64 +#define NAU8360_IS_DSP_REG(reg) \ + ((reg) == NAU8360_RF000_DSP_COMM || (reg) == NAU8360_RF002_DSP_COMM) + +enum { + NAU8360_DSP_REPLY_OK, + NAU8360_DSP_REPLY_MSG_INTEGRETY_ERR, + NAU8360_DSP_REPLY_EXECUTION_ERR, + NAU8360_DSP_REPLY_COMMAND_DOESNT_EXISTS_ERR, + NAU8360_DSP_REPLY_UNKNOWN_ERR, + NAU8360_DSP_REPLY_MSG_TOO_LONG, +}; + +enum { + NAU8360_DSP_CMD_GET_COUNTER = 0x1, + NAU8360_DSP_CMD_GET_FRAME_STATUS = 0x9, + NAU8360_DSP_CMD_GET_REVISION = 0xa, + NAU8360_DSP_CMD_GET_KCS_RSLTS = 0x4, + NAU8360_DSP_CMD_GET_KCS_SETUP = 0x6, + NAU8360_DSP_CMD_SET_KCS_SETUP = 0x7, + NAU8360_DSP_CMD_CLK_STOP = 0xb, + NAU8360_DSP_CMD_CLK_RESTART = 0xc, +}; + +/** + * struct nau8360_cmd_info - DSP command information structure + * @cmd_id: The command identification number + * @msg_param: Message parameters including offset, size, and data + * @setup_data: Setup data for write-only operations + * @reply_data: Reply data received from the DSP + */ +struct nau8360_cmd_info { + int cmd_id; + bool msg_param; + bool setup_data; + bool reply_data; +}; + +/** + * struct nau8360_kcs_setup - KCS setup configuration structure + * @set_len: Length of data written in bytes + * @set_kcs_offset: Address offset relative to the start of KCS + * @set_kcs_data: Pointer to the data written to KCS + * @get_len: Length of data from the reply in bytes + * @get_data: Pointer to the buffer for reply data + */ +struct nau8360_kcs_setup { + int set_len; + int set_kcs_offset; + void *set_kcs_data; + int get_len; + void *get_data; +}; + +/** + * struct nau8360_dsp_ctx - DSP context structure for firmware loading + * @cp: Pointer to the ALSA SoC component structure + * @dsp_addr: Register address of the DSP core (Left or Right channel) + */ +struct nau8360_dsp_ctx { + struct snd_soc_component *cp; + int dsp_addr; +}; + +int nau8360_dsp_init(struct snd_soc_component *component); +int nau8360_dsp_setup_controls(struct snd_soc_component *component); + +#endif /* __NAU8360_DSP_H__ */ diff --git a/sound/soc/codecs/nau8360.c b/sound/soc/codecs/nau8360.c new file mode 100644 index 000000000000..d7af2dffb749 --- /dev/null +++ b/sound/soc/codecs/nau8360.c @@ -0,0 +1,2300 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// The NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver. +// +// Copyright (C) 2026 Nuvoton Technology Corp. +// Author: David Lin +// Seven Lee +// John Hsu +// Neo Chang + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nau8360-dsp.h" +#include "nau8360.h" + +/* range of Master Clock MCLK (Hz) */ +#define MASTER_CLK_MIN 11025000 +#define MASTER_CLK_MAX 24576000 +/* DSP Optimal Clock Range 120MHz~126M(Hz) */ +#define DSP_OP_CLK48 122880000 +#define DSP_OP_CLK44 112896000 +/* the maximum frequency of DAC and IV sense clock */ +#define CLK_DA_IVSNS_MAX 6144000 +#define ADSP_SR_48000 48000 +#define ADSP_SR_44100 44100 + +static const int ivsns_clk_div[] = { 1, 2, 4, 5, 8, 10 }; + +static const int dac_clk_div[] = { 1, 2, 4, 8 }; + +static const char * const nau8360_rx_func_names[] = { + [NAU8360_TDM_DACL] = "DAC_L", + [NAU8360_TDM_DACR] = "DAC_R", + [NAU8360_TDM_ANCL] = "ANC_L", + [NAU8360_TDM_ANCR] = "ANC_R", +}; + +static const char * const nau8360_tx_func_names[] = { + [NAU8360_TDM_AECL] = "AEC_L", + [NAU8360_TDM_AECR] = "AEC_R", + [NAU8360_TDM_ISNSL] = "ISNS_L", + [NAU8360_TDM_ISNSR] = "ISNS_R", + [NAU8360_TDM_VSNSL] = "VSNS_L", + [NAU8360_TDM_VSNSR] = "VSNS_R", + [NAU8360_TDM_TJ] = "TJ", + [NAU8360_TDM_VBAT] = "VBAT", +}; + +/* PLL threshold */ +#define PLL_FREQ_MIN 1000000 +#define PLL_FREQ_MAX 32000000 +#define PLL_FOUT_MIN 12500000 +#define PLL_FOUT_MAX 125000000 +#define PLL_FREF_MAX 8000000 +#define PLL_FVCO_MIN 50000000 +#define MSEL_MAX 32 +#define RSEL_MAX 4 + +static const struct reg_default nau8360_reg_defaults[] = { + { NAU8360_R02_I2C_ADDR, 0x0000 }, + { NAU8360_R03_CLK_CTRL0, 0x0000 }, + { NAU8360_R04_CLK_CTRL1, 0x0000 }, + { NAU8360_R05_INTERRUPT_CTRL, 0x40ff }, + { NAU8360_R07_GP_CTRL, 0xaa30 }, + { NAU8360_R08_GP_CTRL0, 0x1e1e }, + { NAU8360_R09_GP_CTRL1, 0x1e1e }, + { NAU8360_R0A_GP_CTRL2, 0x0000 }, + { NAU8360_R0B_I2S_PCM_CTRL1, 0x0702 }, + { NAU8360_R0C_I2S_PCM_CTRL2, 0x0a10 }, + { NAU8360_R0D_I2S_PCM_CTRL3, 0x1300 }, + { NAU8360_R0E_I2S_DATA_CTRL1, 0x0304 }, + { NAU8360_R0F_I2S_DATA_CTRL2, 0x080b }, + { NAU8360_R10_I2S_DATA_CTRL3, 0x0014 }, + { NAU8360_R11_I2S_DATA_CTRL4, 0x0014 }, + { NAU8360_R12_PATH_CTRL, 0x0400 }, + { NAU8360_R17_I2S0_DATA_CTRL5, 0x0014 }, + { NAU8360_R1A_DSP_CORE_CTRL2, 0x0010 }, + { NAU8360_R2C_ALC_CTRL1, 0x2000 }, + { NAU8360_R2D_ALC_CTRL2, 0x8400 }, + { NAU8360_R2E_ALC_CTRL3, 0x2083 }, + { NAU8360_R31_UVLOP_CTRL1, 0x0000 }, + { NAU8360_R32_UVLOP_CTRL2, 0x8400 }, + { NAU8360_R33_UVLOP_CTRL3, 0x0000 }, + { NAU8360_R40_CLK_DET_CTRL, 0xca60 }, + { NAU8360_R41_CLK_CTL2, 0xc400 }, + { NAU8360_R5D_SINC_CFG, 0x0010 }, + { NAU8360_R5F_ANA_TRIM_CFG1, 0x8400 }, + { NAU8360_R60_RST, 0x0010 }, + { NAU8360_R67_ANALOG_CONTROL_0, 0x0160 }, + { NAU8360_R68_ANALOG_CONTROL_1, 0x00d4 }, + { NAU8360_R6A_SARADC_CFG0, 0x5c09 }, + { NAU8360_R6B_SARADC_CFG1, 0x0008 }, + { NAU8360_R6C_IVSNS_CFG0, 0xf040 }, + { NAU8360_R6D_IVSNS_CFG1, 0x3555 }, + { NAU8360_R6E_DAC_CFG0, 0x0ed5 }, + { NAU8360_R71_CLK_DIV_CFG, 0x0211 }, + { NAU8360_R72_PLL_CFG0, 0xccc4 }, + { NAU8360_R73_PLL_CFG1, 0x0101 }, + { NAU8360_R74_PLL_CFG2, 0x0000 }, + { NAU8360_R7A_DAC_TRIM_CFG2, 0x0000 }, + { NAU8360_R7B_IVSNS_TRIM_CFG, 0x0000 }, + { NAU8360_R7C_MISC_TRIM_CFG, 0x72d5 }, + { NAU8360_R86_HW3_CTL0, 0x2000 }, + { NAU8360_R88_ALC_CTRL6, 0x0000 }, + { NAU8360_R8A_HW3_VL_CTL7, 0xc000 }, + { NAU8360_R8B_HW3_VR_CTL8, 0xc000 }, + { NAU8360_R8C_HW3_CTL6, 0x0000 }, + { NAU8360_R8D_HW3_IL_CTL7, 0xc000 }, + { NAU8360_R8E_HW3_IR_CTL8, 0xc000 }, + { NAU8360_R8F_HW3_CTL9, 0x0000 }, + { NAU8360_R90_HW2_CTL0, 0x2000 }, + { NAU8360_R96_HW2_CTL6, 0x0000 }, + { NAU8360_R97_HW2_CTL7, 0xc000 }, + { NAU8360_R98_HW2_CTL8, 0xc000 }, + { NAU8360_R99_HW2_CTL9, 0x0000 }, + { NAU8360_R9A_HW1_CTL0, 0xc000 }, + { NAU8360_R9B_HW1_CTL1, 0xc000 }, + { NAU8360_R9C_HW1_CTL2, 0x0800 }, + { NAU8360_R9D_PEQ_CTL, 0x0001 }, + { NAU8360_RA0_LEFT_XODRC_CTRL, 0x0000 }, + { NAU8360_RA2_RIGHT_XODRC_CTRL, 0x0000 }, + { NAU8360_RA4_ANA_REG_0, 0x7f86 }, + { NAU8360_RA5_ANA_REG_1, 0x276e }, +}; + +static bool nau8360_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8360_R00_SOFTWARE_RST ... NAU8360_R12_PATH_CTRL: + case NAU8360_R17_I2S0_DATA_CTRL5: + case NAU8360_R1A_DSP_CORE_CTRL2: + case NAU8360_R21_VBAT_READOUT ... NAU8360_R22_TEMP_READOUT: + case NAU8360_R2C_ALC_CTRL1 ... NAU8360_R2E_ALC_CTRL3: + case NAU8360_R31_UVLOP_CTRL1 ... NAU8360_R33_UVLOP_CTRL3: + case NAU8360_R40_CLK_DET_CTRL ... NAU8360_R41_CLK_CTL2: + case NAU8360_R46_I2C_DEVICE_ID: + case NAU8360_R5D_SINC_CFG: + case NAU8360_R5F_ANA_TRIM_CFG1 ... NAU8360_R60_RST: + case NAU8360_R67_ANALOG_CONTROL_0 ... NAU8360_R6E_DAC_CFG0: + case NAU8360_R71_CLK_DIV_CFG ... NAU8360_R74_PLL_CFG2: + case NAU8360_R77_SOFT_SD ... NAU8360_R7C_MISC_TRIM_CFG: + case NAU8360_R7E_CLK_GATED_EN: + case NAU8360_R86_HW3_CTL0: + case NAU8360_R88_ALC_CTRL6: + case NAU8360_R8A_HW3_VL_CTL7 ... NAU8360_R90_HW2_CTL0: + case NAU8360_R96_HW2_CTL6 ... NAU8360_R9D_PEQ_CTL: + case NAU8360_RA0_LEFT_XODRC_CTRL: + case NAU8360_RA2_RIGHT_XODRC_CTRL: + case NAU8360_RA4_ANA_REG_0 ... NAU8360_RA5_ANA_REG_1: + case NAU8360_R100_LEFT_BIQ0_COE ... (NAU8360_R100_LEFT_BIQ0_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R10C_LEFT_BIQ1_COE ... (NAU8360_R10C_LEFT_BIQ1_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R118_LEFT_BIQ2_COE ... (NAU8360_R118_LEFT_BIQ2_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R124_LEFT_BIQ3_COE ... (NAU8360_R124_LEFT_BIQ3_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R130_LEFT_BIQ4_COE ... (NAU8360_R130_LEFT_BIQ4_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R13C_LEFT_BIQ5_COE ... (NAU8360_R13C_LEFT_BIQ5_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R148_LEFT_BIQ6_COE ... (NAU8360_R148_LEFT_BIQ6_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R154_LEFT_BIQ7_COE ... (NAU8360_R154_LEFT_BIQ7_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R160_LEFT_BIQ8_COE ... (NAU8360_R160_LEFT_BIQ8_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R16C_LEFT_BIQ9_COE ... (NAU8360_R16C_LEFT_BIQ9_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R178_LEFT_BIQ10_COE ... (NAU8360_R178_LEFT_BIQ10_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R184_LEFT_BIQ11_COE ... (NAU8360_R184_LEFT_BIQ11_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R190_LEFT_BIQ12_COE ... (NAU8360_R190_LEFT_BIQ12_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R19C_LEFT_BIQ13_COE ... (NAU8360_R19C_LEFT_BIQ13_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R1A8_LEFT_BIQ14_COE ... (NAU8360_R1A8_LEFT_BIQ14_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R200_RIGHT_BIQ0_COE ... (NAU8360_R200_RIGHT_BIQ0_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R20C_RIGHT_BIQ1_COE ... (NAU8360_R20C_RIGHT_BIQ1_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R218_RIGHT_BIQ2_COE ... (NAU8360_R218_RIGHT_BIQ2_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R224_RIGHT_BIQ3_COE ... (NAU8360_R224_RIGHT_BIQ3_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R230_RIGHT_BIQ4_COE ... (NAU8360_R230_RIGHT_BIQ4_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R23C_RIGHT_BIQ5_COE ... (NAU8360_R23C_RIGHT_BIQ5_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R248_RIGHT_BIQ6_COE ... (NAU8360_R248_RIGHT_BIQ6_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R254_RIGHT_BIQ7_COE ... (NAU8360_R254_RIGHT_BIQ7_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R260_RIGHT_BIQ8_COE ... (NAU8360_R260_RIGHT_BIQ8_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R26C_RIGHT_BIQ9_COE ... (NAU8360_R26C_RIGHT_BIQ9_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R278_RIGHT_BIQ10_COE ... (NAU8360_R278_RIGHT_BIQ10_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R284_RIGHT_BIQ11_COE ... (NAU8360_R284_RIGHT_BIQ11_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R290_RIGHT_BIQ12_COE ... (NAU8360_R290_RIGHT_BIQ12_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R29C_RIGHT_BIQ13_COE ... (NAU8360_R29C_RIGHT_BIQ13_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R2A8_RIGHT_BIQ14_COE ... (NAU8360_R2A8_RIGHT_BIQ14_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_RF000_DSP_COMM: + case NAU8360_RF002_DSP_COMM: + return true; + default: + return false; + } + +} + +static bool nau8360_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8360_R00_SOFTWARE_RST ... NAU8360_R12_PATH_CTRL: + case NAU8360_R17_I2S0_DATA_CTRL5: + case NAU8360_R1A_DSP_CORE_CTRL2: + case NAU8360_R2C_ALC_CTRL1 ... NAU8360_R2E_ALC_CTRL3: + case NAU8360_R31_UVLOP_CTRL1 ... NAU8360_R33_UVLOP_CTRL3: + case NAU8360_R40_CLK_DET_CTRL ... NAU8360_R41_CLK_CTL2: + case NAU8360_R5D_SINC_CFG: + case NAU8360_R5F_ANA_TRIM_CFG1: + case NAU8360_R60_RST: + case NAU8360_R67_ANALOG_CONTROL_0 ... NAU8360_R68_ANALOG_CONTROL_1: + case NAU8360_R6A_SARADC_CFG0 ... NAU8360_R6E_DAC_CFG0: + case NAU8360_R71_CLK_DIV_CFG ... NAU8360_R74_PLL_CFG2: + case NAU8360_R77_SOFT_SD ... NAU8360_R7C_MISC_TRIM_CFG: + case NAU8360_R7E_CLK_GATED_EN: + case NAU8360_R86_HW3_CTL0: + case NAU8360_R88_ALC_CTRL6: + case NAU8360_R8A_HW3_VL_CTL7 ... NAU8360_R90_HW2_CTL0: + case NAU8360_R96_HW2_CTL6 ... NAU8360_R9D_PEQ_CTL: + case NAU8360_RA4_ANA_REG_0 ... NAU8360_RA5_ANA_REG_1: + case NAU8360_R100_LEFT_BIQ0_COE ... (NAU8360_R100_LEFT_BIQ0_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R10C_LEFT_BIQ1_COE ... (NAU8360_R10C_LEFT_BIQ1_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R118_LEFT_BIQ2_COE ... (NAU8360_R118_LEFT_BIQ2_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R124_LEFT_BIQ3_COE ... (NAU8360_R124_LEFT_BIQ3_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R130_LEFT_BIQ4_COE ... (NAU8360_R130_LEFT_BIQ4_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R13C_LEFT_BIQ5_COE ... (NAU8360_R13C_LEFT_BIQ5_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R148_LEFT_BIQ6_COE ... (NAU8360_R148_LEFT_BIQ6_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R154_LEFT_BIQ7_COE ... (NAU8360_R154_LEFT_BIQ7_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R160_LEFT_BIQ8_COE ... (NAU8360_R160_LEFT_BIQ8_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R16C_LEFT_BIQ9_COE ... (NAU8360_R16C_LEFT_BIQ9_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R178_LEFT_BIQ10_COE ... (NAU8360_R178_LEFT_BIQ10_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R184_LEFT_BIQ11_COE ... (NAU8360_R184_LEFT_BIQ11_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R190_LEFT_BIQ12_COE ... (NAU8360_R190_LEFT_BIQ12_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R19C_LEFT_BIQ13_COE ... (NAU8360_R19C_LEFT_BIQ13_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R1A8_LEFT_BIQ14_COE ... (NAU8360_R1A8_LEFT_BIQ14_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R200_RIGHT_BIQ0_COE ... (NAU8360_R200_RIGHT_BIQ0_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R20C_RIGHT_BIQ1_COE ... (NAU8360_R20C_RIGHT_BIQ1_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R218_RIGHT_BIQ2_COE ... (NAU8360_R218_RIGHT_BIQ2_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R224_RIGHT_BIQ3_COE ... (NAU8360_R224_RIGHT_BIQ3_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R230_RIGHT_BIQ4_COE ... (NAU8360_R230_RIGHT_BIQ4_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R23C_RIGHT_BIQ5_COE ... (NAU8360_R23C_RIGHT_BIQ5_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R248_RIGHT_BIQ6_COE ... (NAU8360_R248_RIGHT_BIQ6_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R254_RIGHT_BIQ7_COE ... (NAU8360_R254_RIGHT_BIQ7_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R260_RIGHT_BIQ8_COE ... (NAU8360_R260_RIGHT_BIQ8_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R26C_RIGHT_BIQ9_COE ... (NAU8360_R26C_RIGHT_BIQ9_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R278_RIGHT_BIQ10_COE ... (NAU8360_R278_RIGHT_BIQ10_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R284_RIGHT_BIQ11_COE ... (NAU8360_R284_RIGHT_BIQ11_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R290_RIGHT_BIQ12_COE ... (NAU8360_R290_RIGHT_BIQ12_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R29C_RIGHT_BIQ13_COE ... (NAU8360_R29C_RIGHT_BIQ13_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R2A8_RIGHT_BIQ14_COE ... (NAU8360_R2A8_RIGHT_BIQ14_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_RF000_DSP_COMM: + case NAU8360_RF002_DSP_COMM: + return true; + default: + return false; + } +} + +static bool nau8360_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8360_R00_SOFTWARE_RST ... NAU8360_R02_I2C_ADDR: + case NAU8360_R06_INT_CLR_STATUS: + case NAU8360_R21_VBAT_READOUT ... NAU8360_R22_TEMP_READOUT: + case NAU8360_R41_CLK_CTL2: + case NAU8360_R46_I2C_DEVICE_ID: + case NAU8360_R69_ANALOG_CONTROL_3: + case NAU8360_R77_SOFT_SD ... NAU8360_R79_EN_HIRC48M: + case NAU8360_R7E_CLK_GATED_EN: + case NAU8360_R9D_PEQ_CTL: + case NAU8360_RF000_DSP_COMM: + case NAU8360_RF002_DSP_COMM: + return true; + default: + return false; + } +} + +static int nau8360_get_tdm_chan_len(struct nau8360 *nau8360) +{ + int val = 0; + + regmap_read(nau8360->regmap, NAU8360_R0C_I2S_PCM_CTRL2, &val); + val = (val & NAU8360_TDM_CLEN_MASK) >> NAU8360_TDM_CLEN_SFT; + + return (val << 3) + 16; +} + +static inline bool nau8360_dsp_active(struct snd_soc_component *comp) +{ + return (snd_soc_component_read(comp, NAU8360_R12_PATH_CTRL) & + NAU8360_DAC_SEL_DSP); +} + +static int nau8360_anc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cp = snd_kcontrol_chip(kcontrol); + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + int ret, value = NAU8360_PEQ_BAND_8; + + mutex_lock(&nau8360->lock); + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + /* update anc flag if return value 1 and register value changed */ + if (ret != 1) + goto unlock; + + nau8360->anc_enable = ucontrol->value.integer.value[0]; + if (nau8360_dsp_active(cp)) + value = nau8360->anc_enable ? NAU8360_PEQ_BAND_15 : NAU8360_PEQ_BAND_12; + regmap_update_bits(nau8360->regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_BAND_MASK, + value << NAU8360_PEQ_BAND_SFT); + +unlock: + mutex_unlock(&nau8360->lock); + + return ret; +} + +static inline void nau8360_peq_mem_enable(struct regmap *regmap, bool enable) +{ + regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, + NAU8360_HW1_MEM_TEST, enable ? NAU8360_HW1_MEM_TEST : 0); +} + +static inline int nau8360_peq_regaddr(const char *id_name) +{ + int reg, band_num, dsp_addr = NAU8360_DSP_ADDR_BYNAME(id_name); + char *band = strstr(id_name, "BIQ"); + + if (!band || kstrtoint((band + 3), 10, &band_num)) + return -EINVAL; + reg = dsp_addr == NAU8360_RF000_DSP_COMM ? NAU8360_R100_LEFT_BIQ0_COE : + NAU8360_R200_RIGHT_BIQ0_COE; + reg += band_num * NAU8360_TOT_BAND_COE_RANGE; + + return reg; +} + +static int nau8360_peq_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cp = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(cp); + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + int i, value, reg, ret = 0; + u16 *val = (u16 *)ucontrol->value.bytes.data; + + /* Use the DAPM lock to prevent race conditions during DAPM power-up + * state transitions, and check component active status to prohibit + * PEQ access during active audio streams (playback and capture). + */ + snd_soc_dapm_mutex_lock(dapm); + if (snd_soc_component_active(cp)) { + dev_dbg(nau8360->dev, + "PEQ coefficient access is ignored during audio is active"); + ret = -EBUSY; + goto unlock_dapm; + } + + reg = nau8360_peq_regaddr(kcontrol->id.name); + if (reg < 0) { + ret = reg; + goto unlock_dapm; + } + + mutex_lock(&nau8360->lock); + nau8360_peq_mem_enable(nau8360->regmap, true); + for (i = 0; i < params->max / sizeof(u16); i++) { + value = snd_soc_component_read(cp, reg + i); + *(val + i) = cpu_to_be16(value); + } + nau8360_peq_mem_enable(nau8360->regmap, false); + mutex_unlock(&nau8360->lock); + +unlock_dapm: + snd_soc_dapm_mutex_unlock(dapm); + + return ret; +} + +static int nau8360_peq_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cp = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(cp); + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + int i, reg, ret = 0; + __be16 *data = NULL; + bool changed = false; + + /* Use the DAPM lock to prevent race conditions during DAPM power-up + * state transitions, and check component active status to prohibit + * PEQ access during active audio streams (playback and capture). + */ + snd_soc_dapm_mutex_lock(dapm); + if (snd_soc_component_active(cp)) { + dev_dbg(nau8360->dev, + "PEQ coefficient access is ignored during audio is active"); + ret = -EBUSY; + goto unlock_dapm; + } + + reg = nau8360_peq_regaddr(kcontrol->id.name); + if (reg < 0) { + ret = reg; + goto unlock_dapm; + } + + data = kmemdup(ucontrol->value.bytes.data, params->max, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto unlock_dapm; + } + + mutex_lock(&nau8360->lock); + nau8360_peq_mem_enable(nau8360->regmap, true); + for (i = 0; i < params->max / sizeof(u16); i++) { + if (snd_soc_component_read(cp, reg + i) != be16_to_cpu(*(data + i))) { + snd_soc_component_write(cp, reg + i, be16_to_cpu(*(data + i))); + changed = true; + } + } + nau8360_peq_mem_enable(nau8360->regmap, false); + mutex_unlock(&nau8360->lock); + + ret = changed ? 1 : 0; + +unlock_dapm: + snd_soc_dapm_mutex_unlock(dapm); + kfree(data); + + return ret; +} + +#define NAU8360_PEQ_COEF_BYTES_EXT(ch, band) \ + SND_SOC_BYTES_EXT(ch " PEQ Coefficients " band, 20, nau8360_peq_coeff_get, \ + nau8360_peq_coeff_put) + +static const struct snd_kcontrol_new nau8360_snd_controls[] = { + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ0"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ1"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ2"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ3"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ4"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ5"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ6"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ7"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ8"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ9"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ10"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ11"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ12"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ13"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ14"), + + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ0"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ1"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ2"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ3"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ4"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ5"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ6"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ7"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ8"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ9"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ10"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ11"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ12"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ13"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ14"), + SOC_SINGLE("Low Latency Switch", NAU8360_R96_HW2_CTL6, + NAU8360_HW2_LATENCY_SFT, 1, 0), + SOC_SINGLE_EXT("ANC Path Switch", NAU8360_R96_HW2_CTL6, NAU8360_HW1_ANC_EN_SFT, + 1, 0, snd_soc_get_volsw, nau8360_anc_put), +}; + +static int nau8360_adci_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_ON(event)) + snd_soc_component_update_bits(component, NAU8360_R6C_IVSNS_CFG0, + NAU8360_PD_ISNS_R_PMD | NAU8360_PD_ISNS_L_PMD, 0); + else if (SND_SOC_DAPM_EVENT_OFF(event)) + snd_soc_component_update_bits(component, NAU8360_R6C_IVSNS_CFG0, + NAU8360_PD_ISNS_R_PMD | NAU8360_PD_ISNS_L_PMD, + NAU8360_PD_ISNS_R_PMD | NAU8360_PD_ISNS_L_PMD); + + return 0; +} + +static int nau8360_adcv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_ON(event)) + snd_soc_component_update_bits(component, NAU8360_R6C_IVSNS_CFG0, + NAU8360_PD_VSNS_R_PMD | NAU8360_PD_VSNS_L_PMD, 0); + else if (SND_SOC_DAPM_EVENT_OFF(event)) + snd_soc_component_update_bits(component, NAU8360_R6C_IVSNS_CFG0, + NAU8360_PD_VSNS_R_PMD | NAU8360_PD_VSNS_L_PMD, + NAU8360_PD_VSNS_R_PMD | NAU8360_PD_VSNS_L_PMD); + + return 0; +} + +static int nau8360_aif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_OFF(event)) + snd_soc_component_update_bits(component, NAU8360_R67_ANALOG_CONTROL_0, + NAU8360_HV_EN, 0); + + return 0; +} + +static int nau8360_hw1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_OFF(event)) + snd_soc_component_update_bits(component, NAU8360_R67_ANALOG_CONTROL_0, + NAU8360_ANA_MUTE, NAU8360_ANA_MUTE); + + return 0; +} + +static int nau8360_hw1_mux_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + snd_soc_component_update_bits(component, NAU8360_R68_ANALOG_CONTROL_1, + NAU8360_DRVCTL_SEGL_FULL | NAU8360_DRVCTL_SEGR_FULL, 0); + snd_soc_component_update_bits(component, NAU8360_RA5_ANA_REG_1, + NAU8360_CLASSD_SHT_IN | NAU8360_HVEN_SYNC_SAW, + NAU8360_CLASSD_SHT_IN | NAU8360_HVEN_SYNC_SAW); + msleep(20); + } + + return 0; +} + +static int nau8360_hw2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + snd_soc_component_update_bits(component, NAU8360_R99_HW2_CTL9, + NAU8360_HW2_CH_MUTE, 0); + snd_soc_component_update_bits(component, NAU8360_R9C_HW1_CTL2, + NAU8360_HW1_CH_MUTE, 0); + } + + return 0; +} + +static int nau8360_dac_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cp = snd_soc_dapm_to_component(w->dapm); + unsigned int mask = 1 << w->shift; + + if (SND_SOC_DAPM_EVENT_ON(event)) + snd_soc_component_update_bits(cp, NAU8360_R6E_DAC_CFG0, mask, 0); + else if (SND_SOC_DAPM_EVENT_OFF(event)) + snd_soc_component_update_bits(cp, NAU8360_R6E_DAC_CFG0, mask, mask); + + return 0; +} + +static int nau8360_hv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + /* enable Class D HV power after DAC power is stable */ + snd_soc_component_update_bits(component, NAU8360_R67_ANALOG_CONTROL_0, + NAU8360_HV_EN, NAU8360_HV_EN); + msleep(50); + /* Class D modulator input short setting for mute and de-pop purpose. + * Restore normal after initiation. Set segment driver as full driving + * strength. + */ + snd_soc_component_update_bits(component, NAU8360_RA5_ANA_REG_1, + NAU8360_CLASSD_SHT_IN | NAU8360_HVEN_SYNC_SAW, 0); + snd_soc_component_update_bits(component, NAU8360_R68_ANALOG_CONTROL_1, + NAU8360_DRVCTL_SEGL_FULL | NAU8360_DRVCTL_SEGR_FULL, + NAU8360_DRVCTL_SEGL_FULL | NAU8360_DRVCTL_SEGR_FULL); + + snd_soc_component_update_bits(component, NAU8360_R67_ANALOG_CONTROL_0, + NAU8360_ANA_MUTE, 0); + } else if (SND_SOC_DAPM_EVENT_OFF(event)) { + snd_soc_component_update_bits(component, NAU8360_R9C_HW1_CTL2, + NAU8360_HW1_CH_MUTE, NAU8360_HW1_CH_MUTE); + snd_soc_component_update_bits(component, NAU8360_R99_HW2_CTL9, + NAU8360_HW2_CH_MUTE, NAU8360_HW2_CH_MUTE); + } + + return 0; +} + +static inline void nau8360_dsp_enable(struct regmap *regmap, bool enable) +{ + regmap_update_bits(regmap, NAU8360_R86_HW3_CTL0, NAU8360_HW3_STALL, + enable ? 0 : NAU8360_HW3_STALL); + regmap_update_bits(regmap, NAU8360_R1A_DSP_CORE_CTRL2, NAU8360_DSP_RUNSTALL, + enable ? 0 : NAU8360_DSP_RUNSTALL); +} + +static void nau8360_dsp_switch(struct snd_soc_component *component, bool enable) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = nau8360->regmap; + int value = NAU8360_PEQ_BAND_8; + + /* If DSP is enabled, unstall HW3 engine and DSP, loading DSP firmware, + * and configure PEQ after dsp reset. + */ + if (enable) { + value = nau8360->anc_enable ? NAU8360_PEQ_BAND_15 : NAU8360_PEQ_BAND_12; + nau8360_dsp_enable(regmap, true); + } else { + dev_dbg(nau8360->dev, "Bypass DSP path"); + nau8360_dsp_enable(regmap, false); + } + regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_BAND_MASK, + value << NAU8360_PEQ_BAND_SFT); + +} + +static int nau8360_dac_mux_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol); + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm); + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + int ret = 0; + + if (snd_soc_dapm_get_bias_level(dapm) > SND_SOC_BIAS_STANDBY) { + dev_warn(nau8360->dev, "changing path is not allowed during playback"); + return ret; + } + + mutex_lock(&nau8360->lock); + + ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + if (ret <= 0) + goto unlock; + + nau8360_dsp_switch(component, snd_soc_enum_item_to_val(e, item[0])); + +unlock: + mutex_unlock(&nau8360->lock); + + return ret; +} + +/* Select HW1 output source (enables PEQ bypass) */ +static const char *const nau8360_hw1out_src[] = { "Audio", "PEQ" }; + +static SOC_ENUM_SINGLE_DECL(nau8360_hw1out_enum, NAU8360_R12_PATH_CTRL, + NAU8360_SEL_HW1_SFT, nau8360_hw1out_src); + +static const struct snd_kcontrol_new nau8360_hw1out_mux = + SOC_DAPM_ENUM("HW1 Output Source", nau8360_hw1out_enum); + +/* Select DAC input source (enables DSP bypass) */ +static const char *const nau8360_dac_src[] = { "HW1", "DSP" }; + +static SOC_ENUM_SINGLE_DECL(nau8360_dac_enum, NAU8360_R12_PATH_CTRL, + NAU8360_DAC_SEL_SFT, nau8360_dac_src); + +static const struct snd_kcontrol_new nau8360_dac_mux = + SOC_DAPM_ENUM_EXT("DAC Source", nau8360_dac_enum, + snd_soc_dapm_get_enum_double, nau8360_dac_mux_put_enum); + +static const struct snd_soc_dapm_widget nau8360_dapm_widgets[] = { + SND_SOC_DAPM_SIGGEN("Sense"), + SND_SOC_DAPM_ADC_E("ADC_I", NULL, SND_SOC_NOPM, 0, 0, nau8360_adci_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("ADC_V", NULL, SND_SOC_NOPM, 0, 0, nau8360_adcv_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA("DSP", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HW3 Engine", NAU8360_R8C_HW3_CTL6, 3, 0, NULL, 0), + + SND_SOC_DAPM_AIF_IN_E("AIFRX", "Playback", 0, SND_SOC_NOPM, 0, 0, + nau8360_aif_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("HW1 Engine", 0, NAU8360_R9D_PEQ_CTL, 0, 1, + nau8360_hw1_event, SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX_E("HW1 Mux", SND_SOC_NOPM, 0, 0, &nau8360_hw1out_mux, + nau8360_hw1_mux_event, SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, &nau8360_dac_mux), + + SND_SOC_DAPM_PGA_S("HW2 Engine", 1, NAU8360_R96_HW2_CTL6, 5, 0, + nau8360_hw2_event, SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_SUPPLY("DAC Clock", NAU8360_R71_CLK_DIV_CFG, 9, 1, NULL, 0), + SND_SOC_DAPM_DAC_E("DACL", NULL, SND_SOC_NOPM, NAU8360_PD_DACL_SFT, 0, + nau8360_dac_power_event, SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("DACR", NULL, SND_SOC_NOPM, NAU8360_PD_DACR_SFT, 0, + nau8360_dac_power_event, SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("ADACL", 2, SND_SOC_NOPM, NAU8360_PD_DACL_SFT, 0, + nau8360_dac_power_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("ADACR", 2, SND_SOC_NOPM, NAU8360_PD_DACR_SFT, 0, + nau8360_dac_power_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("Class D", 3, SND_SOC_NOPM, 0, 0, + nau8360_hv_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_OUTPUT("OUTL"), + SND_SOC_DAPM_OUTPUT("OUTR"), +}; + +static const struct snd_soc_dapm_route nau8360_dapm_routes[] = { + { "ADC_I", NULL, "Sense" }, + { "ADC_V", NULL, "Sense" }, + { "HW3 Engine", NULL, "ADC_I" }, + { "HW3 Engine", NULL, "ADC_V" }, + { "Capture", NULL, "HW3 Engine" }, + + { "HW1 Engine", NULL, "AIFRX" }, + { "HW1 Mux", "Audio", "AIFRX" }, + { "HW1 Mux", "PEQ", "HW1 Engine" }, + + { "DSP", NULL, "HW1 Mux" }, + { "DAC Mux", "HW1", "HW1 Mux" }, + { "DAC Mux", "DSP", "DSP" }, + + { "HW2 Engine", NULL, "DAC Mux" }, + { "DACL", NULL, "HW2 Engine" }, + { "DACR", NULL, "HW2 Engine" }, + { "DACL", NULL, "DAC Clock" }, + { "DACR", NULL, "DAC Clock" }, + + { "ADACL", NULL, "DACL" }, + { "ADACR", NULL, "DACR" }, + { "Class D", NULL, "ADACL" }, + { "Class D", NULL, "ADACR" }, + + { "OUTL", NULL, "Class D" }, + { "OUTR", NULL, "Class D" }, +}; + +static int nau8360_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + unsigned int i2s_mask = NAU8360_FRAME_START_MASK | NAU8360_RX_OFFSET_MASK; + unsigned int i2s_fmt = NAU8360_FRAME_START_H2L | NAU8360_RX_OFFSET_I2S; + int val = 0; + + flush_work(&nau8360->load_fw_work); + + if (nau8360_dsp_active(component) && !nau8360->load_fw_done) { + dev_warn(nau8360->dev, "DSP firmware is not ready yet!"); + return -EBUSY; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_read(nau8360->regmap, NAU8360_R0B_I2S_PCM_CTRL1, &val); + if ((val & i2s_mask) == i2s_fmt) + regmap_update_bits(nau8360->regmap, NAU8360_R0B_I2S_PCM_CTRL1, + NAU8360_EN_TDM_RX, NAU8360_EN_TDM_RX); + + if (nau8360_dsp_active(component)) + snd_soc_dapm_enable_pin(nau8360->dapm, "Sense"); + } + + return 0; +} + +static void nau8360_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + unsigned int tdm_mask; + + tdm_mask = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + NAU8360_EN_TDM_RX : NAU8360_EN_TDM_TX; + regmap_update_bits(nau8360->regmap, NAU8360_R0B_I2S_PCM_CTRL1, + tdm_mask, 0); + if (nau8360_dsp_active(component) && + substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dapm_disable_pin(nau8360->dapm, "Sense"); +} + +static int nau8360_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + unsigned int val_len, val_srate; + int dlen = params_width(params); + + if (dlen > nau8360_get_tdm_chan_len(nau8360)) { + dev_err(nau8360->dev, "Invalid data length"); + return -EINVAL; + } + + switch (dlen) { + case 16: + val_len = NAU8360_TDM_DLEN_16; + break; + case 20: + val_len = NAU8360_TDM_DLEN_20; + break; + case 24: + val_len = NAU8360_TDM_DLEN_24; + break; + case 32: + val_len = NAU8360_TDM_DLEN_32; + break; + default: + return -EINVAL; + } + + switch (params_rate(params)) { + case 16000: + val_srate = NAU8360_SRATE_16000; + break; + case 32000: + val_srate = NAU8360_SRATE_32000; + break; + case 44100: + case 48000: + val_srate = NAU8360_SRATE_48000; + break; + case 88200: + case 96000: + val_srate = NAU8360_SRATE_96000; + break; + case 176400: + case 192000: + val_srate = NAU8360_SRATE_192000; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8360->regmap, NAU8360_R0C_I2S_PCM_CTRL2, + NAU8360_TDM_DLEN_MASK, val_len); + regmap_update_bits(nau8360->regmap, NAU8360_R40_CLK_DET_CTRL, + NAU8360_SRATE_MASK, val_srate); + + return 0; +} + +static int nau8360_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + unsigned int ctrl_val, ctrl1_val; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl_val = NAU8360_FRAME_START_H2L | NAU8360_RX_OFFSET_I2S; + ctrl1_val = NAU8360_TX_OFFSET_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl_val = NAU8360_RX_OFFSET_LEFT | NAU8360_RX_LEFT_JUSTIFY; + ctrl1_val = NAU8360_TX_OFFSET_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl_val = NAU8360_RX_OFFSET_RIGHT | NAU8360_RX_RIGHT_JUSTIFY; + ctrl1_val = NAU8360_TX_OFFSET_RIGHT; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl_val = NAU8360_RX_OFFSET_PCM_A; + ctrl1_val = NAU8360_TX_OFFSET_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl_val = NAU8360_RX_OFFSET_PCM_B; + ctrl1_val = NAU8360_TX_OFFSET_PCM_B; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, NAU8360_R0B_I2S_PCM_CTRL1, + NAU8360_FRAME_START_MASK | NAU8360_RX_OFFSET_MASK | + NAU8360_RX_JUSTIFY_MASK, ctrl_val); + snd_soc_component_update_bits(component, NAU8360_R0D_I2S_PCM_CTRL3, + NAU8360_TX_OFFSET_MASK, ctrl1_val); + return 0; +} + +static void nau8360_set_tdm_rx_slot(struct snd_soc_component *cp, int type, int slot) +{ + switch (type) { + case NAU8360_TDM_DACL: + snd_soc_component_update_bits(cp, NAU8360_R0C_I2S_PCM_CTRL2, + NAU8360_RX_DACL_MASK, slot); + break; + + case NAU8360_TDM_DACR: + snd_soc_component_update_bits(cp, NAU8360_R0C_I2S_PCM_CTRL2, + NAU8360_RX_DACR_MASK, (slot << NAU8360_RX_DACR_SFT)); + break; + + case NAU8360_TDM_ANCL: + snd_soc_component_update_bits(cp, NAU8360_R10_I2S_DATA_CTRL3, + NAU8360_RX_ANC_L_MASK, (slot << NAU8360_RX_ANC_L_SFT)); + break; + + case NAU8360_TDM_ANCR: + snd_soc_component_update_bits(cp, NAU8360_R10_I2S_DATA_CTRL3, + NAU8360_RX_ANC_R_MASK, (slot << NAU8360_RX_ANC_R_SFT)); + break; + } +} + +static void nau8360_set_tdm_tx_slot(struct snd_soc_component *cp, int type, int slot) +{ + switch (type) { + case NAU8360_TDM_AECL: + snd_soc_component_update_bits(cp, NAU8360_R17_I2S0_DATA_CTRL5, + NAU8360_AEC_L_SLOT_MASK, slot << NAU8360_AEC_L_SLOT_SFT); + break; + + case NAU8360_TDM_AECR: + snd_soc_component_update_bits(cp, NAU8360_R17_I2S0_DATA_CTRL5, + NAU8360_AEC_R_SLOT_MASK, slot); + break; + + case NAU8360_TDM_ISNSL: + snd_soc_component_update_bits(cp, NAU8360_R0E_I2S_DATA_CTRL1, + NAU8360_ISNS_L_SLOT_MASK, slot << NAU8360_ISNS_L_SLOT_SFT); + break; + + case NAU8360_TDM_ISNSR: + snd_soc_component_update_bits(cp, NAU8360_R11_I2S_DATA_CTRL4, + NAU8360_ISNS_R_SLOT_MASK, slot); + break; + + case NAU8360_TDM_VSNSL: + snd_soc_component_update_bits(cp, NAU8360_R0D_I2S_PCM_CTRL3, + NAU8360_VSNS_L_SLOT_MASK, slot); + break; + + case NAU8360_TDM_VSNSR: + snd_soc_component_update_bits(cp, NAU8360_R11_I2S_DATA_CTRL4, + NAU8360_VSNS_R_SLOT_MASK, slot << NAU8360_VSNS_R_SLOT_SFT); + break; + + case NAU8360_TDM_TJ: + snd_soc_component_update_bits(cp, NAU8360_R10_I2S_DATA_CTRL3, + NAU8360_TEMP_SLOT_MASK, slot); + break; + + case NAU8360_TDM_VBAT: + snd_soc_component_update_bits(cp, NAU8360_R0F_I2S_DATA_CTRL2, + NAU8360_VBAT_SLOT_MASK, slot << NAU8360_VBAT_SLOT_SFT); + break; + } +} + +static void nau8360_set_tdm_tx_func(struct snd_soc_component *cp, int type, bool enable) +{ + switch (type) { + case NAU8360_TDM_AECL: + snd_soc_component_update_bits(cp, NAU8360_R17_I2S0_DATA_CTRL5, + NAU8360_AEC_L_EN, enable ? NAU8360_AEC_L_EN : 0); + break; + case NAU8360_TDM_AECR: + snd_soc_component_update_bits(cp, NAU8360_R17_I2S0_DATA_CTRL5, + NAU8360_AEC_R_EN, enable ? NAU8360_AEC_R_EN : 0); + break; + case NAU8360_TDM_ISNSL: + snd_soc_component_update_bits(cp, NAU8360_R0E_I2S_DATA_CTRL1, + NAU8360_ISNS_L_TX_EN, enable ? NAU8360_ISNS_L_TX_EN : 0); + break; + case NAU8360_TDM_ISNSR: + snd_soc_component_update_bits(cp, NAU8360_R11_I2S_DATA_CTRL4, + NAU8360_ISNS_R_TX_EN, enable ? NAU8360_ISNS_R_TX_EN : 0); + break; + case NAU8360_TDM_VSNSL: + snd_soc_component_update_bits(cp, NAU8360_R0D_I2S_PCM_CTRL3, + NAU8360_VSNS_L_TX_EN, enable ? NAU8360_VSNS_L_TX_EN : 0); + break; + case NAU8360_TDM_VSNSR: + snd_soc_component_update_bits(cp, NAU8360_R11_I2S_DATA_CTRL4, + NAU8360_VSNS_R_TX_EN, enable ? NAU8360_VSNS_R_TX_EN : 0); + break; + case NAU8360_TDM_TJ: + snd_soc_component_update_bits(cp, NAU8360_R10_I2S_DATA_CTRL3, + NAU8360_TEMP_TX_EN, enable ? NAU8360_TEMP_TX_EN : 0); + break; + case NAU8360_TDM_VBAT: + snd_soc_component_update_bits(cp, NAU8360_R0F_I2S_DATA_CTRL2, + NAU8360_VBAT_TX_EN, enable ? NAU8360_VBAT_TX_EN : 0); + break; + } +} + +static int nau8360_validate_tdm_slots(struct device *dev, unsigned int mask, + const u32 *func_slots, const char * const *func_names, + int num_funcs, const char *dir, + unsigned int *slot_used) +{ + int i; + unsigned int func_slot; + *slot_used = 0; + + if (!mask) + return 0; + + for (i = 0; i < num_funcs; i++) { + func_slot = func_slots[i]; + if (func_slot == TDM_SLOT_NONE) { + dev_warn(dev, "%s %s slot disabled", + dir, func_names[i]); + continue; + } + + if (!(mask & BIT(func_slot))) { + dev_warn(dev, "%s %s mapped to slot %d, but disabled by mask!", + dir, func_names[i], func_slot); + continue; + } + + if (*slot_used & BIT(func_slot)) { + dev_err(dev, "%s %s slot %d collision!", + dir, func_names[i], func_slot); + return -EINVAL; + } + + *slot_used |= BIT(func_slot); + } + + return 0; +} + +static void nau8360_enable_tdm_channels(struct snd_soc_component *cp, + int rx_slot_used, int tx_slot_used) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + int i, slot; + unsigned int val = 0; + bool enable; + + for (i = 0; i < NAU8360_TDM_TXN; i++) { + slot = nau8360->tdm_tx_func_slot[i]; + enable = (slot != TDM_SLOT_NONE) && (tx_slot_used & BIT(slot)); + nau8360_set_tdm_tx_func(cp, i, enable); + } + + if (rx_slot_used) + val |= NAU8360_EN_TDM_RX; + if (tx_slot_used) + val |= NAU8360_EN_TDM_TX; + + snd_soc_component_update_bits(cp, NAU8360_R0B_I2S_PCM_CTRL1, + NAU8360_EN_TDM_RX | NAU8360_EN_TDM_TX, val); +} + +static void nau8360_tdm_apply(struct snd_soc_component *cp, int slot_width) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + int i, chan_tx; + + for (i = 0; i < NAU8360_TDM_TXN; i++) { + if (nau8360->tdm_tx_func_slot[i] == TDM_SLOT_NONE) + continue; + + /* compute the slot location in bytes according to slot/chan width */ + chan_tx = (slot_width >> 3) * nau8360->tdm_tx_func_slot[i]; + nau8360_set_tdm_tx_slot(cp, i, chan_tx); + } + + regmap_update_bits(nau8360->regmap, NAU8360_R0C_I2S_PCM_CTRL2, + NAU8360_TDM_CLEN_MASK, + ((slot_width - 16) >> 3) << NAU8360_TDM_CLEN_SFT); +} + +static int nau8360_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *cp = dai->component; + struct device *dev = cp->dev; + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + unsigned int tx_slot_used = 0, rx_slot_used = 0; + int ret = 0; + + if (!slots || !slot_width) { + nau8360_enable_tdm_channels(cp, 0, 0); + return ret; + } + + if (slots > NAU8360_TDM_MAX_CHAN) { + dev_err(dev, "Invalid TDM slots: %d", slots); + return -EINVAL; + } + + if (slot_width != 16 && slot_width != 24 && slot_width != 32) { + dev_err(dev, "Invalid TDM channel length: %d", slot_width); + return -EINVAL; + } + + ret = nau8360_validate_tdm_slots(cp->dev, rx_mask, + nau8360->tdm_rx_func_slot, + nau8360_rx_func_names, + NAU8360_TDM_RXN, "RX", + &rx_slot_used); + if (ret < 0) + goto err; + + ret = nau8360_validate_tdm_slots(cp->dev, tx_mask, + nau8360->tdm_tx_func_slot, + nau8360_tx_func_names, + NAU8360_TDM_TXN, "TX", + &tx_slot_used); + if (ret < 0) + goto err; + + if (nau8360_get_tdm_chan_len(nau8360) != slot_width) + nau8360_tdm_apply(cp, slot_width); + + nau8360_enable_tdm_channels(cp, rx_slot_used, tx_slot_used); + + dev_dbg(dev, "TDM: tx_mask 0x%x, rx_mask 0x%x", tx_mask, rx_mask); +err: + return ret; +} + +static inline int dyn_clk_div(int div_max, int fin, int fout) +{ + int clk_div; + + if (!fin || !fout) + return -EINVAL; + + for (clk_div = 0; clk_div <= div_max; clk_div++) + if (fin / (clk_div + 1) <= fout) + break; + if (clk_div > div_max) + return -EINVAL; + + return clk_div; +} + +static inline int tab_clk_div(const int clk_div[], int num_div, int fin) +{ + int i; + + if (!fin) + return -EINVAL; + + for (i = 0; i < num_div; i++) + if ((fin / clk_div[i]) <= CLK_DA_IVSNS_MAX) + break; + if (i == num_div) + return -EINVAL; + + return i; +} + +static int nau8360_set_sysclk_output(struct nau8360 *nau8360, unsigned int freq) +{ + struct device *dev = nau8360->dev; + int ratio = 0, mclk_rate; + + if (freq < MASTER_CLK_MIN || freq > MASTER_CLK_MAX) { + dev_err(dev, "system clock %d Hz exceed range", freq); + return -EINVAL; + } + + if (freq % ADSP_SR_48000 == 0) + ratio = freq / ADSP_SR_48000; + else if (freq % ADSP_SR_44100 == 0) + ratio = freq / ADSP_SR_44100; + + switch (ratio) { + case NAU8360_MCLK_FS_RATIO_250: + mclk_rate = NAU8360_MCLK_RATE_12000; + break; + case NAU8360_MCLK_FS_RATIO_256: + mclk_rate = NAU8360_MCLK_RATE_12288; + break; + case NAU8360_MCLK_FS_RATIO_400: + mclk_rate = NAU8360_MCLK_RATE_19200; + break; + case NAU8360_MCLK_FS_RATIO_500: + mclk_rate = NAU8360_MCLK_RATE_24000; + break; + case NAU8360_MCLK_FS_RATIO_512: + mclk_rate = NAU8360_MCLK_RATE_24576; + break; + default: + dev_err(dev, "invalid sysclk %d", freq); + return -EINVAL; + } + + dev_dbg(dev, "sysclk %d Hz, ratio %d", freq, ratio); + + nau8360->sys_clk = freq; + regmap_update_bits(nau8360->regmap, NAU8360_R40_CLK_DET_CTRL, + NAU8360_MCLK_RATE_MASK, mclk_rate); + + return 0; +} + +static int nau8360_dig_sys_clk(struct nau8360 *nau8360, int source, unsigned int freq) +{ + struct device *dev = nau8360->dev; + struct regmap *regmap = nau8360->regmap; + int value, mclk_div; + + switch (source) { + case NAU8360_CLK_SRC_MCLK: + value = NAU8360_MCLK_SEL_MCLK; + break; + + case NAU8360_CLK_SRC_PLL: + value = NAU8360_MCLK_SEL_PLL; + break; + + default: + return -EINVAL; + } + + regmap_update_bits(regmap, NAU8360_R04_CLK_CTRL1, NAU8360_MCLK_SEL_MASK, value); + + mclk_div = dyn_clk_div(NAU8360_MCLK_DIV_MAX, freq, nau8360->sys_clk); + if (mclk_div < 0) { + dev_err(dev, "mclk_div (%d -> %d): error", freq, nau8360->sys_clk); + return -EINVAL; + } + regmap_update_bits(regmap, NAU8360_R03_CLK_CTRL0, NAU8360_MCLK_DIV_MASK, + mclk_div << NAU8360_MCLK_DIV_SFT); + + dev_dbg(dev, " mclk_div (%d -> %d): %d", freq, nau8360->sys_clk, mclk_div + 1); + + return 0; +} + +static int nau8360_ana_sys_clk(struct nau8360 *nau8360, int source, unsigned int freq) +{ + struct device *dev = nau8360->dev; + struct regmap *regmap = nau8360->regmap; + struct nau8360_pll *pll = &nau8360->pll; + int value, pclk_div, ivdiv_sel, ddiv_sel; + + switch (source) { + case NAU8360_CLK_SRC_PLL: + value = NAU8360_CLK_ANA_SEL_PLL; + break; + case NAU8360_CLK_SRC_MCLK: + value = NAU8360_CLK_ANA_SEL_MCLK; + break; + case NAU8360_CLK_SRC_BCLK: + value = NAU8360_CLK_ANA_SEL_BCLK; + break; + default: + return -EINVAL; + } + + if (source == NAU8360_CLK_SRC_PLL) { + freq = pll->output; + pclk_div = dyn_clk_div(NAU8360_PLLOUT_DIV_MAX, freq, nau8360->sys_clk); + if (pclk_div < 0) { + dev_err(dev, "pll_div (%d -> %d): error", freq, nau8360->sys_clk); + return -EINVAL; + } + + dev_dbg(dev, " pll_div (%d -> %d): %d", freq, nau8360->sys_clk, pclk_div + 1); + + regmap_update_bits(regmap, NAU8360_R72_PLL_CFG0, NAU8360_PLLOUT_DIV_MASK, + pclk_div << NAU8360_PLLOUT_DIV_SFT); + freq /= (pclk_div + 1); + } + + ivdiv_sel = tab_clk_div(ivsns_clk_div, ARRAY_SIZE(ivsns_clk_div), freq); + if (ivdiv_sel < 0) { + dev_err(dev, "iv_div (%d -> %d): error", freq, CLK_DA_IVSNS_MAX); + return -EINVAL; + } + value |= ivdiv_sel << NAU8360_IVSNS_CLK_DIV_SFT; + + ddiv_sel = tab_clk_div(dac_clk_div, ARRAY_SIZE(dac_clk_div), freq); + if (ddiv_sel < 0) { + dev_err(dev, "dac_div (%d -> %d): error", freq, CLK_DA_IVSNS_MAX); + return -EINVAL; + } + value |= ddiv_sel << NAU8360_DAC_CLK_DIV_SFT; + + dev_dbg(dev, " clk_div (%d -> %d): ivsns %d, dac %d", freq, CLK_DA_IVSNS_MAX, + ivsns_clk_div[ivdiv_sel], dac_clk_div[ddiv_sel]); + + regmap_update_bits(regmap, NAU8360_R71_CLK_DIV_CFG, NAU8360_CLK_ANA_SEL_MASK | + NAU8360_IVSNS_CLK_DIV_MASK | NAU8360_DAC_CLK_DIV_MASK, value); + + return 0; +} + +static int nau8360_dsp_hw_clk(struct nau8360 *nau8360, int source, unsigned int freq) +{ + struct device *dev = nau8360->dev; + struct regmap *regmap = nau8360->regmap; + int val_dsp, val_hw, clk_div, dsp_clk; + + if (freq % ADSP_SR_48000 == 0) + dsp_clk = DSP_OP_CLK48; + else if (freq % ADSP_SR_44100 == 0) + dsp_clk = DSP_OP_CLK44; + else + return -EINVAL; + clk_div = dyn_clk_div(NAU8360_DSP_CLK_DIV_MAX, freq, dsp_clk); + if (clk_div < 0) { + dev_err(dev, "dsp/hw clk_div (%d -> %d): error", freq, dsp_clk); + return -EINVAL; + } + val_dsp = clk_div << NAU8360_DSP_CLK_DIV_SFT; + val_hw = clk_div << NAU8360_HW_CLK_DIV_SFT; + if (source == NAU8360_CLK_SRC_MCLK) { + val_dsp |= NAU8360_DSP_CLK_SEL_MCLK; + val_hw |= NAU8360_HW_CLK_SEL_MCLK; + } else if (source == NAU8360_CLK_SRC_PLL) { + val_dsp |= NAU8360_DSP_CLK_SEL_PLL; + val_hw |= NAU8360_HW_CLK_SEL_PLL; + } else + return -EINVAL; + + dev_dbg(dev, " dsp/hw clk_div (%d -> %d): %d", freq, dsp_clk, clk_div + 1); + + regmap_update_bits(regmap, NAU8360_R03_CLK_CTRL0, NAU8360_DSP_CLK_SEL_MASK | + NAU8360_DSP_CLK_DIV_MASK, val_dsp); + regmap_update_bits(regmap, NAU8360_R04_CLK_CTRL1, NAU8360_HW_CLK_SEL_MASK | + NAU8360_HW_CLK_DIV_MASK, val_hw); + + return 0; +} + +static int nau8360_set_sysclk(struct snd_soc_component *cp, + int clk_id, int source, unsigned int freq, int dir) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct regmap *regmap = nau8360->regmap; + struct device *dev = nau8360->dev; + static const char * const idtab[] = { "DIG", "ANA", "Internal" }; + static const char * const srctab[] = { "MCLK", "PLL", "HIRC48M", "BCLK" }; + int ret; + + if (clk_id < 0 || clk_id >= ARRAY_SIZE(idtab)) + return -EINVAL; + + if (source < 0 || source >= ARRAY_SIZE(srctab)) + return -EINVAL; + + if (dir == SND_SOC_CLOCK_OUT) { + dev_dbg(dev, "sysclk: freq %d (out)", freq); + return nau8360_set_sysclk_output(nau8360, freq); + } + + switch (clk_id) { + case NAU8360_CLK_ID_INT: + source = NAU8360_CLK_SRC_ICLK; + dev_dbg(dev, "sysclk: id %d (%s), src %d (%s) (in)", + clk_id, idtab[clk_id], source, srctab[source]); + regmap_update_bits(regmap, NAU8360_R03_CLK_CTRL0, + NAU8360_DSP_CLK_SEL_MASK, NAU8360_DSP_CLK_SEL_HIRC48M); + regmap_update_bits(regmap, NAU8360_R04_CLK_CTRL1, + NAU8360_HW_CLK_SEL_MASK | NAU8360_MCLK_SEL_MASK, + NAU8360_HW_CLK_SEL_HIRC48M | NAU8360_MCLK_SEL_HIRC48M); + regmap_update_bits(regmap, NAU8360_R72_PLL_CFG0, + NAU8360_PD_PLL_MASK, NAU8360_PD_PLL_DIS); + break; + + case NAU8360_CLK_ID_DIG: + dev_dbg(dev, "sysclk: id %d (%s), src %d (%s), freq %d (in)", + clk_id, idtab[clk_id], source, srctab[source], freq); + + if (source == NAU8360_CLK_SRC_BCLK) + return -EINVAL; + + if (source == NAU8360_CLK_SRC_MCLK) + regmap_update_bits(regmap, NAU8360_R72_PLL_CFG0, + NAU8360_PD_PLL_MASK, NAU8360_PD_PLL_DIS); + + ret = nau8360_dig_sys_clk(nau8360, source, freq); + if (ret) + return -EINVAL; + ret = nau8360_dsp_hw_clk(nau8360, source, freq); + if (ret) + return -EINVAL; + break; + + case NAU8360_CLK_ID_ANA: + dev_dbg(dev, "sysclk: id %d (%s), src %d (%s), freq %d (in)", + clk_id, idtab[clk_id], source, srctab[source], freq); + + if (source == NAU8360_CLK_SRC_MCLK || source == NAU8360_CLK_SRC_BCLK) + regmap_update_bits(regmap, NAU8360_R72_PLL_CFG0, + NAU8360_PD_PLL_MASK, NAU8360_PD_PLL_DIS); + + ret = nau8360_ana_sys_clk(nau8360, source, freq); + if (ret) + return -EINVAL; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int nau8360_calc_pll(struct nau8360 *nau8360) +{ + struct nau8360_pll *pll = &nau8360->pll; + u64 fref = 0ULL, fvco = 0ULL, ratio; + int fr, fv; + + if (pll->src != NAU8360_PLL_INTERNAL && + (pll->input > PLL_FREQ_MAX || pll->input < PLL_FREQ_MIN)) + return -EINVAL; + + if (pll->output > PLL_FOUT_MAX || pll->output < PLL_FOUT_MIN) + return -EINVAL; + + for (pll->msel = 1; pll->msel <= MSEL_MAX; pll->msel++) { + fr = pll->input / pll->msel; + if (fr <= PLL_FREF_MAX) { + fref = fr; + break; + } + } + if (!fref) + return -ERANGE; + + for (pll->rsel = 1; pll->rsel <= RSEL_MAX; pll->rsel++) { + fv = pll->output * pll->rsel; + if (fv >= PLL_FVCO_MIN) { + fvco = fv; + break; + } + } + if (!fvco) + return -ERANGE; + + dev_dbg(nau8360->dev, "fref(1-8MHz): %lld, fvco(50-125MHz): %lld", fref, fvco); + + /* Calculate the PLL 8-bit integer input and 12-bit fractional */ + ratio = div_u64(fvco << 12, fref); + pll->nsel = (ratio >> 12) & 0xff; + pll->xsel = ratio & 0xfff; + + return 0; +} + +static int nau8360_set_pll(struct snd_soc_component *cp, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct device *dev = nau8360->dev; + struct nau8360_pll *pll = &nau8360->pll; + int ctrl_val, ret; + + switch (source) { + case NAU8360_PLL_MCLK: + ctrl_val = NAU8360_PLL_CLK_SEL_MCLK; + break; + case NAU8360_PLL_BCLK: + ctrl_val = NAU8360_PLL_CLK_SEL_BCLK; + break; + case NAU8360_PLL_INTERNAL: + ctrl_val = NAU8360_PLL_CLK_SEL_HIRC; + break; + default: + return -EINVAL; + } + pll->src = source; + pll->input = freq_in; + pll->output = freq_out; + ret = nau8360_calc_pll(nau8360); + if (ret) { + dev_err(dev, "clock error input %d output %d", freq_in, freq_out); + return ret; + } + dev_dbg(dev, "src:%d, input:%d, output:%d, M:%d, R:%d, N:%d, X:%d", pll->src, + pll->input, pll->output, pll->msel, pll->rsel, pll->nsel, pll->xsel); + + regmap_update_bits(nau8360->regmap, NAU8360_R72_PLL_CFG0, NAU8360_PD_PLL_MASK | + NAU8360_PLL_CLK_SEL_MASK, NAU8360_PD_PLL_EN | ctrl_val); + regmap_write_bits(nau8360->regmap, NAU8360_R73_PLL_CFG1, + NAU8360_RSEL_MASK | NAU8360_MSEL_MASK | NAU8360_NSEL_MASK, + (pll->rsel - 1) << NAU8360_RSEL_SFT | + (pll->msel - 1) << NAU8360_MSEL_SFT | (pll->nsel - 1)); + regmap_write_bits(nau8360->regmap, NAU8360_R74_PLL_CFG2, NAU8360_XSEL_MASK, + pll->xsel); + + return 0; +} + +static void nau8360_coeff_set_def(struct nau8360 *nau8360) +{ + struct regmap *regmap = nau8360->regmap; + int i; + + mutex_lock(&nau8360->lock); + regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_HW1_MEM_CLEAR, + NAU8360_HW1_MEM_CLEAR); + regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_HW1_MEM_CLEAR, 0); + nau8360_peq_mem_enable(regmap, true); + for (i = 0; i < NAU8360_TOT_BAND_PER_CH; i++) { + regmap_write(regmap, NAU8360_R100_LEFT_BIQ0_COE + 1 + + i * NAU8360_TOT_BAND_COE_RANGE, 0x20); + regmap_write(regmap, NAU8360_R200_RIGHT_BIQ0_COE + 1 + + i * NAU8360_TOT_BAND_COE_RANGE, 0x20); + } + nau8360_peq_mem_enable(regmap, false); + mutex_unlock(&nau8360->lock); +} + +static inline int nau8360_vbat_level(struct regmap *regmap, int *vbat) +{ + struct device *dev = regmap_get_device(regmap); + int value = 0, ret; + + ret = regmap_read(regmap, NAU8360_R21_VBAT_READOUT, &value); + if (ret) + return ret; + + /* multiple 100 on value scale */ + *vbat = (value * 100 + NAU8360_VBAT_BASE) / NAU8360_VBAT_STEP; + if (*vbat < NAU8360_VBAT_MIN || *vbat > NAU8360_VBAT_MAX) { + dev_err(dev, "VBAT %dV is out of valid range (%dV-%dV)", + *vbat, NAU8360_VBAT_MIN, NAU8360_VBAT_MAX); + return -ERANGE; + } + + return 0; +} + +static inline void nau8360_sawtooth_params(int vbat, int *vsaw_level, int *vsaw_slope) +{ + if (vbat < NAU8360_VBAT_MID_THRES) { + *vsaw_level = 0x1; + *vsaw_slope = 0x0; + } else if (vbat < NAU8360_VBAT_HIGH_THRES) { + *vsaw_level = 0x2; + *vsaw_slope = 0x1; + } else { + *vsaw_level = 0x3; + *vsaw_slope = 0x2; + } +} + +static inline void nau8360_dsp_software_reset(struct snd_soc_component *component) +{ + /* Enable PLL for successful DSP reset. After DSP is alive, + * system clock switches to internal clock and disable PLL. + */ + snd_soc_component_update_bits(component, NAU8360_R72_PLL_CFG0, + NAU8360_PD_PLL_MASK, NAU8360_PD_PLL_EN); + msleep(50); + snd_soc_component_write(component, NAU8360_R01_DSP_SOFTWARE_RST, 0x5a5a); + snd_soc_component_write(component, NAU8360_R01_DSP_SOFTWARE_RST, 0xa5a5); + snd_soc_component_set_sysclk(component, NAU8360_CLK_ID_INT, 0, 0, + SND_SOC_CLOCK_IN); +} + +static void nau8360_dsp_bootup(struct snd_soc_component *component) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = nau8360->regmap; + + regmap_update_bits(regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_STALL, 0); + nau8360_dsp_software_reset(component); + nau8360_dsp_enable(regmap, true); +} + +static void nau8360_tdm_function_config(struct snd_soc_component *cp) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + int i, chan_tx, tdm_chan_len; + + for (i = 0; i < NAU8360_TDM_RXN; i++) { + if (nau8360->tdm_rx_func_slot[i] == TDM_SLOT_NONE) + continue; + + nau8360_set_tdm_rx_slot(cp, i, nau8360->tdm_rx_func_slot[i]); + } + + tdm_chan_len = nau8360_get_tdm_chan_len(nau8360) >> 3; + for (i = 0; i < NAU8360_TDM_TXN; i++) { + if (nau8360->tdm_tx_func_slot[i] == TDM_SLOT_NONE) + continue; + + /* compute the slot location in bytes according to slot/chan width */ + chan_tx = (tdm_chan_len * nau8360->tdm_tx_func_slot[i]); + nau8360_set_tdm_tx_slot(cp, i, chan_tx); + } +} + +static void nau8360_dsp_fw_load(struct nau8360 *nau8360) +{ + nau8360->load_fw_done = false; + schedule_work(&nau8360->load_fw_work); +} + +static void nau8360_load_fw_work(struct work_struct *work) +{ + struct nau8360 *nau8360 = container_of(work, struct nau8360, load_fw_work); + struct snd_soc_component *cp = snd_soc_dapm_to_component(nau8360->dapm); + int ret; + + ret = nau8360_dsp_init(cp); + if (ret) { + dev_err(nau8360->dev, "Failed to initialize DSP: %d\n", ret); + nau8360_dsp_enable(nau8360->regmap, false); + return; + } + nau8360->load_fw_done = true; +} + +static int nau8360_codec_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = nau8360->regmap; + struct device *dev = nau8360->dev; + int ret, vbat, vsaw_level, vsaw_slope; + + nau8360->dapm = dapm; + nau8360_dsp_bootup(component); + nau8360_dsp_fw_load(nau8360); + ret = nau8360_dsp_setup_controls(component); + if (ret) { + nau8360_dsp_enable(regmap, false); + dev_err(dev, "DSP setup controls failed(%d)", ret); + goto err; + } + nau8360_tdm_function_config(component); + + /* default disable Sense signal after booting */ + snd_soc_dapm_disable_pin(nau8360->dapm, "Sense"); + snd_soc_dapm_sync(nau8360->dapm); + + nau8360_coeff_set_def(nau8360); + /* VBAT is sensed by chip. */ + ret = nau8360_vbat_level(regmap, &vbat); + if (ret) { + dev_err(dev, "Failed to get valid VBAT level: %d", ret); + goto err; + } + dev_dbg(dev, "VBAT %dV for nau8360", vbat); + + /* Config sawtooth clock according to VBAT. Class D modulator input short setting + * for mute and de-pop purpose. Restore normal after initiation. + */ + nau8360_sawtooth_params(vbat, &vsaw_level, &vsaw_slope); + + regmap_update_bits(regmap, NAU8360_RA5_ANA_REG_1, NAU8360_VSAW_LV_MASK | + NAU8360_KVCO_SAW_MASK, (vsaw_level << NAU8360_VSAW_LV_SFT) | + (vsaw_slope << NAU8360_KVCO_SAW_SFT)); + + return 0; + +err: + cancel_work_sync(&nau8360->load_fw_work); + return ret; +} + +static int __maybe_unused nau8360_suspend(struct snd_soc_component *component) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + + cancel_work_sync(&nau8360->load_fw_work); + + regmap_update_bits(nau8360->regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_STALL, + NAU8360_HW2_STALL); + nau8360_dsp_enable(nau8360->regmap, false); + + regcache_cache_only(nau8360->regmap, true); + regcache_mark_dirty(nau8360->regmap); + + return 0; +} + +static int __maybe_unused nau8360_resume(struct snd_soc_component *component) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = nau8360->regmap; + + regcache_cache_only(regmap, false); + + nau8360_dsp_bootup(component); + + nau8360_peq_mem_enable(regmap, true); + regcache_sync(regmap); + nau8360_peq_mem_enable(regmap, false); + + /* disable Sense at standby */ + snd_soc_dapm_disable_pin(nau8360->dapm, "Sense"); + snd_soc_dapm_sync(nau8360->dapm); + + nau8360_dsp_fw_load(nau8360); + + return 0; +} + +static const struct snd_soc_component_driver soc_comp_dev_nau8360 = { + .probe = nau8360_codec_probe, + .set_sysclk = nau8360_set_sysclk, + .set_pll = nau8360_set_pll, + .suspend = nau8360_suspend, + .resume = nau8360_resume, + .controls = nau8360_snd_controls, + .num_controls = ARRAY_SIZE(nau8360_snd_controls), + .dapm_widgets = nau8360_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(nau8360_dapm_widgets), + .dapm_routes = nau8360_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(nau8360_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, +}; + +static const struct snd_soc_dai_ops nau8360_dai_ops = { + .startup = nau8360_startup, + .shutdown = nau8360_shutdown, + .hw_params = nau8360_hw_params, + .set_fmt = nau8360_set_fmt, + .set_tdm_slot = nau8360_set_tdm_slot, +}; + +#define NAU8360_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) + +#define NAU8360_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver nau8360_dai = { + .name = NAU8360_CODEC_DAI, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 4, + .rates = NAU8360_RATES, + .formats = NAU8360_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 8, + .rates = NAU8360_RATES, + .formats = NAU8360_FORMATS, + }, + .ops = &nau8360_dai_ops, + .symmetric_rate = 1, +}; + +static int nau8360_reg_write(void *context, unsigned int reg, unsigned int value) +{ + struct i2c_client *client = context; + struct nau8360 *nau8360 = i2c_get_clientdata(client); + int ret, count = 0; + + put_unaligned_be16(reg, &nau8360->i2c_write_buf[count]); + count += 2; + + if (NAU8360_IS_DSP_REG(reg)) { + /* format for DSP, 4 bytes value and native */ + put_unaligned_le32(value, &nau8360->i2c_write_buf[count]); + count += 4; + } else { + /* format for Codec, 2 bytes value and endian big */ + put_unaligned_be16(value, &nau8360->i2c_write_buf[count]); + count += 2; + } + + ret = i2c_master_send(client, nau8360->i2c_write_buf, count); + + if (ret == count) + return 0; + if (ret < 0) + return ret; + + return -EIO; +} + +static int nau8360_reg_read(void *context, unsigned int reg, unsigned int *value) +{ + struct i2c_client *client = context; + struct nau8360 *nau8360 = i2c_get_clientdata(client); + struct i2c_msg xfer[2]; + int ret; + + put_unaligned_be16(reg, &nau8360->i2c_read_reg); + xfer[0].addr = client->addr; + xfer[0].len = 2; + xfer[0].buf = nau8360->i2c_read_reg; + xfer[0].flags = 0; + + xfer[1].addr = client->addr; + xfer[1].len = (NAU8360_IS_DSP_REG(reg)) ? 4 : 2; + xfer[1].buf = nau8360->i2c_read_buf; + xfer[1].flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, xfer, ARRAY_SIZE(xfer)); + if (ret < 0) + return ret; + if (ret != ARRAY_SIZE(xfer)) + return -EIO; + + if (NAU8360_IS_DSP_REG(reg)) + *value = get_unaligned_le32(nau8360->i2c_read_buf); + else + *value = get_unaligned_be16(nau8360->i2c_read_buf); + + return 0; +} + +static const struct regmap_config nau8360_regmap_config = { + .reg_bits = NAU8360_REG_ADDR_LEN, + .val_bits = NAU8360_REG_DATA_LEN, + + .max_register = NAU8360_REG_MAX, + .readable_reg = nau8360_readable_reg, + .writeable_reg = nau8360_writeable_reg, + .volatile_reg = nau8360_volatile_reg, + .reg_read = nau8360_reg_read, + .reg_write = nau8360_reg_write, + + .cache_type = REGCACHE_MAPLE, + .reg_defaults = nau8360_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(nau8360_reg_defaults), +}; + +static inline void nau8360_reset_chip(struct regmap *regmap) +{ + regmap_write(regmap, NAU8360_R00_SOFTWARE_RST, 0x5a5a); + regmap_write(regmap, NAU8360_R00_SOFTWARE_RST, 0xa5a5); +} + +static void nau8360_init_regs(struct nau8360 *nau8360) +{ + struct regmap *regmap = nau8360->regmap; + + /* Enable Digital LDO */ + regmap_write(regmap, NAU8360_R78_PD_SW_DLDO, NAU8360_PD_SW_DLDO_EN); + /* Enable Software Shutdown Mode */ + regmap_write(regmap, NAU8360_R77_SOFT_SD, NAU8360_SOFT_SD_EN); + /* Stall HW1 PEQ Engine and Clear DRAM to Zero */ + regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_STALL, + NAU8360_PEQ_STALL); + /* Stall HW2 engine */ + regmap_update_bits(regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_STALL, + NAU8360_HW2_STALL); + /* Stall HW3 engine and DSP processor */ + nau8360_dsp_enable(regmap, false); + /* Enable Clock Gate */ + regmap_write(regmap, NAU8360_R7E_CLK_GATED_EN, NAU8360_CLK_GATED_EN); + /* Latch I2C LSB Value */ + regmap_write(regmap, NAU8360_R02_I2C_ADDR, 0x0001); + /* ANC Left/Right Channel as Slot 2/3 and switch */ + regmap_update_bits(regmap, NAU8360_R10_I2S_DATA_CTRL3, + NAU8360_RX_ANC_R_MASK | NAU8360_RX_ANC_L_MASK, + 0x3 << NAU8360_RX_ANC_R_SFT | 0x2 << NAU8360_RX_ANC_L_SFT); + /* Set DAC Clock Divider as 2 and Chopper Divider as 16 */ + regmap_update_bits(regmap, NAU8360_R71_CLK_DIV_CFG, + NAU8360_DAC_CLK_DIV_MASK | NAU8360_DAC_CHOP_CLK_DIV_MASK, + NAU8360_DAC_CLK_DIV_2 | NAU8360_DAC_CHOP_CLK_DIV_16); + /* Set DAC SINC OSR as 128 and IVSENSE BS OSR as 32 */ + regmap_update_bits(regmap, NAU8360_R5D_SINC_CFG, + NAU8360_DAC_SINC_OSR_MASK | NAU8360_IVSENSE_BS_OSR_MASK, + NAU8360_DAC_SINC_OSR_128 | NAU8360_IVSENSE_BS_OSR_32); + + /* Set Trim Bit Control of DLDO and GVDD to The Highest Voltage */ + regmap_write(regmap, NAU8360_R5F_ANA_TRIM_CFG1, 0xf407); + + /* Disable Data Det and Clock Detection Settings*/ + regmap_update_bits(regmap, NAU8360_R40_CLK_DET_CTRL, NAU8360_APWRUPEN | + NAU8360_CLKPWRUPEN | NAU8360_FS_MCLK_DET | NAU8360_FS_BCLK_DET, 0); + + /* Set HW3 Droop and Filter Sample as 192K */ + regmap_update_bits(regmap, NAU8360_R8C_HW3_CTL6, NAU8360_HW3_DROOP_MASK | + NAU8360_HW3_FS_MASK, NAU8360_HW3_DROOP_192K | NAU8360_HW3_FS_192K); + /* Set HW1 Mute, Mute Interval as 699ms and HW1 Zero THD as 0xff */ + regmap_update_bits(regmap, NAU8360_R9C_HW1_CTL2, NAU8360_MUTE_INTRVL_MASK | + NAU8360_HW1_CH_MUTE | NAU8360_HW1_ZERO_THD_MASK, + NAU8360_MUTE_INTRVL_699MS | NAU8360_HW1_CH_MUTE | 0xff); + /* set HW2 droop with 192K*/ + regmap_update_bits(regmap, NAU8360_R96_HW2_CTL6, NAU8360_HW2_DROOP_SEL_MASK | + NAU8360_HW2_DROOP_EN | NAU8360_HW2_FS_MASK, + NAU8360_HW2_DROOP_SEL_LARGE | NAU8360_HW2_DROOP_EN | NAU8360_HW2_FS_192K); + /* Set HW2 default volume */ + regmap_write(regmap, NAU8360_R97_HW2_CTL7, 0xbf66); + regmap_write(regmap, NAU8360_R98_HW2_CTL8, 0xbf66); + /* Set HW2 Mute and Threshold of Zero Crossing */ + regmap_update_bits(regmap, NAU8360_R99_HW2_CTL9, NAU8360_HW2_CH_MUTE | + NAU8360_HW2_ZERO_THD_MASK, NAU8360_HW2_CH_MUTE | 0xff); + /* According to DSP enabled or not, set stream path as + * HW1->DSP->HW2->SINC->HW3 or HW1->HW2->SINC->HW3 + */ + regmap_update_bits(regmap, NAU8360_R12_PATH_CTRL, NAU8360_SEL_HW1_MASK | + NAU8360_AUD_SEL_MASK | NAU8360_SEL_HW2_MASK | NAU8360_SEL_HW3_MASK | + NAU8360_DAC_SEL_MASK, NAU8360_SEL_HW1_OUT | NAU8360_AUD_SEL_SINCOUT | + NAU8360_SEL_HW2_OUT | NAU8360_SEL_HW3_OUT); + /* Set Input Status as Low and Input/Output Mode Disable for All GPIO */ + regmap_write(regmap, NAU8360_R07_GP_CTRL, 0x0000); + /* enable SARADC with extra average filter of VBAT */ + regmap_update_bits(regmap, NAU8360_R6A_SARADC_CFG0, NAU8360_SARADC_EN | + NAU8360_VBAT_AVG_EN, NAU8360_SARADC_EN | NAU8360_VBAT_AVG_EN); + /* Enable Temperature Sensor, VBATDIV and VRF. + * Set Average Filter Data as 512 for VTEMP and VBAT. + */ + regmap_update_bits(regmap, NAU8360_R6B_SARADC_CFG1, NAU8360_VTEMP_EN | + NAU8360_VBATDIV_EN | NAU8360_VREF_EN | NAU8360_PRELOAD_VREF_EN | + NAU8360_VTEMP_AVG_N_MASK | NAU8360_VBAT_AVG_N_MASK, NAU8360_VTEMP_EN | + NAU8360_VBATDIV_EN | NAU8360_VREF_EN | NAU8360_PRELOAD_VREF_EN | + NAU8360_VTEMP_AVG_N_512 | NAU8360_VBAT_AVG_N_512); + /* Enable IV sense internal LDO */ + regmap_update_bits(regmap, NAU8360_R6C_IVSNS_CFG0, + NAU8360_PD_LDO_SDM_IVSNS_PMD, 0); + /* Enable Bias Current and Reference Voltage for IVSENSE. Set VSNS Gain + * as 1/12 and ISNS Gain as x16(24dB). Set PGA Current as 2'b11 and SDN + * Delay as 2'b00. + */ + regmap_write(regmap, NAU8360_R6D_IVSNS_CFG1, 0x0f5c); + /* sawtooth clock from ivsense clock, PWM frequency 432 Khz */ + regmap_update_bits(regmap, NAU8360_RA4_ANA_REG_0, NAU8360_SEL_STCLK_MASK | + NAU8360_NSEL_SAW_MASK, NAU8360_SEL_STCLK_IVCLK | 0x8); + /* sawtooth PLL APR */ + regmap_update_bits(regmap, NAU8360_RA5_ANA_REG_1, NAU8360_SAW_PLL_MASK, + NAU8360_SAW_PLL_NOR); + + /* Set DAC gain (0dB/+3.2dB) by current cell adjustment */ + regmap_update_bits(regmap, NAU8360_R6E_DAC_CFG0, NAU8360_DAC_CUR_MASK, + nau8360->dac_cur_enable ? NAU8360_DAC_CUR_3_2DB : NAU8360_DAC_CUR_0DB); + /* Config trim short current reference. Enable Non-overlap longer delay. + * Set segment driver as half driving strength. + */ + regmap_update_bits(regmap, NAU8360_R68_ANALOG_CONTROL_1, NAU8360_DTX_EN | + NAU8360_TRIMSCR_MASK | NAU8360_DRVCTL_SEGL_FULL | + NAU8360_DRVCTL_SEGR_FULL, NAU8360_TRIMSCR_MLOW | NAU8360_DTX_EN); + /* Enable SCP Clear Mode and Class D Modulator to Common Mode */ + regmap_update_bits(regmap, NAU8360_R67_ANALOG_CONTROL_0, + NAU8360_SCP_CLEAR_MODE_MASK | NAU8360_CM_COMP_EN, + NAU8360_SCP_CLEAR_MODE_AUTO | NAU8360_CM_COMP_EN); + /* Set Analog Mute and Class D Modulator Gain as 14dB */ + regmap_update_bits(regmap, NAU8360_R67_ANALOG_CONTROL_0, NAU8360_ANA_MUTE | + NAU8360_MOD_GAIN_MASK, NAU8360_ANA_MUTE | NAU8360_MOD_GAIN_14DB); + /* Set Stereo or PBTL Mode */ + regmap_update_bits(regmap, NAU8360_R67_ANALOG_CONTROL_0, + NAU8360_V_PBTL_EN, nau8360->pbtl_enable ? NAU8360_V_PBTL_EN : 0); + regmap_update_bits(regmap, NAU8360_R6C_IVSNS_CFG0, + NAU8360_PBTL_ISENE_LR_MASK, NAU8360_PBTL_ISENE_LR_ILR); + /* adjust HW3 default volume of voltage/current sense */ + regmap_write(regmap, NAU8360_R8A_HW3_VL_CTL7, 0xbe90); + regmap_write(regmap, NAU8360_R8B_HW3_VR_CTL8, 0xbe90); + regmap_write(regmap, NAU8360_R8D_HW3_IL_CTL7, nau8360->pbtl_enable ? 0xc3aa : 0xc24c); + regmap_write(regmap, NAU8360_R8E_HW3_IR_CTL8, nau8360->pbtl_enable ? 0xc3aa : 0xc24c); + /* Set HW3 Zero THD as 0xff */ + regmap_update_bits(regmap, NAU8360_R8F_HW3_CTL9, + NAU8360_HW3_ZERO_THD_MASK, 0xff); + /* Enable TX SDOUT and Data at BCLK Rising. */ + regmap_update_bits(regmap, NAU8360_R0D_I2S_PCM_CTRL3, NAU8360_TX_FILL_MASK, + NAU8360_TX_FILL_ZERO); + regmap_update_bits(regmap, NAU8360_R0D_I2S_PCM_CTRL3, + NAU8360_VSNS_L_SLEN_MASK, NAU8360_VSNS_L_SLEN_16); + regmap_update_bits(regmap, NAU8360_R0E_I2S_DATA_CTRL1, + NAU8360_ISNS_L_SLEN_MASK, NAU8360_ISNS_L_SLEN_16); + regmap_update_bits(regmap, NAU8360_R11_I2S_DATA_CTRL4, + NAU8360_VSNS_R_SLEN_MASK | NAU8360_ISNS_R_SLEN_MASK, + NAU8360_VSNS_R_SLEN_16 | NAU8360_ISNS_R_SLEN_16); + /* set DAC channel temperature trim code slope as 0x7D */ + regmap_update_bits(regmap, NAU8360_R7A_DAC_TRIM_CFG2, + NAU8360_DAC_TEMP_SLOPE_MASK, 0x7D << NAU8360_DAC_TEMP_SLOPE_SFT); + /* set ISNS temperature trim code slope as 0x1a */ + regmap_update_bits(regmap, NAU8360_R7B_IVSNS_TRIM_CFG, + NAU8360_ISNS_TEMP_SLOPE_MASK, 0x1a << NAU8360_ISNS_TEMP_SLOPE_SFT); + /* set misc trim config as 0x67F0 */ + regmap_update_bits(regmap, NAU8360_R7C_MISC_TRIM_CFG, NAU8360_DAC_GAIN_SB_MASK | + NAU8360_DAC_TEMP_SB_MASK | NAU8360_ISNS_TEMP_SB_MASK | + NAU8360_VSNS_TEMP_SB_MASK | NAU8360_ISNS_GAIN_SB_MASK | + NAU8360_VSNS_GAIN_SB_MASK, (0x3 << NAU8360_DAC_TEMP_SB_SFT) | + (0x7 << NAU8360_ISNS_TEMP_SB_SFT) | (0x3 << NAU8360_VSNS_TEMP_SB_SFT)); + /* Enable Efuse Initianllization */ + regmap_update_bits(regmap, NAU8360_R60_RST, NAU8360_EFUSE_CTRL_EN, + NAU8360_EFUSE_CTRL_EN); + /* Clear HW2 DRAM */ + regmap_update_bits(regmap, NAU8360_R90_HW2_CTL0, + NAU8360_HW2_DRAM_CLR, NAU8360_HW2_DRAM_CLR); + /* Finish HW DRAM Clear */ + regmap_update_bits(regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_DRAM_CLR, 0); + /* Clear HW3 DRAM */ + regmap_update_bits(regmap, NAU8360_R86_HW3_CTL0, + NAU8360_HW3_DRAM_CLR, NAU8360_HW3_DRAM_CLR); + /* Finish HW DRAM Clear */ + regmap_update_bits(regmap, NAU8360_R86_HW3_CTL0, NAU8360_HW3_DRAM_CLR, 0); +} + +static void nau8360_print_device_properties(struct nau8360 *nau8360) +{ + int i; + + dev_dbg(nau8360->dev, "pbtl-enable: %d", nau8360->pbtl_enable); + dev_dbg(nau8360->dev, "dac-cur-enable: %d", nau8360->dac_cur_enable); + for (i = 0; i < NAU8360_DSP_FW_NUM; i++) + dev_dbg(nau8360->dev, "firmware-name[%d]: %s", i, + nau8360->dsp_firmware[i]); + + for (i = 0; i < NAU8360_TDM_TXN; i++) + dev_dbg(nau8360->dev, "dsp-tx-slot[%d] (%s): %d%s", i, + nau8360_tx_func_names[i], nau8360->tdm_tx_func_slot[i], + nau8360->tdm_tx_func_slot[i] == TDM_SLOT_NONE ? " (none)" : ""); + + for (i = 0; i < NAU8360_TDM_RXN; i++) + dev_dbg(nau8360->dev, "dsp-rx-slot[%d] (%s): %d%s", i, + nau8360_rx_func_names[i], nau8360->tdm_rx_func_slot[i], + nau8360->tdm_rx_func_slot[i] == TDM_SLOT_NONE ? " (none)" : ""); +} + +static int nau8360_read_device_properties(struct nau8360 *nau8360) +{ + const char *def_fws[NAU8360_DSP_FW_NUM] = { + NAU8360_DSP_FIRMWARE".l", NAU8360_DSP_FIRMWARE".r" + }; + struct device *dev = nau8360->dev; + const char *firmware_names[NAU8360_DSP_FW_NUM]; + int i, ret; + + ret = device_property_read_u32_array(dev, "nuvoton,dsp-tx-slot-mapping", + nau8360->tdm_tx_func_slot, NAU8360_TDM_TXN); + if (ret) { + for (i = 0; i < NAU8360_TDM_TXN; i++) + nau8360->tdm_tx_func_slot[i] = TDM_SLOT_NONE; + } else { + for (i = 0; i < NAU8360_TDM_TXN; i++) { + if (nau8360->tdm_tx_func_slot[i] == TDM_SLOT_NONE) + continue; + if (nau8360->tdm_tx_func_slot[i] >= NAU8360_TDM_MAX_CHAN) { + dev_warn(dev, "Invalid TX slot %d, set to none\n", + nau8360->tdm_tx_func_slot[i]); + nau8360->tdm_tx_func_slot[i] = TDM_SLOT_NONE; + } + } + } + + ret = device_property_read_u32_array(dev, "nuvoton,dsp-rx-slot-mapping", + nau8360->tdm_rx_func_slot, NAU8360_TDM_RXN); + if (ret) { + nau8360->tdm_rx_func_slot[NAU8360_TDM_DACL] = 0; + nau8360->tdm_rx_func_slot[NAU8360_TDM_DACR] = 1; + nau8360->tdm_rx_func_slot[NAU8360_TDM_ANCL] = TDM_SLOT_NONE; + nau8360->tdm_rx_func_slot[NAU8360_TDM_ANCR] = TDM_SLOT_NONE; + } else { + for (i = 0; i < NAU8360_TDM_RXN; i++) { + if (nau8360->tdm_rx_func_slot[i] == TDM_SLOT_NONE) + continue; + if (nau8360->tdm_rx_func_slot[i] >= NAU8360_TDM_MAX_CHAN) { + dev_warn(dev, "Invalid RX slot %d, set to none\n", + nau8360->tdm_rx_func_slot[i]); + nau8360->tdm_rx_func_slot[i] = TDM_SLOT_NONE; + } + } + } + + nau8360->pbtl_enable = device_property_read_bool(dev, "nuvoton,pbtl-enable"); + nau8360->dac_cur_enable = device_property_read_bool(dev, "nuvoton,dac-cur-enable"); + + ret = device_property_read_string_array(dev, "firmware-name", + firmware_names, NAU8360_DSP_FW_NUM); + if (ret != NAU8360_DSP_FW_NUM) { + dev_warn(dev, "The firmware-name was not found, using default."); + for (i = 0; i < NAU8360_DSP_FW_NUM; i++) { + nau8360->dsp_firmware[i] = devm_kstrdup(dev, def_fws[i], GFP_KERNEL); + + if (!nau8360->dsp_firmware[i]) + return -ENOMEM; + } + } else { + for (i = 0; i < NAU8360_DSP_FW_NUM; i++) { + nau8360->dsp_firmware[i] = devm_kasprintf(dev, GFP_KERNEL, + NAU8360_DSP_FIRMDIR "%s", firmware_names[i]); + + if (!nau8360->dsp_firmware[i]) + return -ENOMEM; + } + } + + return 0; +} + +static struct reg_default *nau8360_alloc_defaults(struct device *dev, int *total_regs) +{ + struct reg_default *dyn_defaults; + int reg_num = ARRAY_SIZE(nau8360_reg_defaults); + int total = reg_num + (2 * NAU8360_TOT_BAND_PER_CH * NAU8360_TOT_BAND_COE); + int i, j, idx; + + dyn_defaults = devm_kzalloc(dev, total * sizeof(*dyn_defaults), GFP_KERNEL); + if (!dyn_defaults) + return NULL; + + memcpy(dyn_defaults, nau8360_reg_defaults, sizeof(*dyn_defaults) * reg_num); + idx = reg_num; + + for (i = 0; i < NAU8360_TOT_BAND_PER_CH; i++) { + unsigned int range = i * NAU8360_TOT_BAND_COE_RANGE; + unsigned int l_base = NAU8360_R100_LEFT_BIQ0_COE + range; + unsigned int r_base = NAU8360_R200_RIGHT_BIQ0_COE + range; + + for (j = 0; j < NAU8360_TOT_BAND_COE; j++) { + dyn_defaults[idx++].reg = l_base + j; + dyn_defaults[idx++].reg = r_base + j; + } + } + + *total_regs = total; + + return dyn_defaults; +} + +static int nau8360_i2c_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct nau8360 *nau8360; + struct regmap_config regmap_cfg = nau8360_regmap_config; + struct reg_default *dyn_defaults; + int num_total_regs; + int ret, value; + + nau8360 = devm_kzalloc(dev, sizeof(*nau8360), GFP_KERNEL); + if (!nau8360) + return -ENOMEM; + + i2c_set_clientdata(i2c, nau8360); + mutex_init(&nau8360->lock); + INIT_WORK(&nau8360->load_fw_work, nau8360_load_fw_work); + + dyn_defaults = nau8360_alloc_defaults(dev, &num_total_regs); + if (!dyn_defaults) + return -ENOMEM; + + regmap_cfg.reg_defaults = dyn_defaults; + regmap_cfg.num_reg_defaults = num_total_regs; + + nau8360->regmap = devm_regmap_init(dev, NULL, i2c, ®map_cfg); + if (IS_ERR(nau8360->regmap)) + return PTR_ERR(nau8360->regmap); + nau8360->dev = dev; + + nau8360_reset_chip(nau8360->regmap); + ret = regmap_read(nau8360->regmap, NAU8360_R46_I2C_DEVICE_ID, &value); + if (ret) { + dev_err(dev, "Failed to read NAU83G60 device id %d", ret); + return ret; + } + + ret = nau8360_read_device_properties(nau8360); + if (ret) + return ret; + + nau8360_print_device_properties(nau8360); + nau8360_init_regs(nau8360); + + return devm_snd_soc_register_component(dev, &soc_comp_dev_nau8360, &nau8360_dai, 1); +} + +static void nau8360_i2c_remove(struct i2c_client *client) +{ + struct nau8360 *nau8360 = i2c_get_clientdata(client); + + cancel_work_sync(&nau8360->load_fw_work); +} + +static const struct i2c_device_id nau8360_i2c_ids[] = { + { .name = "nau8360" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, nau8360_i2c_ids); + +static const struct of_device_id nau8360_of_ids[] = { + { .compatible = "nuvoton,nau8360" }, + {} +}; +MODULE_DEVICE_TABLE(of, nau8360_of_ids); + +static const struct acpi_device_id nau8360_acpi_match[] = { + { .id = "NVTN2002" }, + {} +}; +MODULE_DEVICE_TABLE(acpi, nau8360_acpi_match); + +static struct i2c_driver nau8360_i2c_driver = { + .driver = { + .name = "nau8360", + .of_match_table = of_match_ptr(nau8360_of_ids), + .acpi_match_table = ACPI_PTR(nau8360_acpi_match), + }, + .probe = nau8360_i2c_probe, + .remove = nau8360_i2c_remove, + .id_table = nau8360_i2c_ids, +}; +module_i2c_driver(nau8360_i2c_driver); + +MODULE_DESCRIPTION("ASoC NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver"); +MODULE_AUTHOR("David Lin "); +MODULE_AUTHOR("Seven Lee "); +MODULE_AUTHOR("John Hsu "); +MODULE_AUTHOR("Neo Chang "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/nau8360.h b/sound/soc/codecs/nau8360.h new file mode 100644 index 000000000000..cc640ba8c838 --- /dev/null +++ b/sound/soc/codecs/nau8360.h @@ -0,0 +1,911 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// The NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver. +// +// Copyright (C) 2026 Nuvoton Technology Corp. +// Author: David Lin +// Seven Lee +// John Hsu +// Neo Chang + +#ifndef __NAU8360_H__ +#define __NAU8360_H__ + +#define NAU8360_R00_SOFTWARE_RST 0x00 +#define NAU8360_R01_DSP_SOFTWARE_RST 0x01 +#define NAU8360_R02_I2C_ADDR 0x02 +#define NAU8360_R03_CLK_CTRL0 0x03 +#define NAU8360_R04_CLK_CTRL1 0x04 +#define NAU8360_R05_INTERRUPT_CTRL 0x05 +#define NAU8360_R06_INT_CLR_STATUS 0x06 +#define NAU8360_R07_GP_CTRL 0x07 +#define NAU8360_R08_GP_CTRL0 0x08 +#define NAU8360_R09_GP_CTRL1 0x09 +#define NAU8360_R0A_GP_CTRL2 0x0a +#define NAU8360_R0B_I2S_PCM_CTRL1 0x0b +#define NAU8360_R0C_I2S_PCM_CTRL2 0x0c +#define NAU8360_R0D_I2S_PCM_CTRL3 0x0d +#define NAU8360_R0E_I2S_DATA_CTRL1 0x0e +#define NAU8360_R0F_I2S_DATA_CTRL2 0x0f +#define NAU8360_R10_I2S_DATA_CTRL3 0x10 +#define NAU8360_R11_I2S_DATA_CTRL4 0x11 +#define NAU8360_R12_PATH_CTRL 0x12 +#define NAU8360_R16_NO_NAME 0x16 +#define NAU8360_R17_I2S0_DATA_CTRL5 0x17 +#define NAU8360_R1A_DSP_CORE_CTRL2 0x1a +#define NAU8360_R21_VBAT_READOUT 0x21 +#define NAU8360_R22_TEMP_READOUT 0x22 +#define NAU8360_R2C_ALC_CTRL1 0x2c +#define NAU8360_R2D_ALC_CTRL2 0x2d +#define NAU8360_R2E_ALC_CTRL3 0x2e +#define NAU8360_R31_UVLOP_CTRL1 0x31 +#define NAU8360_R32_UVLOP_CTRL2 0x32 +#define NAU8360_R33_UVLOP_CTRL3 0x33 +#define NAU8360_R40_CLK_DET_CTRL 0x40 +#define NAU8360_R41_CLK_CTL2 0x41 +#define NAU8360_R46_I2C_DEVICE_ID 0x46 +#define NAU8360_R5D_SINC_CFG 0x5d +#define NAU8360_R5F_ANA_TRIM_CFG1 0x5f +#define NAU8360_R60_RST 0x60 +#define NAU8360_R67_ANALOG_CONTROL_0 0x67 +#define NAU8360_R68_ANALOG_CONTROL_1 0x68 +#define NAU8360_R69_ANALOG_CONTROL_3 0x69 +#define NAU8360_R6A_SARADC_CFG0 0x6a +#define NAU8360_R6B_SARADC_CFG1 0x6b +#define NAU8360_R6C_IVSNS_CFG0 0x6c +#define NAU8360_R6D_IVSNS_CFG1 0x6d +#define NAU8360_R6E_DAC_CFG0 0x6e +#define NAU8360_R71_CLK_DIV_CFG 0x71 +#define NAU8360_R72_PLL_CFG0 0x72 +#define NAU8360_R73_PLL_CFG1 0x73 +#define NAU8360_R74_PLL_CFG2 0x74 +#define NAU8360_R77_SOFT_SD 0x77 +#define NAU8360_R78_PD_SW_DLDO 0x78 +#define NAU8360_R79_EN_HIRC48M 0x79 +#define NAU8360_R7A_DAC_TRIM_CFG2 0x7a +#define NAU8360_R7B_IVSNS_TRIM_CFG 0x7b +#define NAU8360_R7C_MISC_TRIM_CFG 0x7c +#define NAU8360_R7E_CLK_GATED_EN 0x7e +#define NAU8360_R86_HW3_CTL0 0x86 +#define NAU8360_R88_ALC_CTRL6 0x88 +#define NAU8360_R8A_HW3_VL_CTL7 0x8a +#define NAU8360_R8B_HW3_VR_CTL8 0x8b +#define NAU8360_R8C_HW3_CTL6 0x8c +#define NAU8360_R8D_HW3_IL_CTL7 0x8d +#define NAU8360_R8E_HW3_IR_CTL8 0x8e +#define NAU8360_R8F_HW3_CTL9 0x8f +#define NAU8360_R90_HW2_CTL0 0x90 +#define NAU8360_R96_HW2_CTL6 0x96 +#define NAU8360_R97_HW2_CTL7 0x97 +#define NAU8360_R98_HW2_CTL8 0x98 +#define NAU8360_R99_HW2_CTL9 0x99 +#define NAU8360_R9A_HW1_CTL0 0x9a +#define NAU8360_R9B_HW1_CTL1 0x9b +#define NAU8360_R9C_HW1_CTL2 0x9c +#define NAU8360_R9D_PEQ_CTL 0x9d +#define NAU8360_RA0_LEFT_XODRC_CTRL 0xa0 +#define NAU8360_RA2_RIGHT_XODRC_CTRL 0xa2 +#define NAU8360_RA4_ANA_REG_0 0xa4 +#define NAU8360_RA5_ANA_REG_1 0xa5 +/* Left PEQ includes 15 band, 10 coeff per band, from start addr 0x100 */ +#define NAU8360_R100_LEFT_BIQ0_COE 0x100 +#define NAU8360_R10C_LEFT_BIQ1_COE 0x10c +#define NAU8360_R118_LEFT_BIQ2_COE 0x118 +#define NAU8360_R124_LEFT_BIQ3_COE 0x124 +#define NAU8360_R130_LEFT_BIQ4_COE 0x130 +#define NAU8360_R13C_LEFT_BIQ5_COE 0x13c +#define NAU8360_R148_LEFT_BIQ6_COE 0x148 +#define NAU8360_R154_LEFT_BIQ7_COE 0x154 +#define NAU8360_R160_LEFT_BIQ8_COE 0x160 +#define NAU8360_R16C_LEFT_BIQ9_COE 0x16c +#define NAU8360_R178_LEFT_BIQ10_COE 0x178 +#define NAU8360_R184_LEFT_BIQ11_COE 0x184 +#define NAU8360_R190_LEFT_BIQ12_COE 0x190 +#define NAU8360_R19C_LEFT_BIQ13_COE 0x19c +#define NAU8360_R1A8_LEFT_BIQ14_COE 0x1a8 +/* Right PEQ includes 15 band, 10 coeff per band, from start addr 0x200 */ +#define NAU8360_R200_RIGHT_BIQ0_COE 0x200 +#define NAU8360_R20C_RIGHT_BIQ1_COE 0x20c +#define NAU8360_R218_RIGHT_BIQ2_COE 0x218 +#define NAU8360_R224_RIGHT_BIQ3_COE 0x224 +#define NAU8360_R230_RIGHT_BIQ4_COE 0x230 +#define NAU8360_R23C_RIGHT_BIQ5_COE 0x23c +#define NAU8360_R248_RIGHT_BIQ6_COE 0x248 +#define NAU8360_R254_RIGHT_BIQ7_COE 0x254 +#define NAU8360_R260_RIGHT_BIQ8_COE 0x260 +#define NAU8360_R26C_RIGHT_BIQ9_COE 0x26c +#define NAU8360_R278_RIGHT_BIQ10_COE 0x278 +#define NAU8360_R284_RIGHT_BIQ11_COE 0x284 +#define NAU8360_R290_RIGHT_BIQ12_COE 0x290 +#define NAU8360_R29C_RIGHT_BIQ13_COE 0x29c +#define NAU8360_R2A8_RIGHT_BIQ14_COE 0x2a8 +#define NAU8360_RF000_DSP_COMM 0xf000 +#define NAU8360_RF002_DSP_COMM 0xf002 +#define NAU8360_REG_MAX NAU8360_RF002_DSP_COMM + +/* 16-bit control register address, and 16-bits control register data */ +#define NAU8360_REG_ADDR_LEN 16 +#define NAU8360_REG_DATA_LEN 16 + +/* NAU8360_R03_CLK_CTRL0 (0x03) */ +#define NAU8360_MCLK_DIV_SFT 10 +#define NAU8360_MCLK_DIV_MAX 0x3f +#define NAU8360_MCLK_DIV_MASK (0x3f << NAU8360_MCLK_DIV_SFT) +#define NAU8360_DSP_CLK_DIV_SFT 7 +#define NAU8360_DSP_CLK_DIV_MAX 0x7 +#define NAU8360_DSP_CLK_DIV_MASK (0x7 << NAU8360_DSP_CLK_DIV_SFT) +#define NAU8360_DSP_CLK_SEL_SFT 5 +#define NAU8360_DSP_CLK_SEL_MASK (0x3 << NAU8360_DSP_CLK_SEL_SFT) +#define NAU8360_DSP_CLK_SEL_PLL (0x2 << NAU8360_DSP_CLK_SEL_SFT) +#define NAU8360_DSP_CLK_SEL_HIRC48M (0x1 << NAU8360_DSP_CLK_SEL_SFT) +#define NAU8360_DSP_CLK_SEL_MCLK (0x0 << NAU8360_DSP_CLK_SEL_SFT) + +/* NAU8360_R04_CLK_CTRL1 (0x4) */ +#define NAU8360_MCLK_SEL_SFT 14 +#define NAU8360_MCLK_SEL_MASK (0x3 << NAU8360_MCLK_SEL_SFT) +#define NAU8360_MCLK_SEL_PLL (0x2 << NAU8360_MCLK_SEL_SFT) +#define NAU8360_MCLK_SEL_HIRC48M (0x1 << NAU8360_MCLK_SEL_SFT) +#define NAU8360_MCLK_SEL_MCLK (0x0 << NAU8360_MCLK_SEL_SFT) +#define NAU8360_HW_CLK_SEL_SFT 10 +#define NAU8360_HW_CLK_SEL_MASK (0x3 << NAU8360_HW_CLK_SEL_SFT) +#define NAU8360_HW_CLK_SEL_PLL (0x2 << NAU8360_HW_CLK_SEL_SFT) +#define NAU8360_HW_CLK_SEL_HIRC48M (0x1 << NAU8360_HW_CLK_SEL_SFT) +#define NAU8360_HW_CLK_SEL_MCLK (0x0 << NAU8360_HW_CLK_SEL_SFT) +#define NAU8360_HW_CLK_DIV_SFT 7 +#define NAU8360_HW_CLK_DIV_MAX NAU8360_DSP_CLK_DIV_MAX +#define NAU8360_HW_CLK_DIV_MASK (0x7 << NAU8360_HW_CLK_DIV_SFT) + +/* NAU8360_R07_GP_CTRL (0x7) */ +#define NAU8360_GPIO1_IEOE_SFT 14 +#define NAU8360_GPIO1_IEOE_MASK (0x3 << NAU8360_GPIO1_IEOE_SFT) +#define NAU8360_GPIO2_IEOE_SFT 12 +#define NAU8360_GPIO2_IEOE_MASK (0x3 << NAU8360_GPIO2_IEOE_SFT) +#define NAU8360_GPIO3_IEOE_SFT 10 +#define NAU8360_GPIO3_IEOE_MASK (0x3 << NAU8360_GPIO3_IEOE_SFT) +#define NAU8360_GPIO4_IEOE_SFT 8 +#define NAU8360_GPIO4_IEOE_MASK (0x3 << NAU8360_GPIO4_IEOE_SFT) +#define NAU8360_GPIO1_IN_STAT_SFT 7 +#define NAU8360_GPIO1_IN_STAT_MASK (0x1 << NAU8360_GPIO1_IN_STAT_SFT) +#define NAU8360_GPIO2_IN_STAT_SFT 6 +#define NAU8360_GPIO2_IN_STAT_MASK (0x1 << NAU8360_GPIO2_IN_STAT_SFT) +#define NAU8360_GPIO3_IN_STAT_SFT 5 +#define NAU8360_GPIO3_IN_STAT_MASK (0x1 << NAU8360_GPIO3_IN_STAT_SFT) +#define NAU8360_GPIO4_IN_STAT_SFT 4 +#define NAU8360_GPIO4_IN_STAT_MASK (0x1 << NAU8360_GPIO4_IN_STAT_SFT) + +/* NAU8360_R08_GP_CTRL0 (0x8) */ +#define NAU8360_GPIO2_MUX_SFT 14 +#define NAU8360_GPIO2_MUX_MASK (0x3 << NAU8360_GPIO2_MUX_SFT) +#define NAU8360_GPIO2_SR_SFT 13 +#define NAU8360_GPIO2_SR_MASK (0x1 << NAU8360_GPIO2_SR_SFT) +#define NAU8360_GPIO2_CDS_SFT 11 +#define NAU8360_GPIO2_CDS_MASK (0x3 << NAU8360_GPIO2_CDS_SFT) +#define NAU8360_GPIO2_PS_SFT 10 +#define NAU8360_GPIO2_PS_MASK (0x1 << NAU8360_GPIO2_PS_SFT) +#define NAU8360_GPIO2_PE_SFT 9 +#define NAU8360_GPIO2_PE_EN (0x1 << NAU8360_GPIO2_PE_SFT) +#define NAU8360_GPIO1_MUX_SFT 6 +#define NAU8360_GPIO1_MUX_MASK (0x3 << NAU8360_GPIO1_MUX_SFT) +#define NAU8360_GPIO1_SR_SFT 5 +#define NAU8360_GPIO1_SR_MASK (0x1 << NAU8360_GPIO1_SR_SFT) +#define NAU8360_GPIO1_CDS_SFT 3 +#define NAU8360_GPIO1_CDS_MASK (0x3 << NAU8360_GPIO1_CDS_SFT) +#define NAU8360_GPIO1_PS_SFT 2 +#define NAU8360_GPIO1_PS_MASK (0x1 << NAU8360_GPIO1_PS_SFT) +#define NAU8360_GPIO1_PE_SFT 1 +#define NAU8360_GPIO1_PE_EN (0x1 << NAU8360_GPIO1_PE_SFT) + +/* NAU8360_R09_GP_CTRL1 (0x9) */ +#define NAU8360_GPIO4_MUX_SFT 14 +#define NAU8360_GPIO4_MUX_MASK (0x3 << NAU8360_GPIO4_MUX_SFT) +#define NAU8360_GPIO4_SR_SFT 13 +#define NAU8360_GPIO4_SR_MASK (0x1 << NAU8360_GPIO4_SR_SFT) +#define NAU8360_GPIO4_CDS_SFT 11 +#define NAU8360_GPIO4_CDS_MASK (0x3 << NAU8360_GPIO4_CDS_SFT) +#define NAU8360_GPIO4_PS_SFT 10 +#define NAU8360_GPIO4_PS_MASK (0x1 << NAU8360_GPIO4_PS_SFT) +#define NAU8360_GPIO4_PE_SFT 9 +#define NAU8360_GPIO4_PE_EN (0x1 << NAU8360_GPIO4_PE_SFT) +#define NAU8360_GPIO3_MUX_SFT 6 +#define NAU8360_GPIO3_MUX_MASK (0x3 << NAU8360_GPIO3_MUX_SFT) +#define NAU8360_GPIO3_SR_SFT 5 +#define NAU8360_GPIO3_SR_MASK (0x1 << NAU8360_GPIO3_SR_SFT) +#define NAU8360_GPIO3_CDS_SFT 3 +#define NAU8360_GPIO3_CDS_MASK (0x3 << NAU8360_GPIO3_CDS_SFT) +#define NAU8360_GPIO3_PS_SFT 2 +#define NAU8360_GPIO3_PS_MASK (0x1 << NAU8360_GPIO3_PS_SFT) +#define NAU8360_GPIO3_PE_SFT 1 +#define NAU8360_GPIO3_PE_EN (0x1 << NAU8360_GPIO3_PE_SFT) + +/* NAU8360_R0B_I2S_PCM_CTRL1 (0x0b) */ +#define NAU8360_EN_TDM_TX_SFT 15 +#define NAU8360_EN_TDM_TX (0x1 << NAU8360_EN_TDM_TX_SFT) +#define NAU8360_EN_TDM_RX_SFT 14 +#define NAU8360_EN_TDM_RX (0x1 << NAU8360_EN_TDM_RX_SFT) +#define NAU8360_FRAME_START_SFT 8 +#define NAU8360_FRAME_START_MASK (0x1 << NAU8360_FRAME_START_SFT) +#define NAU8360_FRAME_START_H2L (0x1 << NAU8360_FRAME_START_SFT) +#define NAU8360_FRAME_START_L2H (0x0 << NAU8360_FRAME_START_SFT) +#define NAU8360_RX_JUSTIFY_SFT 7 +#define NAU8360_RX_JUSTIFY_MASK (0x1 << NAU8360_RX_JUSTIFY_SFT) +#define NAU8360_RX_RIGHT_JUSTIFY (0x1 << NAU8360_RX_JUSTIFY_SFT) +#define NAU8360_RX_LEFT_JUSTIFY (0x0 << NAU8360_RX_JUSTIFY_SFT) +#define NAU8360_RX_OFFSET_SFT 2 +#define NAU8360_RX_OFFSET_MASK (0x1f << NAU8360_RX_OFFSET_SFT) +#define NAU8360_RX_OFFSET_I2S (0x1 << NAU8360_RX_OFFSET_SFT) +#define NAU8360_RX_OFFSET_LEFT (0x0 << NAU8360_RX_OFFSET_SFT) +#define NAU8360_RX_OFFSET_RIGHT (0x0 << NAU8360_RX_OFFSET_SFT) +#define NAU8360_RX_OFFSET_PCM_A (0x1 << NAU8360_RX_OFFSET_SFT) +#define NAU8360_RX_OFFSET_PCM_B (0x0 << NAU8360_RX_OFFSET_SFT) + +/* NAU8360_R0C_I2S_PCM_CTRL2 (0xc) */ +#define NAU8360_TDM_DLEN_SFT 10 +#define NAU8360_TDM_DLEN_MASK (0x3 << NAU8360_TDM_DLEN_SFT) +#define NAU8360_TDM_DLEN_32 (0x3 << NAU8360_TDM_DLEN_SFT) +#define NAU8360_TDM_DLEN_24 (0x2 << NAU8360_TDM_DLEN_SFT) +#define NAU8360_TDM_DLEN_20 (0x1 << NAU8360_TDM_DLEN_SFT) +#define NAU8360_TDM_DLEN_16 (0x0 << NAU8360_TDM_DLEN_SFT) +#define NAU8360_TDM_CLEN_SFT 8 +#define NAU8360_TDM_CLEN_MASK (0x3 << NAU8360_TDM_CLEN_SFT) +#define NAU8360_TDM_CLEN_32 (0x2 << NAU8360_TDM_CLEN_SFT) +#define NAU8360_TDM_CLEN_24 (0x1 << NAU8360_TDM_CLEN_SFT) +#define NAU8360_TDM_CLEN_16 (0x0 << NAU8360_TDM_CLEN_SFT) +#define NAU8360_RX_DACR_SFT 4 +#define NAU8360_RX_DACR_MASK (0xf << NAU8360_RX_DACR_SFT) +#define NAU8360_RX_DACL_MASK 0xf + +/* NAU8360_R0D_I2S_PCM_CTRL3 (0x0d) */ +#define NAU8360_TX_FILL_SFT 12 +#define NAU8360_TX_FILL_MASK (0x1 << NAU8360_TX_FILL_SFT) +#define NAU8360_TX_FILL_HIGHZ (0x1 << NAU8360_TX_FILL_SFT) +#define NAU8360_TX_FILL_ZERO (0x0 << NAU8360_TX_FILL_SFT) +#define NAU8360_TX_OFFSET_SFT 9 +#define NAU8360_TX_OFFSET_MASK (0x7 << NAU8360_TX_OFFSET_SFT) +#define NAU8360_TX_OFFSET_I2S (0x1 << NAU8360_TX_OFFSET_SFT) +#define NAU8360_TX_OFFSET_LEFT (0x0 << NAU8360_TX_OFFSET_SFT) +#define NAU8360_TX_OFFSET_RIGHT (0x0 << NAU8360_TX_OFFSET_SFT) +#define NAU8360_TX_OFFSET_PCM_A (0x1 << NAU8360_TX_OFFSET_SFT) +#define NAU8360_TX_OFFSET_PCM_B (0x0 << NAU8360_TX_OFFSET_SFT) +#define NAU8360_VSNS_L_SLEN_SFT 7 +#define NAU8360_VSNS_L_SLEN_MASK (0x1 << NAU8360_VSNS_L_SLEN_SFT) +#define NAU8360_VSNS_L_SLEN_16 (0x1 << NAU8360_VSNS_L_SLEN_SFT) +#define NAU8360_VSNS_L_SLEN_8 (0x0 << NAU8360_VSNS_L_SLEN_SFT) +#define NAU8360_VSNS_L_TX_EN_SFT 6 +#define NAU8360_VSNS_L_TX_EN (0x1 << NAU8360_VSNS_L_TX_EN_SFT) +#define NAU8360_VSNS_L_SLOT_MASK 0x3f + +/* NAU8360_R0E_I2S_DATA_CTRL1 (0x0e) */ +#define NAU8360_ISNS_L_SLEN_SFT 15 +#define NAU8360_ISNS_L_SLEN_MASK (0x1 << NAU8360_ISNS_L_SLEN_SFT) +#define NAU8360_ISNS_L_SLEN_16 (0x1 << NAU8360_ISNS_L_SLEN_SFT) +#define NAU8360_ISNS_L_SLEN_8 (0x0 << NAU8360_ISNS_L_SLEN_SFT) +#define NAU8360_ISNS_L_TX_EN_SFT 14 +#define NAU8360_ISNS_L_TX_EN (0x1 << NAU8360_ISNS_L_TX_EN_SFT) +#define NAU8360_ISNS_L_SLOT_SFT 8 +#define NAU8360_ISNS_L_SLOT_MASK (0x3f << NAU8360_ISNS_L_SLOT_SFT) +#define NAU8360_PDM_L_SLEN_SFT 7 +#define NAU8360_PDM_L_SLEN_MASK (0x1 << NAU8360_PDM_L_SLEN_SFT) +#define NAU8360_PDM_L_SLEN_24 (0x1 << NAU8360_PDM_L_SLEN_SFT) +#define NAU8360_PDM_L_SLEN_16 (0x0 << NAU8360_PDM_L_SLEN_SFT) +#define NAU8360_PDM_L_TX_EN_SFT 6 +#define NAU8360_PDM_L_TX_EN (0x1 << NAU8360_PDM_L_TX_EN_SFT) +#define NAU8360_PDM_L_SLOT_MASK 0x3f + +/* NAU8360_R0F_I2S_DATA_CTRL2 (0xf) */ +#define NAU8360_VBAT_TX_EN_SFT 14 +#define NAU8360_VBAT_TX_EN (0x1 << NAU8360_VBAT_TX_EN_SFT) +#define NAU8360_VBAT_SLOT_SFT 8 +#define NAU8360_VBAT_SLOT_MASK (0x3f << NAU8360_VBAT_SLOT_SFT) +#define NAU8360_PDM_R_SLEN_SFT 7 +#define NAU8360_PDM_R_SLEN_MASK (0x1 << NAU8360_PDM_R_SLEN_SFT) +#define NAU8360_PDM_R_SLEN_24 (0x1 << NAU8360_PDM_R_SLEN_SFT) +#define NAU8360_PDM_R_SLEN_16 (0x0 << NAU8360_PDM_R_SLEN_SFT) +#define NAU8360_PDM_R_TX_EN_SFT 6 +#define NAU8360_PDM_R_TX_EN (0x1 << NAU8360_PDM_R_TX_EN_SFT) +#define NAU8360_PDM_R_SLOT_MASK 0x3f + +/* NAU8360_R10_I2S_DATA_CTRL3 (0x10) */ +#define NAU8360_RX_ANC_R_SFT 12 +#define NAU8360_RX_ANC_R_MASK (0xf << NAU8360_RX_ANC_R_SFT) +#define NAU8360_RX_ANC_L_SFT 8 +#define NAU8360_RX_ANC_L_MASK (0xf << NAU8360_RX_ANC_L_SFT) +#define NAU8360_TDM_LOOPBACK_SFT 7 +#define NAU8360_TDM_LOOPBACK (0x1 << NAU8360_TDM_LOOPBACK_SFT) +#define NAU8360_TEMP_TX_EN_SFT 6 +#define NAU8360_TEMP_TX_EN (0x1 << NAU8360_TEMP_TX_EN_SFT) +#define NAU8360_TEMP_SLOT_MASK 0x3f + +/* NAU8360_R11_I2S_DATA_CTRL4 (0x11) */ +#define NAU8360_VSNS_R_SLEN_SFT 15 +#define NAU8360_VSNS_R_SLEN_MASK (0x1 << NAU8360_VSNS_R_SLEN_SFT) +#define NAU8360_VSNS_R_SLEN_16 (0x1 << NAU8360_VSNS_R_SLEN_SFT) +#define NAU8360_VSNS_R_SLEN_8 (0x0 << NAU8360_VSNS_R_SLEN_SFT) +#define NAU8360_VSNS_R_TX_EN_SFT 14 +#define NAU8360_VSNS_R_TX_EN (0x1 << NAU8360_VSNS_R_TX_EN_SFT) +#define NAU8360_VSNS_R_SLOT_SFT 8 +#define NAU8360_VSNS_R_SLOT_MASK (0x3f << NAU8360_VSNS_R_SLOT_SFT) +#define NAU8360_ISNS_R_SLEN_SFT 7 +#define NAU8360_ISNS_R_SLEN_MASK (0x1 << NAU8360_ISNS_R_SLEN_SFT) +#define NAU8360_ISNS_R_SLEN_16 (0x1 << NAU8360_ISNS_R_SLEN_SFT) +#define NAU8360_ISNS_R_SLEN_8 (0x0 << NAU8360_ISNS_R_SLEN_SFT) +#define NAU8360_ISNS_R_TX_EN_SFT 6 +#define NAU8360_ISNS_R_TX_EN (0x1 << NAU8360_ISNS_R_TX_EN_SFT) +#define NAU8360_ISNS_R_SLOT_MASK 0x3f + +/* NAU8360_R12_PATH_CTRL (0x12) */ +#define NAU8360_SEL_HW1_SFT 9 +#define NAU8360_SEL_HW1_MASK (0x1 << NAU8360_SEL_HW1_SFT) +#define NAU8360_SEL_HW1_OUT (0x1 << NAU8360_SEL_HW1_SFT) +#define NAU8360_SEL_HW1_BYP (0x0 << NAU8360_SEL_HW1_SFT) +#define NAU8360_AUD_SEL_SFT 7 +#define NAU8360_AUD_SEL_MASK (0x1 << NAU8360_AUD_SEL_SFT) +#define NAU8360_AUD_SEL_SINCOUT (0x1 << NAU8360_AUD_SEL_SFT) +#define NAU8360_AUD_SEL_SINCBYP (0x0 << NAU8360_AUD_SEL_SFT) +#define NAU8360_SEL_HW2_SFT 6 +#define NAU8360_SEL_HW2_MASK (0x1 << NAU8360_SEL_HW2_SFT) +#define NAU8360_SEL_HW2_OUT (0x1 << NAU8360_SEL_HW2_SFT) +#define NAU8360_SEL_HW2_BYP (0x0 << NAU8360_SEL_HW2_SFT) +#define NAU8360_DAC_SEL_SFT 5 +#define NAU8360_DAC_SEL_MASK (0x1 << NAU8360_DAC_SEL_SFT) +#define NAU8360_DAC_SEL_DSP (0x1 << NAU8360_DAC_SEL_SFT) +#define NAU8360_DAC_SEL_BYP (0x0 << NAU8360_DAC_SEL_SFT) +#define NAU8360_SEL_HW3_SFT 4 +#define NAU8360_SEL_HW3_MASK (0x1 << NAU8360_SEL_HW3_SFT) +#define NAU8360_SEL_HW3_OUT (0x1 << NAU8360_SEL_HW3_SFT) +#define NAU8360_SEL_HW3_BYP (0x0 << NAU8360_SEL_HW3_SFT) + +/* NAU8360_R17_I2S0_DATA_CTRL5 (0x17)*/ +#define NAU8360_AEC_L_SLEN_SFT 15 +#define NAU8360_AEC_L_SLEN_MASK (0x1 << NAU8360_AEC_L_SLEN_SFT) +#define NAU8360_AEC_L_SLEN_32 (0x1 << NAU8360_AEC_L_SLEN_SFT) +#define NAU8360_AEC_L_SLEN_16 (0x0 << NAU8360_AEC_L_SLEN_SFT) +#define NAU8360_AEC_L_EN_SFT 14 +#define NAU8360_AEC_L_EN (0x1 << NAU8360_AEC_L_EN_SFT) +#define NAU8360_AEC_L_SLOT_SFT 8 +#define NAU8360_AEC_L_SLOT_MASK (0x3f << NAU8360_AEC_L_SLOT_SFT) +#define NAU8360_AEC_R_SLEN_SFT 7 +#define NAU8360_AEC_R_SLEN_MASK (0x1 << NAU8360_AEC_R_SLEN_SFT) +#define NAU8360_AEC_R_SLEN_32 (0x1 << NAU8360_AEC_R_SLEN_SFT) +#define NAU8360_AEC_R_SLEN_16 (0x0 << NAU8360_AEC_R_SLEN_SFT) +#define NAU8360_AEC_R_EN_SFT 6 +#define NAU8360_AEC_R_EN (0x1 << NAU8360_AEC_R_EN_SFT) +#define NAU8360_AEC_R_SLOT_MASK 0x3f + +/* NAU8360_R1A_DSP_CORE_CTRL2 (0x1a) */ +#define NAU8360_DSP_RUNSTALL_SFT 4 +#define NAU8360_DSP_RUNSTALL (0x1 << NAU8360_DSP_RUNSTALL_SFT) + +/* NAU8360_R21_VBAT_READOUT (0x21) */ +#define NAU8360_VBAT_BASE 1108 +#define NAU8360_VBAT_STEP 12353 + +/* NAU8360_R40_CLK_DET_CTRL (0x40) */ +#define NAU8360_APWRUPEN_SFT 15 +#define NAU8360_APWRUPEN (0x1 << NAU8360_APWRUPEN_SFT) +#define NAU8360_CLKPWRUPEN_SFT 14 +#define NAU8360_CLKPWRUPEN (0x1 << NAU8360_CLKPWRUPEN_SFT) +#define NAU8360_SRATE_SFT 10 +#define NAU8360_SRATE_MASK (0x7 << NAU8360_SRATE_SFT) +#define NAU8360_SRATE_192000 (0x4 << NAU8360_SRATE_SFT) +#define NAU8360_SRATE_96000 (0x3 << NAU8360_SRATE_SFT) +#define NAU8360_SRATE_48000 (0x2 << NAU8360_SRATE_SFT) +#define NAU8360_SRATE_32000 (0x1 << NAU8360_SRATE_SFT) +#define NAU8360_SRATE_16000 (0x0 << NAU8360_SRATE_SFT) +#define NAU8360_MCLK_RATE_SFT 7 +#define NAU8360_MCLK_RATE_MASK (0x7 << NAU8360_MCLK_RATE_SFT) +#define NAU8360_MCLK_RATE_24576 (0x4 << NAU8360_MCLK_RATE_SFT) +#define NAU8360_MCLK_RATE_24000 (0x3 << NAU8360_MCLK_RATE_SFT) +#define NAU8360_MCLK_RATE_19200 (0x2 << NAU8360_MCLK_RATE_SFT) +#define NAU8360_MCLK_RATE_12288 (0x1 << NAU8360_MCLK_RATE_SFT) +#define NAU8360_MCLK_RATE_12000 (0x0 << NAU8360_MCLK_RATE_SFT) +#define NAU8360_FS_MCLK_DET_SFT 6 +#define NAU8360_FS_MCLK_DET (0x1 << NAU8360_FS_MCLK_DET_SFT) +#define NAU8360_FS_BCLK_DET_SFT 5 +#define NAU8360_FS_BCLK_DET (0x1 << NAU8360_FS_BCLK_DET_SFT) + +/* NAU8360_R41_CLK_CTL2 (0x41) */ +#define NAU8360_NOISE_FILTER_THD_SFT 12 +#define NAU8360_NOISE_FILTER_THDT_MASK (0xf << NAU8360_NOISE_FILTER_THD_SFT) +#define NAU8360_SEL_BLOCK_SIZE_SFT 6 +#define NAU8360_SEL_BLOCK_SIZE_MASK (0x7 << NAU8360_SEL_BLOCK_SIZE_SFT) +#define NAU8360_EN_ABCLKDET_SFT 3 +#define NAU8360_EN_ABCLKDET (0x1 << NAU8360_EN_ABCLKDET_SFT) +#define NAU8360_EN_DBCLKDET_SFT 2 +#define NAU8360_EN_DBCLKDET (0x1 << NAU8360_EN_DBCLKDET_SFT) +#define NAU8360_EN_AMCLKDET_SFT 1 +#define NAU8360_EN_AMCLKDET (0x1 << NAU8360_EN_AMCLKDET_SFT) +#define NAU8360_EN_DMCLKDET 0x1 + +/* NAU8360_R46_I2C_DEVICE_ID (0x46) */ +#define NAU8360_REG_SI_REV_MASK 0xff +#define NAU8360_REG_SI_REV_B 0xf2 +#define NAU8360_REG_SI_REV_C 0xf5 + +/* NAU8360_R5D_SINC_CFG (0x5d) */ +#define NAU8360_DAC_SINC_OSR_SFT 4 +#define NAU8360_DAC_SINC_OSR_MASK (0x3 << NAU8360_DAC_SINC_OSR_SFT) +#define NAU8360_DAC_SINC_OSR_250 (0x3 << NAU8360_DAC_SINC_OSR_SFT) +#define NAU8360_DAC_SINC_OSR_200 (0x2 << NAU8360_DAC_SINC_OSR_SFT) +#define NAU8360_DAC_SINC_OSR_128 (0x1 << NAU8360_DAC_SINC_OSR_SFT) +#define NAU8360_DAC_SINC_OSR_0 (0x0 << NAU8360_DAC_SINC_OSR_SFT) +#define NAU8360_IVSENSE_BS_OSR_MASK 0x3 +#define NAU8360_IVSENSE_BS_OSR_25 0x3 +#define NAU8360_IVSENSE_BS_OSR_64 0x2 +#define NAU8360_IVSENSE_BS_OSR_32 0x1 +#define NAU8360_IVSENSE_BS_OSR_16 0x0 + +/* NAU8360_R60_RST (0x60) */ +#define NAU8360_PD_POR5_SFT 4 +#define NAU8360_PD_POR5_PWD (0x1 << NAU8360_PD_POR5_SFT) +#define NAU8360_EFUSE_CTRL_SFT 3 +#define NAU8360_EFUSE_CTRL_EN (0x1 << NAU8360_EFUSE_CTRL_SFT) + +/* NAU8360_R67_ANALOG_CONTROL_0 (0x67) */ +#define NAU8360_SCP_CLEAR_MODE_SFT 15 +#define NAU8360_SCP_CLEAR_MODE_MASK (0x1 << NAU8360_SCP_CLEAR_MODE_SFT) +#define NAU8360_SCP_CLEAR_MODE_AUTO (0x1 << NAU8360_SCP_CLEAR_MODE_SFT) +#define NAU8360_SCP_CLEAR_MODE_MANU (0x0 << NAU8360_SCP_CLEAR_MODE_SFT) +#define NAU8360_SCP_CLEAR_SFT 14 +#define NAU8360_SCP_CLEAR (0x1 << NAU8360_SCP_CLEAR_SFT) +#define NAU8360_HV_EN_SFT 13 +#define NAU8360_HV_EN (0x1 << NAU8360_HV_EN_SFT) +#define NAU8360_ANA_MUTE_SFT 8 +#define NAU8360_ANA_MUTE (0x1 << NAU8360_ANA_MUTE_SFT) +#define NAU8360_MOD_GAIN_SFT 5 +#define NAU8360_MOD_GAIN_MASK (0x7 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_20DB (0x7 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_18DB (0x6 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_16DB (0x5 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_14DB (0x4 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_12DB (0x3 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_10DB (0x2 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_8DB (0x1 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_6DB (0x0 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_CM_COMP_SFT 4 +#define NAU8360_CM_COMP_EN (0x1 << NAU8360_CM_COMP_SFT) +#define NAU8360_V_PBTL_SFT 3 +#define NAU8360_V_PBTL_EN (0x1 << NAU8360_V_PBTL_SFT) + +/* NAU8360_R68_ANALOG_CONTROL_1 (0x68) */ +#define NAU8360_TRIMSCR_SFT 6 +#define NAU8360_TRIMSCR_MASK (0x3 << NAU8360_TRIMSCR_SFT) +#define NAU8360_TRIMSCR_HIGH (0x3 << NAU8360_TRIMSCR_SFT) +#define NAU8360_TRIMSCR_MHIGH (0x2 << NAU8360_TRIMSCR_SFT) +#define NAU8360_TRIMSCR_MLOW (0x1 << NAU8360_TRIMSCR_SFT) +#define NAU8360_TRIMSCR_LOW (0x0 << NAU8360_TRIMSCR_SFT) +#define NAU8360_DTX_SFT 5 +#define NAU8360_DTX_EN (0x1 << NAU8360_DTX_SFT) +#define NAU8360_DRVCTL_SEGL_SFT 4 +#define NAU8360_DRVCTL_SEGL_FULL (0x1 << NAU8360_DRVCTL_SEGL_SFT) +#define NAU8360_DRVCTL_SEGR_SFT 2 +#define NAU8360_DRVCTL_SEGR_FULL (0x1 << NAU8360_DRVCTL_SEGR_SFT) +#define NAU8360_SCP_TEST_SFT 1 +#define NAU8360_SCP_TEST (0x1 << NAU8360_SCP_TEST_SFT) +#define NAU8360_SEG_AUTO_CTRL_EN 0x1 + +/* NAU8360_R69_ANALOG_CONTROL_3 (0x69) */ +#define NAU8360_ANA_STATUS_OTP_SFT 8 +#define NAU8360_ANA_STATUS_OTP_MASK (0x1 << NAU8360_ANA_STATUS_OTP_SFT) +#define NAU8360_ANA_STATUS_OVLO_SFT 7 +#define NAU8360_ANA_STATUS_OVLO_MASK (0x1 << NAU8360_ANA_STATUS_OVLO_SFT) +#define NAU8360_ANA_STATUS_UVLO_SFT 6 +#define NAU8360_ANA_STATUS_UVLO_MASK (0x1 << NAU8360_ANA_STATUS_UVLO_SFT) +#define NAU8360_ANA_STATUS_SCP_SFT 4 +#define NAU8360_ANA_STATUS_SCP_MASK (0x1 << NAU8360_ANA_STATUS_SCP_SFT) +#define NAU8360_ANA_STATUS_EN_V2I_SFT 3 +#define NAU8360_ANA_STATUS_EN_V2I_MASK (0x1 << NAU8360_ANA_STATUS_EN_V2I_SFT) +#define NAU8360_ANA_STATUS_FAULT_SFT 2 +#define NAU8360_ANA_STATUS_FAULT_MASK (0x1 << NAU8360_ANA_STATUS_FAULT_SFT) +#define NAU8360_ANA_STATUS_DC_DET_SFT 1 +#define NAU8360_ANA_STATUS_DC_DET_MASK (0x1 << NAU8360_ANA_STATUS_DC_DET_SFT) +#define NAU8360_ANA_STATUS_EN_ALL_MASK 0x1 + +/* NAU8360_R6A_SARADC_CFG0 (0x6a) */ +#define NAU8360_SARADC_SFT 15 +#define NAU8360_SARADC_EN (0x1 << NAU8360_SARADC_SFT) +#define NAU8360_RESETB_SARADC_SFT 14 +#define NAU8360_RESETB_SARADC_MASK (0x1 << NAU8360_RESETB_SARADC_SFT) +#define NAU8360_RESETB_SARADC_NOR (0x1 << NAU8360_RESETB_SARADC_SFT) +#define NAU8360_RESETB_SARADC_RST (0x0 << NAU8360_RESETB_SARADC_SFT) +#define NAU8360_SARADC_CAL_SFT 13 +#define NAU8360_SARADC_CAL (0x1 << NAU8360_SARADC_CAL_SFT) +#define NAU8360_SARADC_DIV_SFT 12 +#define NAU8360_SARADC_DIV (0x1 << NAU8360_SARADC_DIV_SFT) +#define NAU8360_SARADC_CALSEL_SFT 9 +#define NAU8360_SARADC_CALSEL_MASK (0x7 << NAU8360_SARADC_CALSEL_SFT) +#define NAU8360_TRIMSMPL_SFT 3 +#define NAU8360_TRIMSMPL_MASK (0x7 << NAU8360_TRIMSMPL_SFT) +#define NAU8360_SAR_INPUT_CH_SEL_SFT 2 +#define NAU8360_SAR_INPUT_CH_SEL_MASK (0x1 << NAU8360_SAR_INPUT_CH_SEL_SFT) +#define NAU8360_VBAT_AVG_SFT 1 +#define NAU8360_VBAT_AVG_EN (0x1 << NAU8360_VBAT_AVG_SFT) +#define NAU8360_VTEMP_AVG_EN 0x1 + +/* NAU8360_R6B_SARADC_CFG1 (0x6b) */ +#define NAU8360_VTEMP_SFT 15 +#define NAU8360_VTEMP_EN (0x1 << NAU8360_VTEMP_SFT) +#define NAU8360_VBATDIV_SFT 14 +#define NAU8360_VBATDIV_EN (0x1 << NAU8360_VBATDIV_SFT) +#define NAU8360_VREF_SFT 10 +#define NAU8360_VREF_EN (0x1 << NAU8360_VREF_SFT) +#define NAU8360_PRELOAD_VREF_SFT 9 +#define NAU8360_PRELOAD_VREF_EN (0x1 << NAU8360_PRELOAD_VREF_SFT) +#define NAU8360_VTEMP_AVG_N_SFT 2 +#define NAU8360_VTEMP_AVG_N_MASK (0x3 << NAU8360_VTEMP_AVG_N_SFT) +#define NAU8360_VTEMP_AVG_N_512 (0x3 << NAU8360_VTEMP_AVG_N_SFT) +#define NAU8360_VTEMP_AVG_N_128 (0x2 << NAU8360_VTEMP_AVG_N_SFT) +#define NAU8360_VTEMP_AVG_N_32 (0x1 << NAU8360_VTEMP_AVG_N_SFT) +#define NAU8360_VTEMP_AVG_N_1 (0x0 << NAU8360_VTEMP_AVG_N_SFT) +#define NAU8360_VBAT_AVG_N_MASK 0x3 +#define NAU8360_VBAT_AVG_N_512 0x3 +#define NAU8360_VBAT_AVG_N_128 0x2 +#define NAU8360_VBAT_AVG_N_32 0x1 +#define NAU8360_VBAT_AVG_N_1 0 + +/* NAU8360_R6C_IVSNS_CFG0 (0x6c) */ +#define NAU8360_PD_VSNS_R_SFT 15 +#define NAU8360_PD_VSNS_R_PMD (0x1 << NAU8360_PD_VSNS_R_SFT) +#define NAU8360_PD_VSNS_L_SFT 14 +#define NAU8360_PD_VSNS_L_PMD (0x1 << NAU8360_PD_VSNS_L_SFT) +#define NAU8360_PD_ISNS_R_SFT 13 +#define NAU8360_PD_ISNS_R_PMD (0x1 << NAU8360_PD_ISNS_R_SFT) +#define NAU8360_PD_ISNS_L_SFT 12 +#define NAU8360_PD_ISNS_L_PMD (0x1 << NAU8360_PD_ISNS_L_SFT) +#define NAU8360_RESET_SC_VADC_R_SFT 11 +#define NAU8360_RESET_SC_VADC_R_RST (0x1 << NAU8360_RESET_SC_VADC_R_SFT) +#define NAU8360_RESET_SC_VADC_L_SFT 10 +#define NAU8360_RESET_SC_VADC_L_RST (0x1 << NAU8360_RESET_SC_VADC_L_SFT) +#define NAU8360_RESET_SC_IADC_R_SFT 9 +#define NAU8360_RESET_SC_IADC_R_RST (0x1 << NAU8360_RESET_SC_IADC_R_SFT) +#define NAU8360_RESET_SC_IADC_L_SFT 8 +#define NAU8360_RESET_SC_IADC_L_RST (0x1 << NAU8360_RESET_SC_IADC_L_SFT) +#define NAU8360_PBTL_ISENE_LR_SFT 7 +#define NAU8360_PBTL_ISENE_LR_MASK (0x1 << NAU8360_PBTL_ISENE_LR_SFT) +#define NAU8360_PBTL_ISENE_LR_ILR (0x1 << NAU8360_PBTL_ISENE_LR_SFT) +#define NAU8360_PBTL_ISENE_LR_I2R (0x0 << NAU8360_PBTL_ISENE_LR_SFT) +#define NAU8360_PD_LDO_SDM_IVSNS_SFT 6 +#define NAU8360_PD_LDO_SDM_IVSNS_PMD (0x1 << NAU8360_PD_LDO_SDM_IVSNS_SFT) + +/* NAU8360_R6D_IVSNS_CFG1 (0x6d) */ +#define NAU8360_PD_V2I_IVSNS_SFT 13 +#define NAU8360_PD_V2I_IVSNS_MASK (0x1 << NAU8360_PD_V2I_IVSNS_SFT) +#define NAU8360_PD_VREF_SDM_IVSNS_SFT 12 +#define NAU8360_PD_VREF_SDM_IVSNS_MASK (0x1 << NAU8360_PD_VREF_SDM_IVSNS_SFT) +#define NAU8360_VSNS_PGA_CUR_SET_SFT 10 +#define NAU8360_VSNS_PGA_CUR_SET_MASK (0x3 << NAU8360_VSNS_PGA_CUR_SET_SFT) +#define NAU8360_VSNS_GAIN_SFT 8 +#define NAU8360_VSNS_GAIN_MASK (0x3 << NAU8360_VSNS_GAIN_SFT) +#define NAU8360_VREF_CUR_SFT 6 +#define NAU8360_VREF_CUR_MASK (0x3 << NAU8360_VREF_CUR_SFT) +#define NAU8360_ISNS_PGA_CUR_SFT 4 +#define NAU8360_ISNS_PGA_CUR_MASK (0x3 << NAU8360_ISNS_PGA_CUR_SFT) +#define NAU8360_ISNS_GAIN_SFT 2 +#define NAU8360_ISNS_GAIN_MASK (0x3 << NAU8360_ISNS_GAIN_SFT) +#define NAU8360_SDM_DELAY_TRIM_MASK 0x3 + +/* NAU8360_R6E_DAC_CFG0 (0x6e) */ +#define NAU8360_PD_DACL_SFT 11 +#define NAU8360_PD_DACL_DIS (0x1 << NAU8360_PD_DACL_SFT) +#define NAU8360_PD_DACR_SFT 10 +#define NAU8360_PD_DACR_DIS (0x1 << NAU8360_PD_DACR_SFT) +#define NAU8360_PD_CHOP_DAC_SFT 9 +#define NAU8360_PD_CHOP_DAC_MASK (0x1 << NAU8360_PD_CHOP_DAC_SFT) +#define NAU8360_DAC_RES_SFT 7 +#define NAU8360_DAC_RES_MASK (0x1 << NAU8360_DAC_RES_SFT) +#define NAU8360_DAC_CUR_SFT 6 +#define NAU8360_DAC_CUR_MASK (0x1 << NAU8360_DAC_CUR_SFT) +#define NAU8360_DAC_CUR_3_2DB (0x1 << NAU8360_DAC_CUR_SFT) +#define NAU8360_DAC_CUR_0DB (0x0 << NAU8360_DAC_CUR_SFT) +#define NAU8360_DAC_BUFOPCUR_SFT 4 +#define NAU8360_DAC_BUFOPCUR_MASK (0x3 << NAU8360_DAC_BUFOPCUR_SFT) +#define NAU8360_DAC_IVOPCUR_SFT 2 +#define NAU8360_DAC_IVOPCUR_MASK (0x3 << NAU8360_DAC_IVOPCUR_SFT) +#define NAU8360_DAC_LPFOP_CUR_MASK 0x3 + +/* NAU8360_R71_CLK_DIV_CFG (0x71) */ +#define NAU8360_IVSNS_CLK_INV_SEL_SFT 10 +#define NAU8360_IVSNS_CLK_INV_SEL_MASK (0x1 << NAU8360_IVSNS_CLK_INV_SEL_SFT) +#define NAU8360_CLK_ANA_SEL_SFT 7 +#define NAU8360_CLK_ANA_SEL_MASK (0x3 << NAU8360_CLK_ANA_SEL_SFT) +#define NAU8360_CLK_ANA_SEL_PLL (0x2 << NAU8360_CLK_ANA_SEL_SFT) +#define NAU8360_CLK_ANA_SEL_BCLK (0x1 << NAU8360_CLK_ANA_SEL_SFT) +#define NAU8360_CLK_ANA_SEL_MCLK (0x0 << NAU8360_CLK_ANA_SEL_SFT) +#define NAU8360_IVSNS_CLK_DIV_SFT 4 +#define NAU8360_IVSNS_CLK_DIV_MASK (0x7 << NAU8360_IVSNS_CLK_DIV_SFT) +#define NAU8360_DAC_OUTPUT_EDGE_SFT 3 +#define NAU8360_DAC_OUTPUT_EDGE_MASK (0x1 << NAU8360_DAC_OUTPUT_EDGE_SFT) +#define NAU8360_DAC_CLK_DIV_SFT 1 +#define NAU8360_DAC_CLK_DIV_MASK (0x3 << NAU8360_DAC_CLK_DIV_SFT) +#define NAU8360_DAC_CLK_DIV_8 (0x3 << NAU8360_DAC_CLK_DIV_SFT) +#define NAU8360_DAC_CLK_DIV_4 (0x2 << NAU8360_DAC_CLK_DIV_SFT) +#define NAU8360_DAC_CLK_DIV_2 (0x1 << NAU8360_DAC_CLK_DIV_SFT) +#define NAU8360_DAC_CLK_DIV_1 (0x0 << NAU8360_DAC_CLK_DIV_SFT) +#define NAU8360_DAC_CHOP_CLK_DIV_MASK 0x1 +#define NAU8360_DAC_CHOP_CLK_DIV_32 0x1 +#define NAU8360_DAC_CHOP_CLK_DIV_16 0x0 + +/* NAU8360_R72_PLL_CFG0 (0x72) */ +#define NAU8360_PLLOUT_DIV_SFT 10 +#define NAU8360_PLLOUT_DIV_MAX 0x1f +#define NAU8360_PLLOUT_DIV_MASK (0x1f << NAU8360_PLLOUT_DIV_SFT) +#define NAU8360_PLLOUT_DIV_BYPASS (0x0 << NAU8360_PLLOUT_DIV_SFT) +#define NAU8360_PLL_CLK_SEL_SFT 8 +#define NAU8360_PLL_CLK_SEL_MASK (0x3 << NAU8360_PLL_CLK_SEL_SFT) +#define NAU8360_PLL_CLK_SEL_HIRC (0x3 << NAU8360_PLL_CLK_SEL_SFT) +#define NAU8360_PLL_CLK_SEL_BCLK (0x1 << NAU8360_PLL_CLK_SEL_SFT) +#define NAU8360_PLL_CLK_SEL_MCLK (0x0 << NAU8360_PLL_CLK_SEL_SFT) +#define NAU8360_PD_PLL_SFT 7 +#define NAU8360_PD_PLL_MASK (0x1 << NAU8360_PD_PLL_SFT) +#define NAU8360_PD_PLL_DIS (0x1 << NAU8360_PD_PLL_SFT) +#define NAU8360_PD_PLL_EN (0x0 << NAU8360_PD_PLL_SFT) + +/* NAU8360_R73_PLL_CFG1 (0x73) */ +#define NAU8360_RSEL_SFT 13 +#define NAU8360_RSEL_MASK (0x3 << NAU8360_RSEL_SFT) +#define NAU8360_MSEL_SFT 8 +#define NAU8360_MSEL_MASK (0x1f << NAU8360_MSEL_SFT) +#define NAU8360_NSEL_MASK 0xff + +/* NAU8360_R74_PLL_CFG2 (0x74) */ +#define NAU8360_XSEL_MASK 0xfff + +/* NAU8360_R77_SOFT_SD (0x77) */ +#define NAU8360_SOFT_SD 0x55AA +#define NAU8360_SOFT_SD_EN 0 + +/* NAU8360_R78_PD_SW_DLDO (0x78) */ +#define NAU8360_PD_SW_DLDO 0xAA55 +#define NAU8360_PD_SW_DLDO_EN 0 + +/* NAU8360_R7A_DAC_TRIM_CFG2 (0x7a) */ +#define NAU8360_DAC_TEMP_SLOPE_SFT 8 +#define NAU8360_DAC_TEMP_SLOPE_MASK (0x7f << NAU8360_DAC_TEMP_SLOPE_SFT) + +/* NAU8360_R7B_IVSNS_TRIM_CFG (0x7b) */ +#define NAU8360_ISNS_TEMP_SLOPE_SFT 8 +#define NAU8360_ISNS_TEMP_SLOPE_MASK (0x7f << NAU8360_ISNS_TEMP_SLOPE_SFT) +#define NAU8360_VSNS_TEMP_SLOPE_MASK 0x7f + +/* NAU8360_R7C_MISC_TRIM_CFG (0x7c) */ +#define NAU8360_DAC_OS_SB_SFT 13 +#define NAU8360_DAC_OS_SB_SFT_MASK (0x7 << NAU8360_DAC_OS_SB_SFT) +#define NAU8360_DAC_GAIN_SB_SFT 11 +#define NAU8360_DAC_GAIN_SB_MASK (0x3 << NAU8360_DAC_GAIN_SB_SFT) +#define NAU8360_DAC_TEMP_SB_SFT 9 +#define NAU8360_DAC_TEMP_SB_MASK (0x3 << NAU8360_DAC_TEMP_SB_SFT) +#define NAU8360_ISNS_TEMP_SB_SFT 6 +#define NAU8360_ISNS_TEMP_SB_MASK (0x7 << NAU8360_ISNS_TEMP_SB_SFT) +#define NAU8360_VSNS_TEMP_SB_SFT 4 +#define NAU8360_VSNS_TEMP_SB_MASK (0x3 << NAU8360_VSNS_TEMP_SB_SFT) +#define NAU8360_ISNS_GAIN_SB_SFT 2 +#define NAU8360_ISNS_GAIN_SB_MASK (0x3 << NAU8360_ISNS_GAIN_SB_SFT) +#define NAU8360_VSNS_GAIN_SB_MASK 0x3 + +/* NAU8360_R7E_CLK_GATED_EN (0x7e)*/ +#define NAU8360_CLK_GATED_EN 0xa5a5 +#define NAU8360_CLK_GATED_DIS 0 + +/* NAU8360_R86_HW3_CTL0 (0x86) */ +#define NAU8360_HW3_DRAM_CLR_SFT 14 +#define NAU8360_HW3_DRAM_CLR (0x1 << NAU8360_HW3_DRAM_CLR_SFT) +#define NAU8360_HW3_STALL_SFT 13 +#define NAU8360_HW3_STALL (0x1 << NAU8360_HW3_STALL_SFT) + +/* NAU8360_R8C_HW3_CTL6 (0x8c)*/ +#define NAU8360_HW3_DROOP_SFT 2 +#define NAU8360_HW3_DROOP_MASK (0x1 << NAU8360_HW3_DROOP_SFT) +#define NAU8360_HW3_DROOP_192K (0x1 << NAU8360_HW3_DROOP_SFT) +#define NAU8360_HW3_DROOP_96K (0x0 << NAU8360_HW3_DROOP_SFT) +#define NAU8360_HW3_FS_MASK 0x1 +#define NAU8360_HW3_FS_192K 0x1 +#define NAU8360_HW3_FS_96K 0x0 + +/* NAU8360_R8F_HW3_CTL9 (0x8f)*/ +#define NAU8360_HW3_CH_MUTE_SFT 8 +#define NAU8360_HW3_CH_MUTE (0x1 << NAU8360_HW3_CH_MUTE_SFT) +#define NAU8360_HW3_ZERO_THD_MASK 0xff + +/* NAU8360_R90_HW2_CTL0 (0x90) */ +#define NAU8360_HW2_DRAM_CLR_SFT 14 +#define NAU8360_HW2_DRAM_CLR (0x1 << NAU8360_HW2_DRAM_CLR_SFT) +#define NAU8360_HW2_STALL_SFT 13 +#define NAU8360_HW2_STALL (0x1 << NAU8360_HW2_STALL_SFT) + +/* NAU8360_R96_HW2_CTL6 (0x96) */ +#define NAU8360_HW1_ANC_EN_SFT 15 +#define NAU8360_HW1_ANC_EN (0x1 << NAU8360_HW1_ANC_EN_SFT) +#define NAU8360_HW2_DROOP_SEL_SFT 3 +#define NAU8360_HW2_DROOP_SEL_MASK (0x1 << NAU8360_HW2_DROOP_SEL_SFT) +#define NAU8360_HW2_DROOP_SEL_LARGE (0x1 << NAU8360_HW2_DROOP_SEL_SFT) +#define NAU8360_HW2_DROOP_SEL_SMALL (0x0 << NAU8360_HW2_DROOP_SEL_SFT) +#define NAU8360_HW2_DROOP_EN_SFT 2 +#define NAU8360_HW2_DROOP_EN (0x1 << NAU8360_HW2_DROOP_EN_SFT) +#define NAU8360_HW2_LATENCY_SFT 1 +#define NAU8360_HW2_LATENCY_MASK (0x1 << NAU8360_HW2_LATENCY_SFT) +#define NAU8360_HW2_LATENCY_LOW (0x1 << NAU8360_HW2_LATENCY_SFT) +#define NAU8360_HW2_LATENCY_NOR (0x0 << NAU8360_HW2_LATENCY_SFT) +#define NAU8360_HW2_FS_MASK 0x1 +#define NAU8360_HW2_FS_192K 0x1 +#define NAU8360_HW2_FS_96K 0x0 + +/* NAU8360_R99_HW2_CTL9 (0x99) */ +#define NAU8360_HW2_VSR_SFT 9 +#define NAU8360_HW2_VSR_MASK (0x3 << NAU8360_HW2_VSR_SFT) +#define NAU8360_HW2_CH_MUTE_SFT 8 +#define NAU8360_HW2_CH_MUTE (0x1 << NAU8360_HW2_CH_MUTE_SFT) +#define NAU8360_HW2_ZERO_THD_MASK 0xff + +/* NAU8360_R9C_HW1_CTL2 (0x9c) */ +#define NAU8360_MUTE_INTRVL_SFT 11 +#define NAU8360_MUTE_INTRVL_MASK (0x3 << NAU8360_MUTE_INTRVL_SFT) +#define NAU8360_MUTE_INTRVL_699MS (0x3 << NAU8360_MUTE_INTRVL_SFT) +#define NAU8360_MUTE_INTRVL_466MS (0x2 << NAU8360_MUTE_INTRVL_SFT) +#define NAU8360_MUTE_INTRVL_233MS (0x1 << NAU8360_MUTE_INTRVL_SFT) +#define NAU8360_HW1_CH_MUTE_SFT 8 +#define NAU8360_HW1_CH_MUTE (0x1 << NAU8360_HW1_CH_MUTE_SFT) +#define NAU8360_HW1_ZERO_THD_MASK 0xff + +/* NAU8360_R9D_PEQ_CTL (0x9d) */ +#define NAU8360_PEQ_BAND_SFT 12 +#define NAU8360_PEQ_BAND_MASK (0xf << NAU8360_PEQ_BAND_SFT) +#define NAU8360_HW1_MEM_TEST_SFT 2 +#define NAU8360_HW1_MEM_TEST (0x1 << NAU8360_HW1_MEM_TEST_SFT) +#define NAU8360_HW1_MEM_CLEAR_SFT 1 +#define NAU8360_HW1_MEM_CLEAR (0x1 << NAU8360_HW1_MEM_CLEAR_SFT) +#define NAU8360_PEQ_STALL 0x1 + +/* NAU8360_RA4_ANA_REG_0 (0xa4) */ +#define NAU8360_MSEL_SAW_SFT 8 +#define NAU8360_MSEL_SAW_MASK (0xff << NAU8360_MSEL_SAW_SFT) +#define NAU8360_FTRIM_PWM_SFT 6 +#define NAU8360_FTRIM_PWM_MASK (0x3 << NAU8360_MSEL_SAW_SFT) +#define NAU8360_SEL_STCLK_SFT 5 +#define NAU8360_SEL_STCLK_MASK (0x1 << NAU8360_SEL_STCLK_SFT) +#define NAU8360_SEL_STCLK_IVCLK (0x1 << NAU8360_SEL_STCLK_SFT) +#define NAU8360_SEL_STCLK_FREERUN (0x0 << NAU8360_SEL_STCLK_SFT) +#define NAU8360_NSEL_SAW_MASK 0x1f + +/* NAU8360_RA5_ANA_REG_1 (0xa5) */ +#define NAU8360_ICP_SAW_SET_SFT 14 +#define NAU8360_ICP_SAW_SET_MASK (0x3 << NAU8360_ICP_SAW_SET_SFT) +#define NAU8360_VSAW_LV_SFT 12 +#define NAU8360_VSAW_LV_MASK (0x3 << NAU8360_VSAW_LV_SFT) +#define NAU8360_KVCO_SAW_SFT 10 +#define NAU8360_KVCO_SAW_MASK (0x3 << NAU8360_KVCO_SAW_SFT) +#define NAU8360_TRIM_LPF_SFT 8 +#define NAU8360_TRIM_LPF_MASK (0x3 << NAU8360_TRIM_LPF_SFT) +#define NAU8360_SAW_PLL_SFT 7 +#define NAU8360_SAW_PLL_MASK (0x1 << NAU8360_SAW_PLL_SFT) +#define NAU8360_SAW_PLL_NOR (0x1 << NAU8360_SAW_PLL_SFT) +#define NAU8360_SAW_PLL_RST (0x0 << NAU8360_SAW_PLL_SFT) +#define NAU8360_CLASSD_SHT_SFT 6 +#define NAU8360_CLASSD_SHT_IN (0x1 << NAU8360_CLASSD_SHT_SFT) +#define NAU8360_HVEN_SYNC_SFT 5 +#define NAU8360_HVEN_SYNC_SAW (0x1 << NAU8360_HVEN_SYNC_SFT) +#define NAU8360_SYNC_SAW_CLK_SFT 4 +#define NAU8360_SYNC_SAW_CLK_INVT (0x1 << NAU8360_SYNC_SAW_CLK_SFT) +#define NAU8360_DEPOP_CAP_SFT 2 +#define NAU8360_DEPOP_CAP_MASK (0x3 << NAU8360_DEPOP_CAP_SFT) +#define NAU8360_DEPOP_CAP_48PF (0x3 << NAU8360_DEPOP_CAP_SFT) +#define NAU8360_DEPOP_CAP_36PF (0x2 << NAU8360_DEPOP_CAP_SFT) +#define NAU8360_DEPOP_CAP_24PF (0x1 << NAU8360_DEPOP_CAP_SFT) +#define NAU8360_DEPOP_CAP_12PF (0x0 << NAU8360_DEPOP_CAP_SFT) +#define NAU8360_SW_BG_IO_SFT 1 +#define NAU8360_SW_BG_IO_CONN (0x1 << NAU8360_SW_BG_IO_SFT) +#define NAU8360_SW_VPTC_IO_EN 0x1 + +/* NAU8360 VBAT Range Thresholds */ +#define NAU8360_VBAT_MIN 8 +#define NAU8360_VBAT_MID_THRES 13 +#define NAU8360_VBAT_HIGH_THRES 19 +#define NAU8360_VBAT_MAX 24 + +/* NAU8360_R100_LEFT_BIQ0_COE (0x100) + * NAU8360_R200_RIGHT_BIQ0_COE (0x200) + */ +#define NAU8360_TOT_BAND_PER_CH 15 +#define NAU8360_TOT_BAND_COE 10 +#define NAU8360_TOT_BAND_COE_RANGE (NAU8360_TOT_BAND_COE + 2) +#define NAU8360_PEQ_REG_WIDTH (NAU8360_TOT_BAND_COE - 1) + +/* DSP stereo core */ +#define NAU8360_DSP_CORE_NUM 2 +#define NAU8360_DSP_ADDR_BYNAME(x) \ + (strstr((x), "Left") ? NAU8360_RF000_DSP_COMM : NAU8360_RF002_DSP_COMM) + +#define NAU8360_I2C_REG_LEN 2 +#define NAU8360_I2C_RXBUF_LEN 4 +#define NAU8360_I2C_TXBUF_LEN 6 +#define NAU8360_CODEC_DAI "nau8360-hifi" + +/* clock source */ +enum { + NAU8360_CLK_SRC_MCLK, + NAU8360_CLK_SRC_PLL, + NAU8360_CLK_SRC_ICLK, + NAU8360_CLK_SRC_BCLK, +}; + +/* clock target */ +enum { + NAU8360_CLK_ID_DIG, /* DIG_SYS + DSP + HW */ + NAU8360_CLK_ID_ANA, /* IV + DAC */ + NAU8360_CLK_ID_INT, /* HIRC48M standby */ +}; + +enum { + NAU8360_MCLK_FS_RATIO_250 = 250, + NAU8360_MCLK_FS_RATIO_256 = 256, + NAU8360_MCLK_FS_RATIO_400 = 400, + NAU8360_MCLK_FS_RATIO_500 = 500, + NAU8360_MCLK_FS_RATIO_512 = 512, +}; + +enum { + NAU8360_PEQ_BAND_8 = 8, + NAU8360_PEQ_BAND_12 = 12, + NAU8360_PEQ_BAND_15 = 15, +}; + +#define NAU8360_TDM_MAX_CHAN 8 +#define TDM_SLOT_NONE 255 + +enum { + NAU8360_TDM_DACL, + NAU8360_TDM_DACR, + NAU8360_TDM_ANCL, + NAU8360_TDM_ANCR, + NAU8360_TDM_RXN, +}; + +enum { + NAU8360_TDM_AECL, + NAU8360_TDM_AECR, + NAU8360_TDM_ISNSL, + NAU8360_TDM_ISNSR, + NAU8360_TDM_VSNSL, + NAU8360_TDM_VSNSR, + NAU8360_TDM_TJ, + NAU8360_TDM_VBAT, + NAU8360_TDM_TXN, +}; + +/* PLL Source */ +enum { + NAU8360_PLL_MCLK, + NAU8360_PLL_BCLK, + NAU8360_PLL_INTERNAL, +}; + +struct nau8360_pll { + int src; + int input; + int output; + int msel; + int rsel; + int nsel; + int xsel; +}; + +struct nau8360 { + struct device *dev; + struct regmap *regmap; + struct mutex lock; + struct snd_soc_dapm_context *dapm; + int sys_clk; + int anc_enable; + int pbtl_enable; + int dac_cur_enable; + int kcs_setup_size; + struct nau8360_pll pll; + u32 tdm_tx_func_slot[NAU8360_TDM_TXN]; + u32 tdm_rx_func_slot[NAU8360_TDM_RXN]; + u8 i2c_read_reg[NAU8360_I2C_REG_LEN] ____cacheline_aligned; + u8 i2c_read_buf[NAU8360_I2C_RXBUF_LEN] ____cacheline_aligned; + u8 i2c_write_buf[NAU8360_I2C_TXBUF_LEN] ____cacheline_aligned; + struct work_struct load_fw_work; + const char *dsp_firmware[NAU8360_DSP_CORE_NUM]; + bool load_fw_done; +}; + +#endif /* __NAU8360_H__ */ From 2cf55c64e64e1426def6f358d655ed036f2b9250 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 10:52:55 +0200 Subject: [PATCH 019/924] ASoC: codecs: lpass: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/8f05a164722791336be39c83164a45b7a2d56d1f.1787239902.git.geert+renesas@glider.be Signed-off-by: Mark Brown --- sound/soc/codecs/lpass-rx-macro.c | 2 +- sound/soc/codecs/lpass-tx-macro.c | 2 +- sound/soc/codecs/lpass-va-macro.c | 2 +- sound/soc/codecs/lpass-wsa-macro.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/lpass-rx-macro.c b/sound/soc/codecs/lpass-rx-macro.c index 282a6aaa9986..2e74ec1a52f8 100644 --- a/sound/soc/codecs/lpass-rx-macro.c +++ b/sound/soc/codecs/lpass-rx-macro.c @@ -3732,8 +3732,8 @@ static int rx_macro_register_mclk_output(struct rx_macro *rx) struct device *dev = rx->dev; const char *parent_clk_name = NULL; const char *clk_name = "lpass-rx-mclk"; + struct clk_init_data init = {}; struct clk_hw *hw; - struct clk_init_data init; int ret; if (rx->npl) diff --git a/sound/soc/codecs/lpass-tx-macro.c b/sound/soc/codecs/lpass-tx-macro.c index a8e83eb60ba2..4ac8a11f3062 100644 --- a/sound/soc/codecs/lpass-tx-macro.c +++ b/sound/soc/codecs/lpass-tx-macro.c @@ -2210,8 +2210,8 @@ static int tx_macro_register_mclk_output(struct tx_macro *tx) struct device *dev = tx->dev; const char *parent_clk_name = NULL; const char *clk_name = "lpass-tx-mclk"; + struct clk_init_data init = {}; struct clk_hw *hw; - struct clk_init_data init; int ret; if (tx->npl) diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c index dbc5795b9273..a3b3af0d7db3 100644 --- a/sound/soc/codecs/lpass-va-macro.c +++ b/sound/soc/codecs/lpass-va-macro.c @@ -1414,7 +1414,7 @@ static int va_macro_register_fsgen_output(struct va_macro *va) struct device_node *np = dev->of_node; const char *parent_clk_name; const char *clk_name = "fsgen"; - struct clk_init_data init; + struct clk_init_data init = {}; int ret; if (va->has_npl_clk) diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c index cfd2ac0a6cda..f6ef7f188b1a 100644 --- a/sound/soc/codecs/lpass-wsa-macro.c +++ b/sound/soc/codecs/lpass-wsa-macro.c @@ -2638,10 +2638,10 @@ static const struct clk_ops swclk_gate_ops = { static int wsa_macro_register_mclk_output(struct wsa_macro *wsa) { + struct clk_init_data init = {}; struct device *dev = wsa->dev; const char *parent_clk_name; struct clk_hw *hw; - struct clk_init_data init; int ret; if (wsa->npl) From 738a163b7ad5a32c3b2e1d9aec5a55dcb5183ae3 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 10:52:56 +0200 Subject: [PATCH 020/924] ASoC: codecs: wcd934x: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/560811bcde4b8459b5e96c131e50a94ee75664fd.1787239902.git.geert+renesas@glider.be Signed-off-by: Mark Brown --- sound/soc/codecs/wcd934x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wcd934x.c b/sound/soc/codecs/wcd934x.c index caca5cc25100..0ed24cca088f 100644 --- a/sound/soc/codecs/wcd934x.c +++ b/sound/soc/codecs/wcd934x.c @@ -2130,9 +2130,9 @@ static struct clk *wcd934x_register_mclk_output(struct wcd934x_codec *wcd) struct device *dev = wcd->dev; struct device_node *np = dev->parent->of_node; const char *parent_clk_name = NULL; + struct clk_init_data init = {}; const char *clk_name = "mclk"; struct clk_hw *hw; - struct clk_init_data init; int ret; if (of_property_read_u32(np, "clock-frequency", &wcd->rate)) From f1b2a0434b5bdf858ce2f837e55312cf7435ab8c Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 10:52:57 +0200 Subject: [PATCH 021/924] ASoC: tlv320aic32x4: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/7acfe448928df59a62418233d716b0c9806559d5.1787239902.git.geert+renesas@glider.be Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic32x4-clk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/tlv320aic32x4-clk.c b/sound/soc/codecs/tlv320aic32x4-clk.c index deed61650e09..ad15ce9c6079 100644 --- a/sound/soc/codecs/tlv320aic32x4-clk.c +++ b/sound/soc/codecs/tlv320aic32x4-clk.c @@ -451,8 +451,8 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { static struct clk *aic32x4_register_clk(struct device *dev, struct aic32x4_clkdesc *desc) { - struct clk_init_data init; struct clk_aic32x4 *priv; + struct clk_init_data init = {}; const char *devname = dev_name(dev); init.ops = desc->ops; From a13515c7f1d52238cde3bb986610cd12b2fa87bf Mon Sep 17 00:00:00 2001 From: Jonathan Seth Mainguy Date: Fri, 28 Aug 2026 20:49:58 -0400 Subject: [PATCH 022/924] ALSA: usb-audio: add Roland OCTA-CAPTURE multirate support The OCTA-CAPTURE provides one alternate setting for each supported sample rate. Add the 48, 96 and 192 kHz playback and capture settings. The existing S32_LE description causes the white-noise playback reported by Niklas Aldervall and other users. The device sends packed 24-bit samples, but S32_LE makes ALSA interpret each sample as a four-byte value, which misaligns the stream. USB captures and PCM streaming tests confirm that S24_3LE is the correct format for every setting. The device provides 10 playback and 12 capture channels through 96 kHz, and four channels in each direction at 192 kHz. Correct the product name in the commented override. Tested on a physical OCTA-CAPTURE with full-duplex streams at 44.1, 48, 96 and 192 kHz. Reported-by: Niklas Aldervall Closes: https://lore.kernel.org/alsa-devel/6b623cb6-3a9b-4fe4-a57c-9a165d261692@aldervall.se/ Signed-off-by: Jonathan Seth Mainguy Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260829005001.534571-2-jon@soh.re --- sound/usb/quirks-table.h | 104 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 4 deletions(-) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index dd02c6294edf..ef618a0f18d3 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -1227,15 +1227,15 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - /* only 44.1 kHz works at the moment */ + /* S24_3LE; altsettings 1-4 match the device clock. */ USB_DEVICE(0x0582, 0x0120), QUIRK_DRIVER_INFO { /* .vendor_name = "Roland", */ - /* .product_name = "OCTO-CAPTURE", */ + /* .product_name = "OCTA-CAPTURE", */ QUIRK_DATA_COMPOSITE { { QUIRK_DATA_AUDIOFORMAT(0) { - .formats = SNDRV_PCM_FMTBIT_S32_LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 10, .iface = 0, .altsetting = 1, @@ -1249,9 +1249,57 @@ YAMAHA_DEVICE(0x7010, "UB99"), .rate_table = (unsigned int[]) { 44100 } } }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 0, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_192000, + .rate_min = 192000, + .rate_max = 192000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 192000 } + } + }, { QUIRK_DATA_AUDIOFORMAT(1) { - .formats = SNDRV_PCM_FMTBIT_S32_LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 12, .iface = 1, .altsetting = 1, @@ -1265,6 +1313,54 @@ YAMAHA_DEVICE(0x7010, "UB99"), .rate_table = (unsigned int[]) { 44100 } } }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 12, + .iface = 1, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 12, + .iface = 1, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 1, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_192000, + .rate_min = 192000, + .rate_max = 192000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 192000 } + } + }, { QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0001, From a334881bcb59274df61301db8f3eec3b51fc181c Mon Sep 17 00:00:00 2001 From: Jonathan Seth Mainguy Date: Fri, 28 Aug 2026 20:49:59 -0400 Subject: [PATCH 023/924] ALSA: usb-audio: expose OCTA-CAPTURE control MIDI cables The OCTA-CAPTURE uses cable 0 for MIDI. Control requests use cable 2, and replies use cable 1. Expose output cables 0 and 2 with mask 0x0005, and input cables 0 and 1 with mask 0x0003. Tested on a physical OCTA-CAPTURE. Control requests sent on cable 2 received matching readback replies on cable 1, while cable 0 remained available for MIDI. Signed-off-by: Jonathan Seth Mainguy Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260829005001.534571-3-jon@soh.re --- sound/usb/quirks-table.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index ef618a0f18d3..35eef9cf834b 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -1363,8 +1363,8 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { - .out_cables = 0x0001, - .in_cables = 0x0001 + .out_cables = 0x0005, + .in_cables = 0x0003 } }, { QUIRK_DATA_IGNORE(3) }, From b4801a387ca396b3dd442ea9801794a5bc3ba91e Mon Sep 17 00:00:00 2001 From: Jonathan Seth Mainguy Date: Fri, 28 Aug 2026 20:50:00 -0400 Subject: [PATCH 024/924] ALSA: usb-audio: add Roland QUAD-CAPTURE multirate support The QUAD-CAPTURE provides alternate settings for 44.1, 48, 96 and 192 kHz. Its fixed quirk currently describes only 44.1 kHz. Add the remaining playback and capture settings. The device provides four playback and six capture channels through 96 kHz, and two channels in each direction at 192 kHz. Each setting uses an S32_LE container. Tested on a physical QUAD-CAPTURE with full-duplex streams at 44.1, 48, 96 and 192 kHz. Signed-off-by: Jonathan Seth Mainguy Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260829005001.534571-4-jon@soh.re --- sound/usb/quirks-table.h | 98 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 35eef9cf834b..a1a33f11de4a 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -1374,7 +1374,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - /* only 44.1 kHz works at the moment */ + /* S32_LE; altsettings 1-4 match the device clock. */ USB_DEVICE(0x0582, 0x012f), QUIRK_DRIVER_INFO { /* .vendor_name = "Roland", */ @@ -1396,6 +1396,54 @@ YAMAHA_DEVICE(0x7010, "UB99"), .rate_table = (unsigned int[]) { 44100 } } }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 4, + .iface = 0, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 4, + .iface = 0, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .iface = 0, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_192000, + .rate_min = 192000, + .rate_max = 192000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 192000 } + } + }, { QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S32_LE, @@ -1412,6 +1460,54 @@ YAMAHA_DEVICE(0x7010, "UB99"), .rate_table = (unsigned int[]) { 44100 } } }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 6, + .iface = 1, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 6, + .iface = 1, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .iface = 1, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_192000, + .rate_min = 192000, + .rate_max = 192000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 192000 } + } + }, { QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0001, From bdeed0642157667bd14912241c83ffd50f9fec22 Mon Sep 17 00:00:00 2001 From: Jonathan Seth Mainguy Date: Fri, 28 Aug 2026 20:50:01 -0400 Subject: [PATCH 025/924] ALSA: usb-audio: set Roland Capture rate during stream preparation Roland OCTA-CAPTURE and QUAD-CAPTURE select their hardware clock with a vendor control request. Streaming fails when the hardware clock differs from the rate selected for the USB endpoint. During stream preparation, read the hardware rate and send the rate request only when it differs from the selected endpoint rate. The control transfer can complete before the device reports the new clock rate, so poll readback every 25 ms and continue as soon as it matches. Forty attempts avoid hammering the control endpoint while bounding an unresponsive transition to one second. Run the transaction from the existing format-setup quirk, after ALSA has selected the endpoint rate and before data URBs are submitted. Hold the USB-audio device mutex so playback and capture preparation cannot issue overlapping clock changes. Apply the quirk to: 0582:0120 Roland OCTA-CAPTURE 0582:012f Roland QUAD-CAPTURE Tested on physical OCTA-CAPTURE and QUAD-CAPTURE units. Rate changes followed by full-duplex streaming succeeded at 44.1, 48, 96 and 192 kHz on both. External digital clocking on the OCTA-CAPTURE was tested at all supported rates: 44.1, 48 and 96 kHz. The OCTA-CAPTURE disables its digital input and output at 192 kHz, so external clocking is unavailable at that rate. Signed-off-by: Jonathan Seth Mainguy Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260829005001.534571-5-jon@soh.re --- sound/usb/quirks.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 91938172912a..4936d66f9591 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -3,6 +3,7 @@ */ #include +#include #include #include #include @@ -1855,6 +1856,75 @@ static int rme_digiface_set_format_quirk(struct snd_usb_substream *subs) return 0; } +#define ROLAND_CAPTURE_RATE_REQUEST 3 +#define ROLAND_CAPTURE_RATE_READ_VALUE 0x0001 +#define ROLAND_CAPTURE_RATE_WRITE_VALUE 0x0008 +#define ROLAND_CAPTURE_RATE_WRITE_PREFIX 0x40 +#define ROLAND_CAPTURE_RATE_RETRIES 40 +#define ROLAND_CAPTURE_RATE_POLL_MS 25 + +/* + * OCTA-CAPTURE and QUAD-CAPTURE use the same vendor request for their + * hardware clock. A read returns the 24-bit little-endian rate followed by + * a transition-status byte. A write carries 0x40 followed by the rate. + */ +static int roland_capture_read_rate(struct usb_device *dev, u32 *rate) +{ + u8 data[4]; + int err; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + ROLAND_CAPTURE_RATE_REQUEST, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + ROLAND_CAPTURE_RATE_READ_VALUE, 0, + data, sizeof(data)); + if (err != sizeof(data)) + return err < 0 ? err : -EIO; + + *rate = combine_triple(data); + return 0; +} + +static void roland_capture_set_rate(struct snd_usb_substream *subs) +{ + struct snd_usb_audio *chip = subs->stream->chip; + struct usb_device *dev = chip->dev; + u32 rate = subs->data_endpoint->cur_rate; + u32 current_rate; + u8 data[4]; + int err; + int i; + + /* Serialize playback and capture endpoint starts during a clock change. */ + guard(mutex)(&chip->mutex); + err = roland_capture_read_rate(dev, ¤t_rate); + if (!err && current_rate == rate) + return; + + data[0] = ROLAND_CAPTURE_RATE_WRITE_PREFIX; + data[1] = rate; + data[2] = rate >> 8; + data[3] = rate >> 16; + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + ROLAND_CAPTURE_RATE_REQUEST, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + ROLAND_CAPTURE_RATE_WRITE_VALUE, 0, + data, sizeof(data)); + if (err != sizeof(data)) { + usb_audio_warn(chip, "cannot set Roland sample rate to %u Hz: %d\n", + rate, err < 0 ? err : -EIO); + return; + } + + for (i = 0; i < ROLAND_CAPTURE_RATE_RETRIES; i++) { + err = roland_capture_read_rate(dev, ¤t_rate); + if (!err && current_rate == rate) + return; + msleep(ROLAND_CAPTURE_RATE_POLL_MS); + } + usb_audio_warn(chip, "Roland sample rate did not reach %u Hz\n", rate); +} + void snd_usb_set_format_quirk(struct snd_usb_substream *subs, const struct audioformat *fmt) { @@ -1885,6 +1955,10 @@ void snd_usb_set_format_quirk(struct snd_usb_substream *subs, case USB_ID(0x2a39, 0x3fa0): /* RME Digiface USB (alternate) */ rme_digiface_set_format_quirk(subs); break; + case USB_ID(0x0582, 0x0120): /* Roland OCTA-CAPTURE */ + case USB_ID(0x0582, 0x012f): /* Roland QUAD-CAPTURE */ + roland_capture_set_rate(subs); + break; } } From e152ac60589bcb6a5743930fe7a83ea2e9ffbf4f Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Mon, 31 Aug 2026 21:32:40 +1000 Subject: [PATCH 026/924] ASoC: tas2764: Initialise SDZ GPIO low On Apple Silicon machines integrating TAS2764, the SDZ pin of all codecs is connected to a shared GPIO line. The kernel's shared GPIO infrastructure relies on the first change of the GPIO state being marked as its "default" state, and allows any consumer of the shared line to assert that state without consensus from other consumers. If the pin is initialised high, the shared GPIO core does not allow the line to be asserted without consensus. This breaks resuming from suspend on Apple Silicon machines; the driver attempts to restore the codec's register state and fails because other codecs are still holding the line low. Initialise the SDZ GPIO low so that the first state change on probe asserts it high, allowing any codec to pull the line up when it needs to. Signed-off-by: James Calligeros Link: https://patch.msgid.link/20260831-apple-shared-gpio-v1-2-e855b12e18ce@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2764.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c index f9c65c9f6d0a..b3c08d106dd7 100644 --- a/sound/soc/codecs/tas2764.c +++ b/sound/soc/codecs/tas2764.c @@ -960,7 +960,7 @@ static int tas2764_parse_dt(struct device *dev, struct tas2764_priv *tas2764) } } - tas2764->sdz_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH); + tas2764->sdz_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_LOW); if (IS_ERR(tas2764->sdz_gpio)) { if (PTR_ERR(tas2764->sdz_gpio) == -EPROBE_DEFER) return -EPROBE_DEFER; From 87e41baa36fcd9a166e237067a85d80b5c1c4069 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Mon, 31 Aug 2026 21:32:41 +1000 Subject: [PATCH 027/924] ASoC: tas2770: Initialise SDZ GPIO low On Apple Silicon machines integrating TAS2770, the SDZ pin of all codecs is connected to a shared GPIO line. The kernel's shared GPIO infrastructure relies on the first change of the GPIO state being marked as its "default" state, and allows any consumer of the shared line to assert that state without consensus from other consumers. If the pin is initialised high, the shared GPIO core does not allow the line to be asserted without consensus. This breaks resuming from suspend on Apple Silicon machines; the driver attempts to restore the codec's register state and fails because other codecs are still holding the line low. Initialise the SDZ GPIO low so that the first state change on probe asserts it high, allowing any codec to pull the line up when it needs to. Signed-off-by: James Calligeros Link: https://patch.msgid.link/20260831-apple-shared-gpio-v1-3-e855b12e18ce@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2770.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c index e79754edac5c..ecc37d4ac00b 100644 --- a/sound/soc/codecs/tas2770.c +++ b/sound/soc/codecs/tas2770.c @@ -886,7 +886,7 @@ static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *tas2770) if (rc) tas2770->pdm_slot = -1; - tas2770->sdz_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH); + tas2770->sdz_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_LOW); if (IS_ERR(tas2770->sdz_gpio)) { if (PTR_ERR(tas2770->sdz_gpio) == -EPROBE_DEFER) return -EPROBE_DEFER; From bf71d5c64cd0e0e02689f755832142e5048ec0fe Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 18 Aug 2026 17:45:26 +0700 Subject: [PATCH 028/924] ASoC: mediatek: mt7986: Drop redundant probe error messages The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() calls are redundant and can be removed. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260818104526.37904-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt7986/mt7986-afe-pcm.c | 8 ++++---- sound/soc/mediatek/mt7986/mt7986-dai-etdm.c | 2 +- sound/soc/mediatek/mt7986/mt7986-wm8960.c | 4 +--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/sound/soc/mediatek/mt7986/mt7986-afe-pcm.c b/sound/soc/mediatek/mt7986/mt7986-afe-pcm.c index 7a6ad9116e55..e7ecb2761b4d 100644 --- a/sound/soc/mediatek/mt7986/mt7986-afe-pcm.c +++ b/sound/soc/mediatek/mt7986/mt7986-afe-pcm.c @@ -334,7 +334,7 @@ static int mt7986_init_clock(struct mtk_base_afe *afe) ret = devm_clk_bulk_get(afe->dev, afe_priv->num_clks, afe_priv->clks); if (ret) - return dev_err_probe(afe->dev, ret, "Failed to get clocks\n"); + return ret; return 0; } @@ -414,7 +414,7 @@ static int mt7986_afe_runtime_resume(struct device *dev) ret = clk_bulk_prepare_enable(afe_priv->num_clks, afe_priv->clks); if (ret) - return dev_err_probe(afe->dev, ret, "Failed to enable clocks\n"); + return ret; if (!afe->regmap || afe_priv->pm_runtime_bypass_reg_ctl) return 0; @@ -484,7 +484,7 @@ static int mt7986_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt7986_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "Cannot initialize clocks\n"); + return ret; ret = devm_pm_runtime_enable(dev); if (ret) @@ -535,7 +535,7 @@ static int mt7986_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt7986_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); if (ret) - return dev_err_probe(dev, ret, "Failed to request irq for asys-isr\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); diff --git a/sound/soc/mediatek/mt7986/mt7986-dai-etdm.c b/sound/soc/mediatek/mt7986/mt7986-dai-etdm.c index fc55ff47b7bc..49f5b2bf565f 100644 --- a/sound/soc/mediatek/mt7986/mt7986-dai-etdm.c +++ b/sound/soc/mediatek/mt7986/mt7986-dai-etdm.c @@ -124,7 +124,7 @@ static int mtk_dai_etdm_startup(struct snd_pcm_substream *substream, ret = clk_bulk_prepare_enable(afe_priv->num_clks, afe_priv->clks); if (ret) - return dev_err_probe(afe->dev, ret, "Failed to enable clocks\n"); + return ret; regmap_update_bits(afe->regmap, AUDIO_TOP_CON2, CLK_OUT5_PDN_MASK, 0); regmap_update_bits(afe->regmap, AUDIO_TOP_CON2, CLK_IN5_PDN_MASK, 0); diff --git a/sound/soc/mediatek/mt7986/mt7986-wm8960.c b/sound/soc/mediatek/mt7986/mt7986-wm8960.c index f1dc18222be7..4517af9e2d14 100644 --- a/sound/soc/mediatek/mt7986/mt7986-wm8960.c +++ b/sound/soc/mediatek/mt7986/mt7986-wm8960.c @@ -135,10 +135,8 @@ static int mt7986_wm8960_machine_probe(struct platform_device *pdev) } ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); - if (ret) { - dev_err(&pdev->dev, "Failed to parse audio-routing: %d\n", ret); + if (ret) goto err_of_node_put; - } ret = devm_snd_soc_register_card(&pdev->dev, card); if (ret) { From 17bca5d55446c1e21944d47354ee2f0b80db36d6 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Sun, 23 Aug 2026 11:09:55 +0200 Subject: [PATCH 029/924] ASoC: wcd-mbhc-v2: Fix incorrect properties names and typos Improves debugging experience. Fixes: 0e5c9e7ff899 ("ASoC: codecs: wcd: add multi button Headset detection support") Signed-off-by: David Heidelberg Link: https://patch.msgid.link/20260823-wcd-dt-typo-fix-v1-1-a71b458e0d31@ixit.cz Signed-off-by: Mark Brown --- sound/soc/codecs/wcd-mbhc-v2.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/wcd-mbhc-v2.c b/sound/soc/codecs/wcd-mbhc-v2.c index bb0c8478a8eb..850be84d65ce 100644 --- a/sound/soc/codecs/wcd-mbhc-v2.c +++ b/sound/soc/codecs/wcd-mbhc-v2.c @@ -864,7 +864,7 @@ static int wcd_measure_adc_continuous(struct wcd_mbhc *mbhc) u8 adc_en; /* Pre-requisites for ADC continuous measurement */ - /* Read legacy electircal detection and disable */ + /* Read legacy electrical detection and disable */ wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC, 0x00); /* Set ADC to continuous measurement */ wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_MODE, 1); @@ -965,7 +965,7 @@ static int wcd_check_cross_conn(struct wcd_mbhc *mbhc) if (wcd_mbhc_read_field(mbhc, WCD_MBHC_HPH_PA_EN)) return -EINVAL; - /* Read legacy electircal detection and disable */ + /* Read legacy electrical detection and disable */ elect_ctl = wcd_mbhc_read_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC); wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC, 0); @@ -1470,14 +1470,14 @@ int wcd_dt_parse_mbhc_data(struct device *dev, struct wcd_mbhc_config *cfg) ret = of_property_read_u32(np, "qcom,mbhc-headset-vthreshold-microvolt", µvolt); if (ret) - dev_dbg(dev, "missing qcom,mbhc-hs-mic-max-vthreshold--microvolt in dt node\n"); + dev_dbg(dev, "missing qcom,mbhc-headset-vthreshold-microvolt in dt node\n"); else cfg->hs_thr = microvolt/1000; ret = of_property_read_u32(np, "qcom,mbhc-headphone-vthreshold-microvolt", µvolt); if (ret) - dev_dbg(dev, "missing qcom,mbhc-hs-mic-min-vthreshold-microvolt entry\n"); + dev_dbg(dev, "missing qcom,mbhc-headphone-vthreshold-microvolt entry\n"); else cfg->hph_thr = microvolt/1000; From 27bb680f95568c6e319fc35fe9ddc1a952e87e33 Mon Sep 17 00:00:00 2001 From: Zain Aboobacker Date: Mon, 24 Aug 2026 16:19:51 -0400 Subject: [PATCH 030/924] ASoC: dt-bindings: foursemi,fs2105s: Fix typo Correct the misspelling of "generated" in the FS2105S binding. Signed-off-by: Zain Aboobacker Link: https://patch.msgid.link/20260824201951.3116-1-zainaboobacker33@gmail.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml b/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml index 4da735317e0f..17c938a565e5 100644 --- a/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml +++ b/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml @@ -63,7 +63,7 @@ properties: a. Register initialization settings b. DSP effect parameters c. Multi-scene sound effect configurations(optional) - It's gernerated by FourSemi's tuning tool. + It's generated by FourSemi's tuning tool. required: - compatible From caae0e2d17185a2489365f3f97d8a5b09a9da638 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 14 Aug 2026 13:50:18 -0700 Subject: [PATCH 031/924] ASoC: tegra: ADX: convert map to flexible array Replace the pointer-based map allocation with a flexible array member and use struct_size() for allocation. This simplifies the code and avoid an extra allocation. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Reviewed-by: Thierry Reding Link: https://patch.msgid.link/20260814205018.39015-1-rosenp@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_adx.c | 12 ++++-------- sound/soc/tegra/tegra210_adx.h | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/sound/soc/tegra/tegra210_adx.c b/sound/soc/tegra/tegra210_adx.c index e75bf50f8fb0..f2adf1c6e37e 100644 --- a/sound/soc/tegra/tegra210_adx.c +++ b/sound/soc/tegra/tegra210_adx.c @@ -676,11 +676,13 @@ static int tegra210_adx_platform_probe(struct platform_device *pdev) void __iomem *regs; int err, i; - adx = devm_kzalloc(dev, sizeof(*adx), GFP_KERNEL); + soc_data = of_device_get_match_data(dev); + adx = devm_kzalloc(dev, + struct_size(adx, map, soc_data->ram_depth * TEGRA_ADX_SLOTS_PER_WORD), + GFP_KERNEL); if (!adx) return -ENOMEM; - soc_data = of_device_get_match_data(dev); adx->soc_data = soc_data; dev_set_drvdata(dev, adx); @@ -697,12 +699,6 @@ static int tegra210_adx_platform_probe(struct platform_device *pdev) regcache_cache_only(adx->regmap, true); - adx->map = devm_kcalloc(dev, - soc_data->ram_depth * TEGRA_ADX_SLOTS_PER_WORD, - sizeof(*adx->map), GFP_KERNEL); - if (!adx->map) - return -ENOMEM; - adx->byte_mask = devm_kcalloc(dev, soc_data->byte_mask_size, sizeof(*adx->byte_mask), GFP_KERNEL); if (!adx->byte_mask) diff --git a/sound/soc/tegra/tegra210_adx.h b/sound/soc/tegra/tegra210_adx.h index a6298c3dcca5..e5f1b44b2e76 100644 --- a/sound/soc/tegra/tegra210_adx.h +++ b/sound/soc/tegra/tegra210_adx.h @@ -92,8 +92,8 @@ struct tegra210_adx_soc_data { struct tegra210_adx { struct regmap *regmap; unsigned int *byte_mask; - u16 *map; const struct tegra210_adx_soc_data *soc_data; + u16 map[]; }; #endif From 12d9c9f8679c56e8c131dc2d8985c350ece7c4c3 Mon Sep 17 00:00:00 2001 From: Qianghua Wang Date: Tue, 1 Sep 2026 11:00:47 +0800 Subject: [PATCH 032/924] ASoC: codecs: add SN624x SDCA SoundWire driver Add a SoundWire SDCA driver for Senary SN624x multi-function codecs (jack, speaker amp, and DMIC). Program SDCA SampleFreqIndex in hw_params, keep SDCA jack IRQs masked with poll-based detection, and extend the Senary MAINTAINERS entry for the new codec files only. Signed-off-by: Qianghua Wang Link: https://patch.msgid.link/0aa5fde4e0213362322fd9d0d50ed9c28c40fc09.1788230065.git.qianghua.wang@senarytech.com Signed-off-by: Mark Brown --- MAINTAINERS | 3 + sound/soc/codecs/Kconfig | 13 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sn624x-sdca-sdw.c | 1888 ++++++++++++++++++++++++++++ sound/soc/codecs/sn624x-sdca.h | 152 +++ 5 files changed, 2058 insertions(+) create mode 100644 sound/soc/codecs/sn624x-sdca-sdw.c create mode 100644 sound/soc/codecs/sn624x-sdca.h diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..e06f747a46f5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -30180,9 +30180,12 @@ F: tools/testing/selftests/cgroup/test_zswap.c SENARYTECH AUDIO CODEC DRIVER M: bo liu +M: qianghua.wang S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git F: sound/hda/codecs/senarytech.c +F: sound/soc/codecs/sn624x* +F: sound/soc/sdw_utils/soc_sdw_senary* ZTE DINGHAI ETHERNET DRIVER M: Junyang Han diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f9a47e262a77..62224dc19cab 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -255,6 +255,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_SIMPLE_MUX imply SND_SOC_SMA1303 imply SND_SOC_SMA1307 + imply SND_SOC_SN624X_SDCA_SDW imply SND_SOC_SPDIF imply SND_SOC_SRC4XXX_I2C imply SND_SOC_SSM2305 @@ -2099,6 +2100,18 @@ config SND_SOC_SMA1307 If you are using a system with an SMA1307 amplifier connected via I2C, enable this option. +config SND_SOC_SN624X_SDCA_SDW + tristate "Senary SN624x SDCA Codec - SDW" + depends on SND_SOC_SDCA + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + help + Enable support for Senary SN624x SDCA SoundWire codecs. + These multi-function devices provide headset jack, speaker + amplifier, and digital microphone endpoints on the SoundWire + bus. + config SND_SOC_SPDIF tristate "S/PDIF CODEC" diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 3d122ace75ad..3b8d7594b80f 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -301,6 +301,7 @@ snd-soc-sigmadsp-regmap-y := sigmadsp-regmap.o snd-soc-si476x-y := si476x.o snd-soc-sma1303-y := sma1303.o snd-soc-sma1307-y := sma1307.o +snd-soc-sn624x-sdca-y := sn624x-sdca-sdw.o snd-soc-spdif-tx-y := spdif_transmitter.o snd-soc-spdif-rx-y := spdif_receiver.o snd-soc-src4xxx-y := src4xxx.o @@ -742,6 +743,7 @@ obj-$(CONFIG_SND_SOC_SIGMADSP_REGMAP) += snd-soc-sigmadsp-regmap.o obj-$(CONFIG_SND_SOC_SI476X) += snd-soc-si476x.o obj-$(CONFIG_SND_SOC_SMA1303) += snd-soc-sma1303.o obj-$(CONFIG_SND_SOC_SMA1307) += snd-soc-sma1307.o +obj-$(CONFIG_SND_SOC_SN624X_SDCA_SDW) += snd-soc-sn624x-sdca.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif-rx.o snd-soc-spdif-tx.o obj-$(CONFIG_SND_SOC_SRC4XXX) += snd-soc-src4xxx.o obj-$(CONFIG_SND_SOC_SRC4XXX_I2C) += snd-soc-src4xxx-i2c.o diff --git a/sound/soc/codecs/sn624x-sdca-sdw.c b/sound/soc/codecs/sn624x-sdca-sdw.c new file mode 100644 index 000000000000..c9e690482e8e --- /dev/null +++ b/sound/soc/codecs/sn624x-sdca-sdw.c @@ -0,0 +1,1888 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// sn624x-sdw-sdca.c -- SN624X SDCA ALSA SoC SoundWire audio driver +// +// Copyright(c) 2025 Senary Semiconductor Corp. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sn624x-sdca.h" + +/* Resume wait after bus re-enumeration (rt722-style probe timeout) */ +#define SN624X_PROBE_TIMEOUT_MS 5000U +#define SN624X_PWR_D0 SDCA_PDE_PS0 +#define SN624X_PWR_D3 SDCA_PDE_PS3 +#define SN624X_MUTE_ON 1 +/* + * Budget for ACTUAL_PS poll via sdca_asoc_pde_poll_actual_ps() (100 polls). + * Matches previous ~2s hand-rolled loop for slow PDE transitions. + */ +#define SN624X_PDE_PS_POLL_BUDGET_US 2000000U + +/* + * Prefer ACPI mipi-sdca-function-initialization-table via + * sdca_regmap_write_init(). If absent or DisCo parse is incomplete, + * a temporary vendor-window OEM fallback is used until firmware tables land. + * + * Jack detection is poll-driven (UAJ pulse + GE35); SDCA INTMASK bits stay + * masked so interrupt storms cannot schedule extra work. + */ + +struct sn624x_sdca_priv; + +static void sn624x_sdca_sdca_irq_mask_all(struct sn624x_sdca_priv *sn624x); +static int sn624x_uaj_ge35_apply_detected_mode(struct device *dev, + struct sn624x_sdca_priv *sn624x); +#define SN624X_DBG(dev, fmt, ...) dev_dbg(dev, "sn624x: " fmt, ##__VA_ARGS__) + +/* + * SDCA control addresses via SDW_SDCA_CTL(fun, ent, ctl, ch). + * Pass ch=0 for single-value controls (power/rate/status); use CH_L/CH_R + * only for multi-channel FU mute/volume. CH_ENABLE_* are not SDCA Control + * Prefix addresses — keep as raw windows. + */ +/* Speaker amp (function 4) — PDE/FU power/mute/vol; rate on CS01 */ +#define SN624X_REG_PWR_STATE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_PDE03, \ + SDCA_CTL_PDE_ACTUAL_PS, 0) +#define SN624X_REG_PWR_STATE_WRITE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_PDE03, \ + SDCA_CTL_PDE_REQUESTED_PS, 0) +#define SN624X_REG_RATE_SEL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_CS01, \ + SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX, 0) +#define SN624X_REG_CH_ENABLE_A 0x00000220u +#define SN624X_REG_CH_ENABLE_B 0x00000230u +#define SN624X_REG_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_USER_FU10, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_L) +#define SN624X_REG_CHR_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_USER_FU10, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_R) +#define SN624X_REG_MUTE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_USER_FU10, \ + SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_L) +#define SN624X_REG_RIGHT_MUTE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_USER_FU10, \ + SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_R) + +/* Jack playback (function 1) */ +#define SN624X_REG_JACK_OUT_PWR_STATE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_PDE03, \ + SDCA_CTL_PDE_ACTUAL_PS, 0) +#define SN624X_REG_JACK_OUT_PWR_STATE_WRITE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_PDE03, \ + SDCA_CTL_PDE_REQUESTED_PS, 0) +#define SN624X_REG_JACK_OUT_RATE_SEL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_CS01, \ + SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX, 0) +#define SN624X_REG_JACK_OUT_CH_ENABLE_A 0x00000120u +#define SN624X_REG_JACK_OUT_CH_ENABLE_B 0x00000130u +#define SN624X_REG_JACK_OUT_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_L) +#define SN624X_REG_JACK_OUT_CHR_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_R) +#define SN624X_REG_JACK_OUT_MUTE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_L) +#define SN624X_REG_JACK_OUT_CLUSTER_INDEX \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_CRU05, \ + SN624X_SDCA_CTL_CLUSTER_INDEX, 0) + +/* Jack headset capture (function 1) */ +#define SN624X_REG_JACK_CAP_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU1E, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_L) +#define SN624X_REG_JACK_CAP_CHR_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU1E, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_R) +#define SN624X_REG_JACK_CAP_MUTE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU1E, \ + SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_L) +#define SN624X_REG_JACK_CAP_PWR_STATE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_PDE04, \ + SDCA_CTL_PDE_ACTUAL_PS, 0) +#define SN624X_REG_JACK_CAP_PWR_STATE_WRITE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_PDE04, \ + SDCA_CTL_PDE_REQUESTED_PS, 0) +#define SN624X_REG_JACK_CAP_RATE_SEL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_CS02, \ + SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX, 0) +#define SN624X_REG_JACK_CAP_CH_ENABLE_A 0x00000420u /* low bits: 0x3=both */ +#define SN624X_REG_JACK_CAP_CH_ENABLE_B 0x00000430u + +#define SN624X_REG_JACK_FUN_STATUS_CTL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_ENTITY0, \ + SN624X_SDCA_CTL_FUNCTION_STATUS, 0) +#define SN624X_JACK_FUN_NEEDS_INIT BIT(5) +#define SN624X_JACK_FUN_HAS_BEEN_RESET BIT(6) + +/* DMIC capture (function 2) */ +#define SN624X_REG_DMIC_CAP_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_L) +#define SN624X_REG_DMIC_CAP_CHR_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_R) +/* Q7.8 channel volume: integer in bits 15:8, fraction in bits 7:0 */ +#define SN624X_VOL_Q78(int_part, frac_part) \ + ((((unsigned int)(int_part) & 0xff) << 8) | ((unsigned int)(frac_part) & 0xff)) +#define SN624X_REG_DMIC_CAP_MUTE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_L) +#define SN624X_REG_DMIC_CAP_CHR_MUTE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_R) +#define SN624X_REG_DMIC_CAP_RATE_SEL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_CS02, \ + SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX, 0) +/* OEM sheet used ctl=0; SDCA Actual PS is 0x10 — poll the standard selector */ +#define SN624X_REG_DMIC_CAP_PWR_STATE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_PDE03, \ + SDCA_CTL_PDE_ACTUAL_PS, 0) +#define SN624X_REG_DMIC_CAP_PWR_STATE_WRITE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_PDE03, \ + SDCA_CTL_PDE_REQUESTED_PS, 0) +#define SN624X_REG_DMIC_CAP_CH_ENABLE_A 0x00000520u /* low bits: 0x3=both */ +#define SN624X_REG_DMIC_CAP_CH_ENABLE_B 0x00000530u + +#define SN624X_REG_DMIC_FUN_STATUS_CTL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_ENTITY0, \ + SN624X_SDCA_CTL_FUNCTION_STATUS, 0) + +/* + * Write REQUESTED_PS then poll ACTUAL_PS with the shared SDCA helper + * (sdca_asoc_pde_poll_actual_ps). Caller must not hand-roll ACTUAL_PS waits. + */ +static int sn624x_pde_request_ps(struct device *dev, struct regmap *regmap, + unsigned int fun, unsigned int ent, + unsigned int ps); + +/* + * DAPM PDE supplies (rt711/rt712 style): write REQUESTED_PS then poll ACTUAL_PS. + * Mute-before-D3 is handled by FU widget PRE_PMD, which runs before SUPPLY PRE_PMD + * when the FU lists the PDE as a dependency. + */ +static int sn624x_dapm_pde_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event, + unsigned int fun, unsigned int ent) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct sn624x_sdca_priv *sn624x = + snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + int ret; + + if (!sn624x || !sn624x->regmap) + return -ENODEV; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + ret = sn624x_pde_request_ps(dev, sn624x->regmap, fun, ent, + SN624X_PWR_D0); + if (ret < 0) + dev_warn(dev, + "sn624x: DAPM %s PDE fun=%u ent=0x%x -> PS0 failed (%d)\n", + w->name, fun, ent, ret); + break; + case SND_SOC_DAPM_PRE_PMD: + ret = sn624x_pde_request_ps(dev, sn624x->regmap, fun, ent, + SN624X_PWR_D3); + if (ret < 0) + dev_warn(dev, + "sn624x: DAPM %s PDE fun=%u ent=0x%x -> PS3 failed (%d)\n", + w->name, fun, ent, ret); + break; + default: + return 0; + } + return ret; +} + +static int sn624x_pde_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_dapm_pde_event(w, kcontrol, event, + SN624X_FUNC_NUM_JACK_CODEC, + SN624X_SDCA_ENT_PDE03); +} + +static int sn624x_pde_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_dapm_pde_event(w, kcontrol, event, + SN624X_FUNC_NUM_SPEAKER_AMP, + SN624X_SDCA_ENT_PDE03); +} + +static int sn624x_pde_mic2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_dapm_pde_event(w, kcontrol, event, + SN624X_FUNC_NUM_JACK_CODEC, + SN624X_SDCA_ENT_PDE04); +} + +static int sn624x_pde_dmic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_dapm_pde_event(w, kcontrol, event, + SN624X_FUNC_NUM_MIC_ARRAY, + SN624X_SDCA_ENT_PDE03); +} + +static int sn624x_fu_mute_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event, + unsigned int mute_l, unsigned int mute_r, + unsigned int vol_l, unsigned int vol_r, + unsigned int vol_val) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct sn624x_sdca_priv *sn624x = + snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + int ret; + + if (!sn624x || !sn624x->regmap) + return -ENODEV; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (vol_l) { + ret = regmap_write(sn624x->regmap, vol_l, vol_val); + if (ret < 0) + dev_warn(dev, "sn624x: DAPM %s vol(L) failed (%d)\n", + w->name, ret); + } + if (vol_r) { + ret = regmap_write(sn624x->regmap, vol_r, vol_val); + if (ret < 0) + dev_warn(dev, "sn624x: DAPM %s vol(R) failed (%d)\n", + w->name, ret); + } + ret = regmap_write(sn624x->regmap, mute_l, 0); + if (ret < 0) + dev_warn(dev, "sn624x: DAPM %s unmute(L) failed (%d)\n", + w->name, ret); + if (mute_r) { + ret = regmap_write(sn624x->regmap, mute_r, 0); + if (ret < 0) + dev_warn(dev, "sn624x: DAPM %s unmute(R) failed (%d)\n", + w->name, ret); + } + break; + case SND_SOC_DAPM_PRE_PMD: + /* Mute and settle before PDE SUPPLY PRE_PMD takes the domain to PS3 */ + ret = regmap_write(sn624x->regmap, mute_l, SN624X_MUTE_ON); + if (ret < 0) + dev_warn(dev, "sn624x: DAPM %s mute(L) failed (%d)\n", + w->name, ret); + if (mute_r) { + ret = regmap_write(sn624x->regmap, mute_r, SN624X_MUTE_ON); + if (ret < 0) + dev_warn(dev, "sn624x: DAPM %s mute(R) failed (%d)\n", + w->name, ret); + } + usleep_range(SN624X_ROUTE_AFTER_MUTE_US_MIN, + SN624X_ROUTE_AFTER_MUTE_US_MAX); + break; + default: + break; + } + return 0; +} + +static int sn624x_fu_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_fu_mute_event(w, kcontrol, event, + SN624X_REG_JACK_OUT_MUTE, 0, + SN624X_REG_JACK_OUT_VOL, + SN624X_REG_JACK_OUT_CHR_VOL, 0); +} + +static int sn624x_fu_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_fu_mute_event(w, kcontrol, event, + SN624X_REG_MUTE, SN624X_REG_RIGHT_MUTE, + SN624X_REG_VOL, SN624X_REG_CHR_VOL, 0); +} + +static int sn624x_fu_mic2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct sn624x_sdca_priv *sn624x = + snd_soc_component_get_drvdata(component); + int ret; + + if (event == SND_SOC_DAPM_POST_PMU && sn624x) + sn624x_uaj_ge35_apply_detected_mode(component->dev, sn624x); + + ret = sn624x_fu_mute_event(w, kcontrol, event, + SN624X_REG_JACK_CAP_MUTE, 0, + SN624X_REG_JACK_CAP_VOL, + SN624X_REG_JACK_CAP_CHR_VOL, + SN624X_VOL_Q78(6, 0)); + return ret; +} + +static int sn624x_fu_dmic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_fu_mute_event(w, kcontrol, event, + SN624X_REG_DMIC_CAP_MUTE, + SN624X_REG_DMIC_CAP_CHR_MUTE, + SN624X_REG_DMIC_CAP_VOL, + SN624X_REG_DMIC_CAP_CHR_VOL, + SN624X_VOL_Q78(6, 0)); +} + +static int sn624x_pde_request_ps(struct device *dev, struct regmap *regmap, + unsigned int fun, unsigned int ent, + unsigned int ps) +{ + static const struct sdca_pde_delay delays[] = { + { .from_ps = SDCA_PDE_PS0, .to_ps = SDCA_PDE_PS3, + .us = SN624X_PDE_PS_POLL_BUDGET_US }, + { .from_ps = SDCA_PDE_PS3, .to_ps = SDCA_PDE_PS0, + .us = SN624X_PDE_PS_POLL_BUDGET_US }, + { .from_ps = SDCA_PDE_PS1, .to_ps = SDCA_PDE_PS0, + .us = SN624X_PDE_PS_POLL_BUDGET_US }, + { .from_ps = SDCA_PDE_PS2, .to_ps = SDCA_PDE_PS0, + .us = SN624X_PDE_PS_POLL_BUDGET_US }, + { .from_ps = SDCA_PDE_PS0, .to_ps = SDCA_PDE_PS0, + .us = SN624X_PDE_PS_POLL_BUDGET_US }, + { .from_ps = SDCA_PDE_PS3, .to_ps = SDCA_PDE_PS3, + .us = SN624X_PDE_PS_POLL_BUDGET_US }, + }; + unsigned int req = SDW_SDCA_CTL(fun, ent, SDCA_CTL_PDE_REQUESTED_PS, 0); + unsigned int act = SDW_SDCA_CTL(fun, ent, SDCA_CTL_PDE_ACTUAL_PS, 0); + unsigned int from_ps = SDCA_PDE_PS3; + int ret; + + ret = regmap_read(regmap, act, &from_ps); + if (ret < 0) { + dev_warn(dev, + "sn624x: PDE fun=%u ent=0x%x ACTUAL_PS read failed (%d)\n", + fun, ent, ret); + from_ps = (ps == SN624X_PWR_D0) ? SN624X_PWR_D3 : SN624X_PWR_D0; + } else { + from_ps &= 0xffu; + } + + ret = regmap_write(regmap, req, ps); + if (ret < 0) { + dev_warn(dev, + "sn624x: PDE fun=%u ent=0x%x REQUESTED_PS=%u write failed (%d)\n", + fun, ent, ps, ret); + return ret; + } + + ret = sdca_asoc_pde_poll_actual_ps(regmap, fun, ent, + from_ps, ps, + delays, ARRAY_SIZE(delays)); + if (ret < 0) + dev_warn(dev, + "sn624x: PDE fun=%u ent=0x%x ACTUAL_PS poll failed (%d; want %u)\n", + fun, ent, ret, ps); + return ret; +} + +static void sn624x_jack_type_from_det_mode(struct sn624x_sdca_priv *sn624x, + unsigned int det_mode) +{ + switch (det_mode) { + case 0x00: + sn624x->jack_type = 0; + break; + case 0x03: + sn624x->jack_type = SND_JACK_HEADPHONE; + break; + case 0x05: + sn624x->jack_type = SND_JACK_HEADSET; + break; + default: + SN624X_DBG(&sn624x->slave->dev, + "unknown GE35 detected_mode 0x%x\n", det_mode); + sn624x->jack_type = 0; + break; + } +} + +static int sn624x_uaj_ge35_select_mode(struct device *dev, + struct sn624x_sdca_priv *sn624x, + unsigned int mode) +{ + int ret; + + ret = regmap_write(sn624x->regmap, + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, + SN624X_SDCA_ENT_GE35, + SN624X_SDCA_CTL_SELECTED_MODE, 0), + mode); + if (ret < 0) + dev_warn(dev, + "sn624x: jack_cap: GE35 SELECTED_MODE=0x%x failed (%d)\n", + mode, ret); + else + dev_dbg(dev, + "sn624x: jack_cap: GE35 SELECTED_MODE=0x%x\n", + mode); + return ret; +} + +/* + * Follow DETECTED_MODE → SELECTED_MODE (rt711/rt722 pattern). Never force + * headset: mic path is only valid when the jack hardware reports one. + * Also updates sn624x->jack_type from DETECTED_MODE. + */ +static int sn624x_uaj_ge35_apply_detected_mode(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + unsigned int det_mode = 0; + int ret; + + ret = regmap_read(sn624x->regmap, + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, + SN624X_SDCA_ENT_GE35, + SN624X_SDCA_CTL_DETECTED_MODE, 0), + &det_mode); + if (ret < 0) { + dev_warn(dev, + "sn624x: jack_cap: GE35 DETECTED_MODE read failed (%d)\n", + ret); + return ret; + } + + dev_dbg(dev, "sn624x: jack_cap: GE35 DETECTED_MODE=0x%x\n", det_mode); + sn624x_jack_type_from_det_mode(sn624x, det_mode); + + if (!det_mode) { + dev_dbg(dev, + "sn624x: jack_cap: no jack detected — skip SELECTED_MODE\n"); + return 0; + } + + return sn624x_uaj_ge35_select_mode(dev, sn624x, det_mode); +} + +static const char *sn624x_status_str(enum sdw_slave_status status) +{ + switch (status) { + case SDW_SLAVE_UNATTACHED: + return "UNATTACHED"; + case SDW_SLAVE_ATTACHED: + return "ATTACHED"; + case SDW_SLAVE_ALERT: + return "ALERT"; + case SDW_SLAVE_RESERVED: + return "RESERVED"; + default: + return "UNKNOWN"; + } +} + +static int sn624x_sdca_mbq_size(struct device *dev, unsigned int reg) +{ + if (!SDW_SDCA_VALID_CTL(reg)) + return 1; + + /* + * FU_VOLUME and GE35 DETECTED_MODE share control selector 0x02. + * Only list known FU volume addresses as 16-bit MBQ — matching + * DETECTED_MODE by csel alone makes GE35 use SDW_SDCA_MBQ_CTL and + * fails with -ENODATA (-61), which breaks jack plug/unplug. + */ + switch (reg) { + case SN624X_REG_VOL: + case SN624X_REG_CHR_VOL: + case SN624X_REG_JACK_OUT_VOL: + case SN624X_REG_JACK_OUT_CHR_VOL: + case SN624X_REG_JACK_CAP_VOL: + case SN624X_REG_JACK_CAP_CHR_VOL: + case SN624X_REG_DMIC_CAP_VOL: + case SN624X_REG_DMIC_CAP_CHR_VOL: + return 2; + default: + return 1; + } +} + +static bool sn624x_sdca_volatile_register(struct device *dev, unsigned int reg) +{ + if (!SDW_SDCA_VALID_CTL(reg)) + return true; + + switch (reg) { + case SN624X_REG_PWR_STATE: + case SN624X_REG_JACK_OUT_PWR_STATE: + case SN624X_REG_JACK_CAP_PWR_STATE: + case SN624X_REG_DMIC_CAP_PWR_STATE: + case SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_GE35, + SN624X_SDCA_CTL_DETECTED_MODE, 0): + return true; + default: + if (SDW_SDCA_CTL_CSEL(reg) == SDCA_CTL_PDE_ACTUAL_PS) + return true; + return false; + } +} + +static const struct regmap_sdw_mbq_cfg sn624x_sdca_mbq_cfg = { + .mbq_size = sn624x_sdca_mbq_size, +}; + +static const struct regmap_config sn624x_sdca_regmap = { + .reg_bits = 32, + .val_bits = 16, + .volatile_reg = sn624x_sdca_volatile_register, + .max_register = 0xffffffff, + .cache_type = REGCACHE_MAPLE, + .use_single_read = true, + .use_single_write = true, +}; + +/* + * Prefer ACPI mipi-sdca-function-initialization-table (sdca_regmap_write_init). + * sdca_parse_function() loads that table before entity parsing, so a later + * DisCo parse failure can still leave fn->num_init_table usable. + */ +static int sn624x_write_sdca_init_table(struct device *dev, + struct sn624x_sdca_priv *sn624x, + struct sdca_function_data *fn, + const char *label) +{ + int ret; + + if (!fn || !fn->num_init_table) + return -ENOENT; + + ret = sdca_regmap_write_init(dev, sn624x->regmap, fn); + if (ret < 0) { + dev_warn(dev, + "sn624x: %s function-initialization-table write failed (%d)\n", + label, ret); + return ret; + } + + dev_info(dev, + "sn624x: applied %s function-initialization-table (%d writes)\n", + label, fn->num_init_table); + return 0; +} + +/* + * Temporary vendor-window fallback when ACPI has no init table. + * Validated on current platforms; remove once DisCo init tables are complete. + */ +static int sn624x_jack_oem_fallback_init(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + static const struct reg_sequence jack_oem_table[] = { + REG_SEQ0(SN624X_UAJ_CTL_IT33_MIC_BIAS, 0x06), + REG_SEQ0(SN624X_UAJ_CTL_IT31_MIC_BIAS, 0x06), + REG_SEQ0(SN624X_UAJ_CTL_FU41_CH1_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU41_CH2_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU31_CH0_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU32_CH0_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU33_CH0_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU36_CH1_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU36_CH2_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU31_CH0_GAIN_HIGH, 0x12), + REG_SEQ0(SN624X_UAJ_CTL_FU31_CH0_GAIN_LOW, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU32_CH0_GAIN_HIGH, 0x0C), + REG_SEQ0(SN624X_UAJ_CTL_FU32_CH0_GAIN_LOW, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU33_CH0_GAIN_HIGH, 0x12), + REG_SEQ0(SN624X_UAJ_CTL_FU33_CH0_GAIN_LOW, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_INPUT_DELAY_TIME, 0x0F), + REG_SEQ0(SN624X_UAJ_CTL_PORTA_CHARGE_PUMP, 0x0B), + }; + int ret; + + dev_warn_once(dev, + "sn624x: using temporary jack OEM init fallback (no ACPI init table)\n"); + ret = regmap_multi_reg_write_bypassed(sn624x->regmap, jack_oem_table, + ARRAY_SIZE(jack_oem_table)); + if (ret < 0) { + dev_warn(dev, "sn624x: jack OEM fallback failed (%d)\n", ret); + return ret; + } + usleep_range(1000, 1500); + return 0; +} + +static int sn624x_dmic_oem_fallback_init(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + static const struct reg_sequence dmic_oem_table[] = { + REG_SEQ0(0x00002041, 0x00), + REG_SEQ0(SN624X_UAJ_DMIC_PPU11, 0x30), + REG_SEQ0(0x00002105, 0x66), + REG_SEQ0(0x00002107, 0x26), + REG_SEQ0(0x00002109, 0x62), + REG_SEQ0(0x40800a09, 0x00), + REG_SEQ0(0x40800a0a, 0x00), + REG_SEQ0(0x40802a11, 0x00), + REG_SEQ0(0x40800a11, 0x00), + REG_SEQ0(0x40802a12, 0x00), + REG_SEQ0(0x40800a12, 0x00), + REG_SEQ0(0x40802a13, 0x00), + REG_SEQ0(0x40800a13, 0x00), + REG_SEQ0(0x40802a14, 0x00), + REG_SEQ0(0x40800a14, 0x00), + REG_SEQ0(0x408029d8, 0x0c), + REG_SEQ0(0x408009d8, 0x00), + REG_SEQ0(0x408029d9, 0x0c), + REG_SEQ0(0x408009d9, 0x00), + REG_SEQ0(0x00002140, 0x18), + REG_SEQ0(0x00002045, 0x01), + }; + int ret; + + dev_warn_once(dev, + "sn624x: using temporary dmic OEM init fallback (no ACPI init table)\n"); + ret = regmap_multi_reg_write_bypassed(sn624x->regmap, dmic_oem_table, + ARRAY_SIZE(dmic_oem_table)); + if (ret < 0) { + dev_warn(dev, "sn624x: dmic OEM fallback failed (%d)\n", ret); + return ret; + } + usleep_range(1000, 1500); + return 0; +} + +static void sn624x_uaj_apply_io_defaults(struct sn624x_sdca_priv *sn624x) +{ + struct device *dev = &sn624x->slave->dev; + int ret; + + ret = sn624x_write_sdca_init_table(dev, sn624x, sn624x->jack_func, + "jack"); + if (ret) + sn624x_jack_oem_fallback_init(dev, sn624x); +} + +static int sn624x_parse_sdca_functions(struct sn624x_sdca_priv *sn624x) +{ + struct sdw_slave *slave = sn624x->slave; + struct device *dev = &slave->dev; + int i, ret; + + if (!slave->sdca_data.num_functions) { + dev_dbg(dev, "sn624x: no SDCA function descriptors from DisCo\n"); + return 0; + } + + for (i = 0; i < slave->sdca_data.num_functions; i++) { + struct sdca_function_desc *desc = &slave->sdca_data.function[i]; + struct sdca_function_data *fn; + bool is_jack = false, is_mic = false, is_amp = false; + + switch (desc->type) { + case SDCA_FUNCTION_TYPE_UAJ: + case SDCA_FUNCTION_TYPE_SIMPLE_JACK: + case SDCA_FUNCTION_TYPE_RJ: + is_jack = true; + break; + case SDCA_FUNCTION_TYPE_SMART_MIC: + case SDCA_FUNCTION_TYPE_SIMPLE_MIC: + is_mic = true; + break; + case SDCA_FUNCTION_TYPE_SMART_AMP: + case SDCA_FUNCTION_TYPE_SIMPLE_AMP: + case SDCA_FUNCTION_TYPE_SPEAKER_MIC: + is_amp = true; + break; + default: + break; + } + + if (desc->adr == SN624X_FUNC_NUM_JACK_CODEC) + is_jack = true; + else if (desc->adr == SN624X_FUNC_NUM_MIC_ARRAY) + is_mic = true; + else if (desc->adr == SN624X_FUNC_NUM_SPEAKER_AMP) + is_amp = true; + + if (!is_jack && !is_mic && !is_amp) + continue; + + fn = devm_kzalloc(dev, sizeof(*fn), GFP_KERNEL); + if (!fn) + return -ENOMEM; + + fn->desc = desc; + ret = sdca_parse_function(dev, fn); + if (ret) { + /* + * sdca_parse_function() reads the ACPI + * mipi-sdca-function-initialization-table first, + * then parses entities/controls. On some platforms + * DisCo entity data is incomplete so the later + * steps fail (-EINVAL), but fn->init_table is + * already filled. Keep that partial function so + * io_init can still apply the ACPI table; if the + * table was never loaded, drop this function and + * let the OEM register fallback run instead. + */ + dev_warn(dev, + "sn624x: sdca_parse_function(%s adr=%u) failed (%d)%s\n", + desc->name ? desc->name : "?", desc->adr, ret, + fn->num_init_table ? + ", keeping ACPI init table" : ""); + if (!fn->num_init_table) + continue; + } else { + dev_dbg(dev, + "sn624x: parsed SDCA function %s type=%u adr=%u init_writes=%d\n", + desc->name ? desc->name : "?", desc->type, + desc->adr, fn->num_init_table); + } + + if (is_jack && !sn624x->jack_func) + sn624x->jack_func = fn; + else if (is_mic && !sn624x->mic_func) + sn624x->mic_func = fn; + else if (is_amp && !sn624x->amp_func) + sn624x->amp_func = fn; + } + + return 0; +} + +/* + * Poll-driven jack detect: pulse UAJ jack-detect, then apply GE35 + * DETECTED_MODE → SELECTED_MODE via sn624x_uaj_ge35_apply_detected_mode(). + */ +static int sn624x_sdca_headset_detect(struct sn624x_sdca_priv *sn624x) +{ + struct device *dev = &sn624x->slave->dev; + int ret; + + SN624X_DBG(dev, "headset_detect: pulse UAJ jack-detect, read GE35\n"); + ret = regmap_write(sn624x->regmap, + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, + SN624X_UAJ_ENT_BLOCK, + SN624X_UAJ_CTL_JACK_DETECT, 0), + 0x08); + if (ret < 0) + goto io_error; + + usleep_range(5000, 5500); + + ret = sn624x_uaj_ge35_apply_detected_mode(dev, sn624x); + + /* Clear the detect pulse even if GE35 apply failed. */ + regmap_write(sn624x->regmap, + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_UAJ_ENT_BLOCK, + SN624X_UAJ_CTL_JACK_DETECT, 0), + 0x00); + + if (ret < 0) + goto io_error; + + SN624X_DBG(dev, "headset_detect: jack_type=0x%x\n", sn624x->jack_type); + return 0; + +io_error: + if (ret == -ENODATA) + SN624X_DBG(dev, "%s: UAJ/GE35 access ignored (-ENODATA)\n", + __func__); + else + dev_err_ratelimited(dev, "%s failed (%d)\n", __func__, ret); + return ret; +} + +static void sn624x_sdca_sdca_irq_mask_all(struct sn624x_sdca_priv *sn624x) +{ + if (!sn624x->slave) + return; + + sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK1, 0); + sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK2, 0); + sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK3, 0); + sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK4, 0); + SN624X_DBG(&sn624x->slave->dev, + "SDCA INTMASK1-4 = 0 (all SDCA sources masked)\n"); +} + +static void sn624x_sdca_jack_irq_unmask(struct sn624x_sdca_priv *sn624x) +{ + /* Jack detection is poll-driven; SDCA jack IRQs remain masked. */ + (void)sn624x; +} + +static void sn624x_sdca_jack_irq_mask(struct sn624x_sdca_priv *sn624x) +{ + if (!sn624x->slave) + return; + + sn624x_sdca_sdca_irq_mask_all(sn624x); +} + +/* + * Hold a runtime PM reference for the lifetime of jack detection so the + * device cannot autosuspend while the poll worker needs register access. + */ +static int sn624x_jack_rpm_get(struct sn624x_sdca_priv *sn624x) +{ + struct device *dev; + int ret; + + if (!sn624x) + return -EINVAL; + if (sn624x->jack_rpm) + return 0; + if (!sn624x->component) + return -ENODEV; + + dev = sn624x->component->dev; + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + if (ret != -EACCES) { + dev_err(dev, "sn624x: jack rpm get failed (%d)\n", ret); + return ret; + } + /* pm_runtime not enabled yet (before first ATTACHED io_init) */ + return 0; + } + + sn624x->jack_rpm = true; + return 0; +} + +static void sn624x_jack_rpm_put(struct sn624x_sdca_priv *sn624x) +{ + if (!sn624x || !sn624x->jack_rpm || !sn624x->component) + return; + + pm_runtime_mark_last_busy(sn624x->component->dev); + pm_runtime_put_autosuspend(sn624x->component->dev); + sn624x->jack_rpm = false; +} + +static void sn624x_jack_schedule_poll(struct sn624x_sdca_priv *sn624x) +{ + if (!sn624x || !sn624x->hs_jack || !sn624x->slave) + return; + + mod_delayed_work(system_power_efficient_wq, &sn624x->jack_detect_work, + msecs_to_jiffies(SN624X_JACK_POLL_MS)); +} + +static void sn624x_sdca_jack_poll_and_report(struct sn624x_sdca_priv *sn624x) +{ + struct device *dev; + + if (!sn624x->hs_jack || !sn624x->slave) + return; + + dev = &sn624x->slave->dev; + if (sn624x_sdca_headset_detect(sn624x) < 0) + return; + + snd_soc_jack_report(sn624x->hs_jack, sn624x->jack_type, + SND_JACK_HEADSET | SND_JACK_HEADPHONE); + sn624x->jack_type_last = sn624x->jack_type; + dev_dbg(dev, "sn624x: jack polled: detected_mode -> jack_type=0x%x\n", + sn624x->jack_type); +} + +static void sn624x_sdca_jack_detect_handler(struct work_struct *work) +{ + struct sn624x_sdca_priv *sn624x = + container_of(work, struct sn624x_sdca_priv, jack_detect_work.work); + struct device *dev; + + if (!sn624x->slave) { + sn624x_jack_schedule_poll(sn624x); + return; + } + + dev = &sn624x->slave->dev; + + if (!sn624x->hs_jack) { + SN624X_DBG(dev, "jack_work: skip (no hs_jack)\n"); + goto out_reschedule; + } + + /* Registers are only valid after ATTACHED → io_init (hw_init). */ + if (!sn624x->hw_init) { + SN624X_DBG(dev, "jack_work: skip (hw_init not ready)\n"); + goto out_reschedule; + } + + /* Poll GE35 every interval; do not rely on SDCA_0 IRQ or PCM activity */ + sn624x_sdca_headset_detect(sn624x); + + if (sn624x->jack_type != sn624x->jack_type_last) { + dev_dbg(dev, + "sn624x: jack event: %s (jack_type=0x%x; HP=0x%x HS=0x%x)\n", + sn624x->jack_type ? "plug" : "unplug", + sn624x->jack_type, SND_JACK_HEADPHONE, SND_JACK_HEADSET); + sn624x->jack_type_last = sn624x->jack_type; + + snd_soc_jack_report(sn624x->hs_jack, sn624x->jack_type, + SND_JACK_HEADSET | SND_JACK_HEADPHONE); + } + +out_reschedule: + sn624x_jack_schedule_poll(sn624x); +} + +static void sn624x_sdca_jack_init(struct sn624x_sdca_priv *sn624x) +{ + /* No machine set_jack() yet — not "jack unsupported". */ + if (!sn624x->hs_jack) + return; + + sn624x_sdca_jack_irq_unmask(sn624x); + sn624x_sdca_jack_poll_and_report(sn624x); + sn624x_jack_schedule_poll(sn624x); + + dev_dbg(&sn624x->slave->dev, + "sn624x: jack_init: poll scheduled every %ums\n", SN624X_JACK_POLL_MS); +} + +static int sn624x_sdca_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack, void *data) +{ + struct sn624x_sdca_priv *sn624x = snd_soc_component_get_drvdata(component); + int ret; + + dev_dbg(component->dev, + "sn624x: set_jack: hs_jack=%p first_hw_init=%d hw_init=%d\n", + hs_jack, sn624x->first_hw_init, sn624x->hw_init); + + if (!hs_jack) { + cancel_delayed_work_sync(&sn624x->jack_detect_work); + sn624x->hs_jack = NULL; + sn624x_jack_rpm_put(sn624x); + return 0; + } + + /* + * Component registration happens before runtime PM is enabled (RPM is + * enabled from io_init on first ATTACHED). Do not touch runtime PM + * until then, or get/put counts can go out of sync. + */ + if (!sn624x->first_hw_init) { + sn624x->hs_jack = hs_jack; + sn624x->jack_type_last = -1; + sn624x_jack_schedule_poll(sn624x); + return 0; + } + + /* + * Take the runtime PM reference before publishing hs_jack, so a + * failed get cannot leave jack detection marked as enabled. + */ + ret = sn624x_jack_rpm_get(sn624x); + if (ret < 0) + return ret; + + sn624x->hs_jack = hs_jack; + sn624x->jack_type_last = -1; + sn624x_sdca_jack_init(sn624x); + return 0; +} + +/* Prefer ACPI init table; OEM vendor-window fallback only if absent. */ +static int sn624x_jack_apply_defaults(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + int ret; + + if (!sn624x || !sn624x->regmap || !sn624x->slave) + return -ENODEV; + + ret = sn624x_write_sdca_init_table(dev, sn624x, sn624x->jack_func, + "jack"); + if (ret == 0) { + usleep_range(1000, 1500); + return 0; + } + + return sn624x_jack_oem_fallback_init(dev, sn624x); +} + +static int sn624x_dmic_apply_defaults(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + int ret; + + if (!sn624x || !sn624x->regmap || !sn624x->slave) + return -ENODEV; + + ret = sn624x_write_sdca_init_table(dev, sn624x, sn624x->mic_func, + "dmic"); + if (ret == 0) { + usleep_range(1000, 1500); + return 0; + } + + return sn624x_dmic_oem_fallback_init(dev, sn624x); +} + +static int sn624x_sdca_jack_function_init(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + unsigned int jack_func_status; + const struct reg_sequence clear_status = + REG_SEQ0(SN624X_REG_JACK_FUN_STATUS_CTL, 0x61); + int ret; + + ret = regmap_read_bypassed(sn624x->regmap, + SN624X_REG_JACK_FUN_STATUS_CTL, + &jack_func_status); + if (ret < 0) + return ret; + + if (!(jack_func_status & (SN624X_JACK_FUN_NEEDS_INIT | + SN624X_JACK_FUN_HAS_BEEN_RESET))) + return 0; + + ret = sn624x_jack_apply_defaults(dev, sn624x); + if (ret < 0) + return ret; + + ret = regmap_multi_reg_write_bypassed(sn624x->regmap, &clear_status, 1); + if (ret < 0) + return ret; + + usleep_range(1000, 1500); + return 0; +} + +static int sn624x_sdca_dmic_function_init(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + unsigned int dmic_func_status; + const struct reg_sequence clear_status = + REG_SEQ0(SN624X_REG_DMIC_FUN_STATUS_CTL, 0x61); + int ret; + + ret = regmap_read_bypassed(sn624x->regmap, + SN624X_REG_DMIC_FUN_STATUS_CTL, + &dmic_func_status); + if (ret < 0) + return ret; + + if (!(dmic_func_status & (SN624X_JACK_FUN_NEEDS_INIT | + SN624X_JACK_FUN_HAS_BEEN_RESET))) + return 0; + + ret = sn624x_dmic_apply_defaults(dev, sn624x); + if (ret < 0) + return ret; + + ret = regmap_multi_reg_write_bypassed(sn624x->regmap, &clear_status, 1); + if (ret < 0) + return ret; + + usleep_range(1000, 1500); + return 0; +} + +static int sn624x_sdca_io_init(struct device *dev, struct sdw_slave *slave) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev); + int ret; + + if (sn624x->hw_init) { + SN624X_DBG(dev, "io_init: skip (hw_init already true)\n"); + return 0; + } + + SN624X_DBG(dev, "io_init: start (first hardware bring-up)\n"); + sn624x->disable_irq = false; + + sn624x_sdca_sdca_irq_mask_all(sn624x); + + regcache_cache_only(sn624x->regmap, false); + if (!sn624x->first_hw_init) { + /* + * PM runtime is only enabled when a Slave reports as Attached + * (same pattern as rt722). + */ + pm_runtime_set_autosuspend_delay(dev, 3000); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_enable(dev); + } + + pm_runtime_get_noresume(dev); + + sn624x_uaj_apply_io_defaults(sn624x); + + /* + * set_jack() may have run before RPM was enabled (-EACCES). Take the + * long-lived jack PM ref now that runtime PM is active. + */ + if (sn624x->hs_jack) { + sn624x_jack_rpm_get(sn624x); + sn624x_sdca_jack_init(sn624x); + } + + ret = sn624x_sdca_jack_function_init(dev, sn624x); + if (ret < 0) { + dev_err(dev, "sn624x: jack function init failed (%d)\n", ret); + goto err; + } + + ret = sn624x_sdca_dmic_function_init(dev, sn624x); + if (ret < 0) { + dev_err(dev, "sn624x: dmic function init failed (%d)\n", ret); + goto err; + } + + /* + * Re-attach after bus reset: force a full cache write-back on the + * following resume/sync path (same idea as rt722). + */ + if (sn624x->first_hw_init) + regcache_mark_dirty(sn624x->regmap); + + sn624x->hw_init = true; + sn624x->first_hw_init = true; + + pm_runtime_put_autosuspend(dev); + + SN624X_DBG(dev, "io_init: complete (hw_init set), jack %s\n", + sn624x->hs_jack ? "registered" : "not registered yet"); + return 0; + +err: + pm_runtime_put_noidle(dev); + return ret; +} + +/* + * Bus lifecycle (same as rt722): enumerate → assign dev_num → ATTACHED → + * io_init(). Do not poll enumeration/init around each register access; after + * unattach, resume uses sdw_slave_wait_for_init() then regcache_sync. + */ +static int sn624x_sdca_update_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(&slave->dev); + bool need_io_init; + int ret; + + SN624X_DBG(&slave->dev, "update_status: %s hw_init=%d dev_num=%u\n", + sn624x_status_str(status), sn624x->hw_init, slave->dev_num); + + if (status == SDW_SLAVE_UNATTACHED) { + cancel_delayed_work_sync(&sn624x->jack_detect_work); + sn624x->hw_init = false; + SN624X_DBG(&slave->dev, + "slave UNATTACHED: cleared hw_init, jack work cancelled\n"); + } + + if (status != SDW_SLAVE_ATTACHED) + return 0; + + sn624x_sdca_sdca_irq_mask_all(sn624x); + + need_io_init = !sn624x->hw_init; + if (need_io_init) { + dev_notice(&slave->dev, "sn624x: update_status ATTACHED -> io_init()\n"); + ret = sn624x_sdca_io_init(&slave->dev, slave); + if (ret < 0) + return ret; + } else { + dev_dbg(&slave->dev, + "sn624x: update_status ATTACHED: io_init already done\n"); + } + + /* Re-ATTACHED without io_init: restore jack detect after bus is up. */ + if (sn624x->hs_jack && sn624x->hw_init && !need_io_init) { + sn624x_sdca_jack_irq_unmask(sn624x); + sn624x_sdca_jack_poll_and_report(sn624x); + sn624x_jack_schedule_poll(sn624x); + } + + return 0; +} + +static int sn624x_sdca_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + struct sdw_dpn_prop *dpn; + unsigned long addr; + unsigned int bit; + int nval; + int i, j; + + sdw_slave_read_lane_mapping(slave); + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; + prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; + prop->paging_support = true; + prop->use_domain_irq = true; + prop->wake_capable = true; + + prop->source_ports = BIT(SN624X_PORT_JACK_CAPTURE) | + BIT(SN624X_PORT_DMIC_CAPTURE); + prop->sink_ports = BIT(SN624X_PORT_JACK_PLAYBACK) | + BIT(SN624X_PORT_SPEAKER_PLAYBACK); + + nval = hweight32(prop->source_ports); + prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->src_dpn_prop), GFP_KERNEL); + if (!prop->src_dpn_prop) + return -ENOMEM; + i = 0; + dpn = prop->src_dpn_prop; + addr = prop->source_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[i].num = bit; + dpn[i].type = SDW_DPN_FULL; + dpn[i].simple_ch_prep_sm = true; + dpn[i].ch_prep_timeout = 10; + i++; + } + nval = hweight32(prop->sink_ports); + prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->sink_dpn_prop), GFP_KERNEL); + if (!prop->sink_dpn_prop) + return -ENOMEM; + j = 0; + dpn = prop->sink_dpn_prop; + addr = prop->sink_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[j].num = bit; + dpn[j].type = SDW_DPN_FULL; + dpn[j].simple_ch_prep_sm = true; + dpn[j].ch_prep_timeout = 10; + j++; + } + prop->clk_stop_timeout = 900; + prop->simple_clk_stop_capable = true; + prop->lane_control_support = true; + SN624X_DBG(&slave->dev, + "read_prop: src_ports=0x%x sink_ports=0x%x lane irq paging\n", + prop->source_ports, prop->sink_ports); + + return 0; +} + +/* + * Clear SDCA INT1/2 only. DP0_INT standard fields (PORT_READY, etc.) are + * owned by the SoundWire bus core; cascade clears when SDCA status is clear. + * Pending SDCA IRQs can block clock-stop prepare (CLK_STP_NF -> -ETIMEDOUT). + */ +static int sn624x_sdca_flush_pending_irqs(struct sn624x_sdca_priv *sn624x, + struct device *dev) +{ + int ret, stat = 0; + int count = 0; + const int retry = 3; + unsigned int scp_sdca_stat1, scp_sdca_stat2 = 0; + + if (!sn624x || !sn624x->slave) + return 0; + + ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT1); + if (ret < 0) + return ret; + sn624x->scp_sdca_stat1 = ret; + + ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT2); + if (ret < 0) + return ret; + sn624x->scp_sdca_stat2 = ret; + + do { + ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT1); + if (ret < 0) + return ret; + if (ret & SDW_SCP_SDCA_INTMASK_SDCA_0) { + ret = sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INT1, + SDW_SCP_SDCA_INTMASK_SDCA_0); + if (ret < 0) + return ret; + } + + ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT2); + if (ret < 0) + return ret; + if (ret & SDW_SCP_SDCA_INTMASK_SDCA_8) { + ret = sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INT2, + SDW_SCP_SDCA_INTMASK_SDCA_8); + if (ret < 0) + return ret; + } + + ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT1); + if (ret < 0) + return ret; + scp_sdca_stat1 = ret & SDW_SCP_SDCA_INTMASK_SDCA_0; + + ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT2); + if (ret < 0) + return ret; + scp_sdca_stat2 = ret & SDW_SCP_SDCA_INTMASK_SDCA_8; + + stat = scp_sdca_stat1 || scp_sdca_stat2; + count++; + } while (stat != 0 && count < retry); + + if (stat && dev) + dev_warn(dev, + "sn624x: SDCA IRQ flush incomplete (stat=%u)\n", stat); + + return stat ? -EBUSY : 0; +} + +static int sn624x_sdca_interrupt_callback(struct sdw_slave *slave, + struct sdw_slave_intr_status *status) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(&slave->dev); + int ret; + + if (!sn624x) + return 0; + + /* + * Bus expects the codec to clear SCP SDCA INT1/2 when cascade is set. + * Leaving them set can stall alert handling. + */ + + SN624X_DBG(&slave->dev, + "irq: control_port=%#x sdca_cascade=%u disable_irq=%d\n", + status->control_port, status->sdca_cascade, sn624x->disable_irq); + + cancel_delayed_work_sync(&sn624x->jack_detect_work); + + mutex_lock(&sn624x->disable_irq_lock); + + ret = sn624x_sdca_flush_pending_irqs(sn624x, &slave->dev); + if (ret < 0 && ret != -EBUSY) + goto io_error; + + SN624X_DBG(&slave->dev, "irq: INT1=0x%x INT2=0x%x\n", + sn624x->scp_sdca_stat1, sn624x->scp_sdca_stat2); + + /* + * Always restart the jack poll after IRQ handling so a cancel above + * cannot stop detection. SDCA INTMASK stays masked; any residual SCP + * IRQ still triggers an early poll. The poll itself does the UAJ + * pulse + GE35 read. + */ + if (!sn624x->disable_irq) + sn624x_jack_schedule_poll(sn624x); + + mutex_unlock(&sn624x->disable_irq_lock); + return 0; + +io_error: + mutex_unlock(&sn624x->disable_irq_lock); + dev_err_ratelimited(&slave->dev, "%s: IO error (%d)\n", __func__, ret); + return ret; +} + +static const struct sdw_slave_ops sn624x_sdca_slave_ops = { + .read_prop = sn624x_sdca_read_prop, + .interrupt_callback = sn624x_sdca_interrupt_callback, + .update_status = sn624x_sdca_update_status, +}; + +static int sn624x_sdca_probe(struct snd_soc_component *component) +{ + struct sn624x_sdca_priv *sn624x = snd_soc_component_get_drvdata(component); + int ret; + + sn624x->component = component; + + if (!sn624x->first_hw_init) + return 0; + + ret = pm_runtime_resume(component->dev); + if (ret < 0 && ret != -EACCES) + return ret; + + return 0; +} + +static int sn624x_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct sn624x_sdca_priv *sn624x = snd_soc_component_get_drvdata(component); + struct sdw_stream_config stream_config = {}; + struct sdw_port_config port_config = {}; + struct sdw_stream_runtime *sdw_stream; + unsigned int sampling_rate; + int port; + int ret; + + dev_dbg(dai->dev, + "sn624x: hw_params: entered dai=%s id=%d stream=%s\n", + dai->name, dai->id, snd_pcm_stream_str(substream)); + + sdw_stream = snd_soc_dai_get_dma_data(dai, substream); + if (!sdw_stream) { + dev_warn(dai->dev, + "sn624x: hw_params: no SDW stream (set_stream not run yet?)\n"); + return -EINVAL; + } + if (!sn624x->slave) { + dev_warn(dai->dev, "sn624x: hw_params: slave NULL\n"); + return -EINVAL; + } + + ret = pm_runtime_resume(component->dev); + if (ret < 0 && ret != -EACCES) { + dev_err(dai->dev, + "sn624x: hw_params: pm_runtime_resume failed (%d)\n", ret); + return ret; + } + + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (dai->id == SN624X_DAI_JACK) + port = SN624X_PORT_JACK_PLAYBACK; + else if (dai->id == SN624X_DAI_SPEAKER) + port = SN624X_PORT_SPEAKER_PLAYBACK; + else + return -EINVAL; + } else { + if (dai->id == SN624X_DAI_JACK) + port = SN624X_PORT_JACK_CAPTURE; + else if (dai->id == SN624X_DAI_DMIC) + port = SN624X_PORT_DMIC_CAPTURE; + else + return -EINVAL; + } + port_config.num = port; + + ret = sdw_stream_add_slave(sn624x->slave, &stream_config, + &port_config, 1, sdw_stream); + if (ret) { + dev_err(dai->dev, "%s: sdw_stream_add_slave port %d failed: %d\n", + __func__, port, ret); + return ret; + } + + /* SDCA SampleFreqIndex (same encoding as other SDCA codecs) */ + switch (params_rate(params)) { + case 44100: + sampling_rate = SN624X_SDCA_RATE_44100HZ; + break; + case 48000: + sampling_rate = SN624X_SDCA_RATE_48000HZ; + break; + case 96000: + sampling_rate = SN624X_SDCA_RATE_96000HZ; + break; + case 192000: + sampling_rate = SN624X_SDCA_RATE_192000HZ; + break; + default: + dev_err(dai->dev, "%s: Rate %d is not supported\n", + __func__, params_rate(params)); + return -EINVAL; + } + + if (dai->id == SN624X_DAI_JACK) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = regmap_write(sn624x->regmap, + SN624X_REG_JACK_OUT_RATE_SEL, + sampling_rate); + else + ret = regmap_write(sn624x->regmap, + SN624X_REG_JACK_CAP_RATE_SEL, + sampling_rate); + } else if (dai->id == SN624X_DAI_SPEAKER) { + ret = regmap_write(sn624x->regmap, SN624X_REG_RATE_SEL, + sampling_rate); + } else if (dai->id == SN624X_DAI_DMIC) { + ret = regmap_write(sn624x->regmap, SN624X_REG_DMIC_CAP_RATE_SEL, + sampling_rate); + } else { + return -EINVAL; + } + + return ret; +} + +static int sn624x_sdca_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sn624x_sdca_priv *sn624x = snd_soc_component_get_drvdata(dai->component); + struct sdw_stream_runtime *sdw_stream = + snd_soc_dai_get_dma_data(dai, substream); + + if (!sn624x->slave) + return -EINVAL; + + return sdw_stream_remove_slave(sn624x->slave, sdw_stream); +} + +static int sn624x_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, + int direction) +{ + snd_soc_dai_dma_data_set(dai, direction, sdw_stream); + return 0; +} + +static void sn624x_sdca_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_soc_dai_set_dma_data(dai, substream, NULL); +} + +static const struct snd_soc_dapm_widget sn624x_sdca_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("RX", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX", "Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SPK RX", "Speaker Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DMIC TX", "DMic Capture", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SUPPLY("PDE HP", SND_SOC_NOPM, 0, 0, + sn624x_pde_hp_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("PDE SPK", SND_SOC_NOPM, 0, 0, + sn624x_pde_spk_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("PDE MIC2", SND_SOC_NOPM, 0, 0, + sn624x_pde_mic2_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("PDE DMIC", SND_SOC_NOPM, 0, 0, + sn624x_pde_dmic_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_DAC_E("FU HP", NULL, SND_SOC_NOPM, 0, 0, + sn624x_fu_hp_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_DAC_E("FU SPK", NULL, SND_SOC_NOPM, 0, 0, + sn624x_fu_spk_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("FU MIC2", NULL, SND_SOC_NOPM, 0, 0, + sn624x_fu_mic2_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("FU DMIC", NULL, SND_SOC_NOPM, 0, 0, + sn624x_fu_dmic_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_OUTPUT("HP"), + SND_SOC_DAPM_OUTPUT("SPK"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("DMIC"), +}; + +static const struct snd_soc_dapm_route sn624x_sdca_audio_map[] = { + { "FU HP", NULL, "RX" }, + { "FU HP", NULL, "PDE HP" }, + { "HP", NULL, "FU HP" }, + + { "FU SPK", NULL, "SPK RX" }, + { "FU SPK", NULL, "PDE SPK" }, + { "SPK", NULL, "FU SPK" }, + + { "FU MIC2", NULL, "MIC2" }, + { "FU MIC2", NULL, "PDE MIC2" }, + /* UAJ headset capture also needs jack PDE03 (shared with HP) */ + { "FU MIC2", NULL, "PDE HP" }, + { "TX", NULL, "FU MIC2" }, + + { "FU DMIC", NULL, "DMIC" }, + { "FU DMIC", NULL, "PDE DMIC" }, + { "DMIC TX", NULL, "FU DMIC" }, +}; + +static const struct snd_soc_dai_ops sn624x_sdca_ops = { + .hw_params = sn624x_sdca_pcm_hw_params, + .hw_free = sn624x_sdca_pcm_hw_free, + .set_stream = sn624x_sdca_set_sdw_stream, + .shutdown = sn624x_sdca_shutdown, +}; + +static struct snd_soc_dai_driver sn624x_sdca_dai[] = { + { + .name = "sn624x-sdca-aif", + .id = 0, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SN624X_STEREO_RATES, + .formats = SN624X_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SN624X_STEREO_RATES, + .formats = SN624X_FORMATS, + }, + .ops = &sn624x_sdca_ops, + }, + { + .name = "sn624x-sdca-aif2", + .id = 1, + .playback = { + .stream_name = "Speaker Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SN624X_STEREO_RATES, + .formats = SN624X_FORMATS, + }, + .ops = &sn624x_sdca_ops, + }, + { + .name = "sn624x-sdca-aif3", + .id = 2, + .capture = { + .stream_name = "DMic Capture", + .channels_min = 1, + .channels_max = 4, + .rates = SN624X_STEREO_RATES, + .formats = SN624X_FORMATS, + }, + .ops = &sn624x_sdca_ops, + }, +}; + +static const struct snd_soc_component_driver soc_sdca_dev_sn624x = { + .probe = sn624x_sdca_probe, + .dapm_widgets = sn624x_sdca_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sn624x_sdca_dapm_widgets), + .dapm_routes = sn624x_sdca_audio_map, + .num_dapm_routes = ARRAY_SIZE(sn624x_sdca_audio_map), + .set_jack = sn624x_sdca_set_jack_detect, + .endianness = 1, +}; + +int sn624x_sdca_init(struct device *dev, struct regmap *regmap, + struct sdw_slave *slave) +{ + struct sn624x_sdca_priv *sn624x; + int ret; + + sn624x = devm_kzalloc(dev, sizeof(*sn624x), GFP_KERNEL); + if (!sn624x) + return -ENOMEM; + + dev_set_drvdata(dev, sn624x); + sn624x->slave = slave; + sn624x->regmap = regmap; + + ret = sn624x_parse_sdca_functions(sn624x); + if (ret < 0) + return ret; + + mutex_init(&sn624x->disable_irq_lock); + INIT_DELAYED_WORK(&sn624x->jack_detect_work, sn624x_sdca_jack_detect_handler); + + regcache_cache_only(sn624x->regmap, true); + + ret = devm_snd_soc_register_component(dev, + &soc_sdca_dev_sn624x, sn624x_sdca_dai, + ARRAY_SIZE(sn624x_sdca_dai)); + if (ret < 0) + return ret; + + /* + * Do not enable runtime PM here. SoundWire slaves are only powered / + * accessible after ATTACHED; enable RPM from io_init() then. + */ + SN624X_DBG(dev, "snd_soc_register_component OK (DAI sn624x-sdca-aif)\n"); + return 0; +} + +static int sn624x_sdca_sdw_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct regmap *regmap; + int ret; + + SN624X_DBG(&slave->dev, "sdw_probe: dev_num=%u status=%s\n", + slave->dev_num, sn624x_status_str(slave->status)); + + regmap = devm_regmap_init_sdw_mbq_cfg(&slave->dev, slave, + &sn624x_sdca_regmap, + &sn624x_sdca_mbq_cfg); + if (IS_ERR(regmap)) { + dev_err(&slave->dev, "sn624x: devm_regmap_init_sdw_mbq_cfg failed (%ld)\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + ret = sn624x_sdca_init(&slave->dev, regmap, slave); + if (ret < 0) + dev_err(&slave->dev, "sn624x: sn624x_sdca_init failed (%d)\n", ret); + else { + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(&slave->dev); + + if (sn624x) + sn624x_sdca_sdca_irq_mask_all(sn624x); + SN624X_DBG(&slave->dev, "SoundWire probe finished OK\n"); + } + + return ret; +} + +static void sn624x_sdca_sdw_remove(struct sdw_slave *slave) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(&slave->dev); + + SN624X_DBG(&slave->dev, "SoundWire driver remove\n"); + cancel_delayed_work_sync(&sn624x->jack_detect_work); + sn624x_jack_rpm_put(sn624x); + if (sn624x->first_hw_init) + pm_runtime_disable(&slave->dev); + mutex_destroy(&sn624x->disable_irq_lock); +} + +static const struct sdw_device_id sn624x_sdca_id[] = { + SDW_SLAVE_ENTRY_EXT(0x0496, 0x6242, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x0496, 0x6244, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x0496, 0x6247, 0x3, 0x1, 0), + { } +}; +MODULE_DEVICE_TABLE(sdw, sn624x_sdca_id); + +static int sn624x_sdca_dev_suspend(struct device *dev) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev); + + if (!sn624x->first_hw_init) + return 0; + + regcache_cache_only(sn624x->regmap, true); + /* + * Suspend / clock-stop may reset the peripheral. Mark the cache dirty + * so resume's regcache_sync restores software state to hardware. + */ + regcache_mark_dirty(sn624x->regmap); + + return 0; +} + +static int sn624x_sdca_dev_system_suspend(struct device *dev) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev); + + if (!sn624x->first_hw_init) + return 0; + + /* Mask first so IRQ cannot re-schedule work between cancel and disable. */ + mutex_lock(&sn624x->disable_irq_lock); + sn624x->disable_irq = true; + sn624x_sdca_jack_irq_mask(sn624x); + mutex_unlock(&sn624x->disable_irq_lock); + + cancel_delayed_work_sync(&sn624x->jack_detect_work); + + return sn624x_sdca_dev_suspend(dev); +} + +static int sn624x_sdca_regmap_resume(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev); + int ret; + + if (!sn624x->first_hw_init) + return 0; + + SN624X_DBG(dev, "resume: unattach_request=%u\n", slave->unattach_request); + if (slave->unattach_request) { + ret = sdw_slave_wait_for_init(slave, SN624X_PROBE_TIMEOUT_MS); + if (ret) { + sdw_show_ping_status(slave->bus, true); + return ret; + } + } + + regcache_cache_only(sn624x->regmap, false); + regcache_sync(sn624x->regmap); + + return 0; +} + +static int sn624x_sdca_dev_resume(struct device *dev) +{ + return sn624x_sdca_regmap_resume(dev); +} + +static int sn624x_sdca_dev_system_resume(struct device *dev) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev); + int ret; + + ret = sn624x_sdca_regmap_resume(dev); + if (ret < 0) + return ret; + + mutex_lock(&sn624x->disable_irq_lock); + if (sn624x->disable_irq) { + sn624x_sdca_jack_irq_unmask(sn624x); + sn624x->disable_irq = false; + } + mutex_unlock(&sn624x->disable_irq_lock); + + if (sn624x->hs_jack && sn624x->hw_init) { + sn624x_sdca_jack_poll_and_report(sn624x); + sn624x_jack_schedule_poll(sn624x); + } + + return 0; +} + +static const struct dev_pm_ops sn624x_sdca_pm = { + SYSTEM_SLEEP_PM_OPS(sn624x_sdca_dev_system_suspend, sn624x_sdca_dev_system_resume) + RUNTIME_PM_OPS(sn624x_sdca_dev_suspend, sn624x_sdca_dev_resume, NULL) +}; + +static struct sdw_driver sn624x_sdca_sdw_driver = { + .driver = { + .name = "sn624x-sdca", + .pm = pm_ptr(&sn624x_sdca_pm), + }, + .probe = sn624x_sdca_sdw_probe, + .remove = sn624x_sdca_sdw_remove, + .ops = &sn624x_sdca_slave_ops, + .id_table = sn624x_sdca_id, +}; +module_sdw_driver(sn624x_sdca_sdw_driver); + +MODULE_DESCRIPTION("ASoC SN624X SDCA SoundWire driver with UAJ jack support"); +MODULE_AUTHOR("Senary Semiconductor Corp."); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("SND_SOC_SDCA"); +MODULE_SOFTDEP("pre: snd-soc-sdca"); diff --git a/sound/soc/codecs/sn624x-sdca.h b/sound/soc/codecs/sn624x-sdca.h new file mode 100644 index 000000000000..6eb588ba2b3b --- /dev/null +++ b/sound/soc/codecs/sn624x-sdca.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * sn624x-sdca.h -- SN624X SDCA ALSA SoC audio driver header + * + * Copyright(c) 2025 Senary Semiconductor Corp. + */ + +#ifndef __SN624X_H__ +#define __SN624X_H__ + +#include +#include +#include +#include +#include + +struct sn624x_sdca_priv { + struct regmap *regmap; + struct snd_soc_component *component; + struct sdw_slave *slave; + /* Parsed DisCo functions (init tables / defaults from ACPI) */ + struct sdca_function_data *jack_func; + struct sdca_function_data *mic_func; + struct sdca_function_data *amp_func; + bool hw_init; + bool first_hw_init; + struct snd_soc_jack *hs_jack; + struct delayed_work jack_detect_work; + struct mutex disable_irq_lock; + bool disable_irq; + /* Long-lived runtime PM ref while jack detection is enabled */ + bool jack_rpm; + int jack_type; + int jack_type_last; + unsigned int scp_sdca_stat1; + unsigned int scp_sdca_stat2; +}; + +/* SoundWire data port numbers (slave prop source/sink port masks) */ +#define SN624X_PORT_JACK_PLAYBACK 1 +#define SN624X_PORT_SPEAKER_PLAYBACK 2 +#define SN624X_PORT_JACK_CAPTURE 4 +#define SN624X_PORT_DMIC_CAPTURE 5 + +/* Order matches sn624x_sdca_dai[] .id */ +enum sn624x_dai_id { + SN624X_DAI_JACK = 0, + SN624X_DAI_SPEAKER = 1, + SN624X_DAI_DMIC = 2, +}; + +/* + * SDCA Function Numbers (from platform ACPI / device topology). + * Address with SDW_SDCA_CTL(fun, ent, ctl, ch) from sdw_registers.h. + */ +#define SN624X_FUNC_NUM_JACK_CODEC 0x01 +#define SN624X_FUNC_NUM_MIC_ARRAY 0x02 +#define SN624X_FUNC_NUM_SPEAKER_AMP 0x04 + +/* Entity instance IDs (from platform ACPI / SN624X topology) */ +#define SN624X_SDCA_ENT_ENTITY0 0x00 +#define SN624X_SDCA_ENT_CS01 0x01 +#define SN624X_SDCA_ENT_CS02 0x02 +#define SN624X_SDCA_ENT_PDE03 0x03 +#define SN624X_SDCA_ENT_PDE04 0x04 +#define SN624X_SDCA_ENT_CRU05 0x05 +#define SN624X_SDCA_ENT_USER_FU10 0x10 +#define SN624X_SDCA_ENT_USER_FU14 0x14 +#define SN624X_SDCA_ENT_USER_FU1E 0x1e +#define SN624X_UAJ_ENT_BLOCK 0x20 +#define SN624X_SDCA_ENT_GE35 0x1F +/* UAJ power domains from ssdt26.dsl AF01 entity list (PDE 34 / PDE 47) */ +#define SN624X_UAJ_ENT_PDE47 SN624X_SDCA_ENT_PDE03 +#define SN624X_UAJ_ENT_PDE34 SN624X_SDCA_ENT_PDE04 + +/* Control selectors (SDCA; same values as sound/sdca_function.h) */ +/* PDE power state selectors — use standard SDCA defines from sdca_function.h */ +#define SN624X_SDCA_CTL_FU_MUTE 0x01 +#define SN624X_SDCA_CTL_FU_VOLUME 0x02 +#define SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX 0x10 +#define SN624X_SDCA_CTL_CLUSTER_INDEX 0x10 +#define SN624X_SDCA_CTL_FUNCTION_STATUS 0x10 /* Entity 0 */ +#define SN624X_SDCA_CTL_SELECTED_MODE 0x01 +#define SN624X_SDCA_CTL_DETECTED_MODE 0x02 + +/* + * Control Number / channel for multi-channel FU controls (mute/volume). + * Single-value controls (PDE power, rate, function status, …) pass 0 + * directly to SDW_SDCA_CTL — no CH_0 alias. + */ +#define SN624X_SDCA_CH_L 0x01 +#define SN624X_SDCA_CH_R 0x02 + +/* GE35 Selected_Mode / Detected_Mode (SDCA Table 153, UAJ §5.5) */ +#define SN624X_GE35_MODE_HEADPHONE 0x03 +#define SN624X_GE35_MODE_HEADSET 0x05 + +/* Vendor UAJ SCP windows */ +#define SN624X_UAJ_CTL_INPUT_DELAY 0x30 +#define SN624X_UAJ_CTL_CHARGE_PUMP 0x31 +#define SN624X_UAJ_CTL_JACK_DETECT 0x32 + +/* + * Vendor SCP addresses used only when ACPI + * mipi-sdca-function-initialization-table is missing (temporary fallback). + * Remove once platform DisCo ships a complete init table. + */ +#define SN624X_UAJ_CTL_IT33_MIC_BIAS 0x40400418 +#define SN624X_UAJ_CTL_IT31_MIC_BIAS 0x40400318 +#define SN624X_UAJ_CTL_FU41_CH1_MUTE 0x40400A09 +#define SN624X_UAJ_CTL_FU41_CH2_MUTE 0x40400A0A +#define SN624X_UAJ_CTL_FU31_CH0_MUTE 0x40400C08 +#define SN624X_UAJ_CTL_FU32_CH0_MUTE 0x40400C88 +#define SN624X_UAJ_CTL_FU33_CH0_MUTE 0x40400D08 +#define SN624X_UAJ_CTL_FU36_CH1_MUTE 0x40400F09 +#define SN624X_UAJ_CTL_FU36_CH2_MUTE 0x40400F0A +#define SN624X_UAJ_CTL_FU31_CH0_GAIN_HIGH 0x40402C58 +#define SN624X_UAJ_CTL_FU31_CH0_GAIN_LOW 0x40400C58 +#define SN624X_UAJ_CTL_FU32_CH0_GAIN_HIGH 0x40402CD8 +#define SN624X_UAJ_CTL_FU32_CH0_GAIN_LOW 0x40400CD8 +#define SN624X_UAJ_CTL_FU33_CH0_GAIN_HIGH 0x40402D58 +#define SN624X_UAJ_CTL_FU33_CH0_GAIN_LOW 0x40400D58 +#define SN624X_UAJ_CTL_INPUT_DELAY_TIME 0x00002124 +#define SN624X_UAJ_CTL_PORTA_CHARGE_PUMP 0x00002086 +#define SN624X_UAJ_DMIC_PPU11 0x40880380 + +/* Jack poll interval (ms) */ +#define SN624X_JACK_POLL_MS 1000 + +/* µs settle times for headphone <-> speaker route switch during active PCM */ +#define SN624X_ROUTE_AFTER_MUTE_US_MIN 5000 +#define SN624X_ROUTE_AFTER_MUTE_US_MAX 8000 +#define SN624X_ROUTE_AFTER_D3_US_MIN 10000 +#define SN624X_ROUTE_AFTER_D3_US_MAX 15000 +#define SN624X_ROUTE_BEFORE_ACTIVE_US_MIN 10000 +#define SN624X_ROUTE_BEFORE_ACTIVE_US_MAX 15000 + +/* SDCA SampleFreqIndex (same encoding as other SDCA codecs, e.g. rt722) */ +#define SN624X_SDCA_RATE_44100HZ 0x08 +#define SN624X_SDCA_RATE_48000HZ 0x09 +#define SN624X_SDCA_RATE_96000HZ 0x0b +#define SN624X_SDCA_RATE_192000HZ 0x0d + +#define SN624X_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) +#define SN624X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +int sn624x_sdca_init(struct device *dev, struct regmap *regmap, + struct sdw_slave *slave); + +#endif /* __SN624X_H__ */ From 77272ecd96d73a90ffc6d4f7939d16ce45303f5b Mon Sep 17 00:00:00 2001 From: Qianghua Wang Date: Tue, 1 Sep 2026 11:00:48 +0800 Subject: [PATCH 033/924] ASoC: sdw_utils: add Senary SN624x helpers and codec_info Add sof_sdw helpers for SN624x jack, amp, and DMIC, and register the codec_info entries used by the Intel SOF SoundWire machine driver. Use name_prefix "sn624x" to match UCM and DAI widget naming. Signed-off-by: Qianghua Wang Link: https://patch.msgid.link/766c70645ad35ef446aa7078a2600c3361994e71.1788230065.git.qianghua.wang@senarytech.com Signed-off-by: Mark Brown --- include/sound/soc_sdw_utils.h | 20 ++ sound/soc/sdw_utils/Makefile | 4 +- sound/soc/sdw_utils/soc_sdw_senary_amp.c | 80 +++++++ sound/soc/sdw_utils/soc_sdw_senary_dmic.c | 49 +++++ sound/soc/sdw_utils/soc_sdw_senary_sdca.c | 63 ++++++ .../soc_sdw_senary_sdca_jack_common.c | 197 ++++++++++++++++++ sound/soc/sdw_utils/soc_sdw_utils.c | 166 +++++++++++++++ 7 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 sound/soc/sdw_utils/soc_sdw_senary_amp.c create mode 100644 sound/soc/sdw_utils/soc_sdw_senary_dmic.c create mode 100644 sound/soc/sdw_utils/soc_sdw_senary_sdca.c create mode 100644 sound/soc/sdw_utils/soc_sdw_senary_sdca_jack_common.c diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h index 9b28e9aef4f1..3c1d177a2f2d 100644 --- a/include/sound/soc_sdw_utils.h +++ b/include/sound/soc_sdw_utils.h @@ -294,4 +294,24 @@ int asoc_sdw_es9356_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai int asoc_sdw_es9356_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); int asoc_sdw_es9356_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); +/* Senary SN624x SDCA */ +int asoc_sdw_senary_sdca_jack_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, + bool playback); +int asoc_sdw_senary_sdca_jack_exit(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link); +int asoc_sdw_senary_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai); +int asoc_sdw_senary_amp_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, + bool playback); +int asoc_sdw_senary_amp_exit(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link); +int asoc_sdw_senary_sdca_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai); +int asoc_sdw_senary_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai); + #endif diff --git a/sound/soc/sdw_utils/Makefile b/sound/soc/sdw_utils/Makefile index 5ae8c69b8b98..1dbc00a2d64a 100644 --- a/sound/soc/sdw_utils/Makefile +++ b/sound/soc/sdw_utils/Makefile @@ -10,5 +10,7 @@ snd-soc-sdw-utils-y := soc_sdw_utils.o soc_sdw_dmic.o soc_sdw_rt_dmic.o \ soc_sdw_cs_amp.o \ soc_sdw_es9356.o \ soc_sdw_maxim.o \ - soc_sdw_ti_amp.o + soc_sdw_ti_amp.o \ + soc_sdw_senary_sdca.o soc_sdw_senary_sdca_jack_common.o \ + soc_sdw_senary_amp.o soc_sdw_senary_dmic.o obj-$(CONFIG_SND_SOC_SDW_UTILS) += snd-soc-sdw-utils.o diff --git a/sound/soc/sdw_utils/soc_sdw_senary_amp.c b/sound/soc/sdw_utils/soc_sdw_senary_amp.c new file mode 100644 index 000000000000..9370402ab4c7 --- /dev/null +++ b/sound/soc/sdw_utils/soc_sdw_senary_amp.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-only +// This file incorporates work covered by the following copyright notice: +// Copyright (c) 2022 Intel Corporation +// Copyright (c) 2024 Advanced Micro Devices, Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int senary_amp_add_device_props(struct device *sdw_dev) +{ + return 0; +} + +int asoc_sdw_senary_amp_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) +{ + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + + if (ctx->amp_dev1) { + device_remove_software_node(ctx->amp_dev1); + put_device(ctx->amp_dev1); + } + + if (ctx->amp_dev2) { + device_remove_software_node(ctx->amp_dev2); + put_device(ctx->amp_dev2); + } + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_amp_exit, "SND_SOC_SDW_UTILS"); + +int asoc_sdw_senary_amp_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, + bool playback) +{ + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct device *sdw_dev1, *sdw_dev2; + int ret; + + /* Count amp number and do init on playback link only. */ + if (!playback) + return 0; + + info->amp_num++; + + if (info->amp_num == 2) { + sdw_dev1 = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[0].name); + if (!sdw_dev1) + return -EPROBE_DEFER; + + ret = senary_amp_add_device_props(sdw_dev1); + if (ret < 0) { + put_device(sdw_dev1); + return ret; + } + ctx->amp_dev1 = sdw_dev1; + + sdw_dev2 = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[1].name); + if (!sdw_dev2) + return -EPROBE_DEFER; + + ret = senary_amp_add_device_props(sdw_dev2); + if (ret < 0) { + put_device(sdw_dev2); + return ret; + } + ctx->amp_dev2 = sdw_dev2; + } + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_amp_init, "SND_SOC_SDW_UTILS"); diff --git a/sound/soc/sdw_utils/soc_sdw_senary_dmic.c b/sound/soc/sdw_utils/soc_sdw_senary_dmic.c new file mode 100644 index 000000000000..7481706561f9 --- /dev/null +++ b/sound/soc/sdw_utils/soc_sdw_senary_dmic.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-only +// This file incorporates work covered by the following copyright notice: +// Copyright (c) 2024 Intel Corporation +// Copyright (c) 2024 Advanced Micro Devices, Inc. + +#include +#include +#include +#include +#include +#include + +/* Card pin "Dmic" → codec input (name_prefix + " DMIC") */ +static const struct snd_soc_dapm_route senary_dmic_map[] = { + { "sn624x DMIC", NULL, "Dmic" }, +}; + +int asoc_sdw_senary_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); + struct snd_soc_component *component; + char *mic_name; + int ret; + + component = dai->component; + + ret = snd_soc_dapm_add_routes(dapm, senary_dmic_map, + ARRAY_SIZE(senary_dmic_map)); + if (ret) { + dev_err(card->dev, "senary dmic map addition failed: %d\n", ret); + return ret; + } + + mic_name = devm_kasprintf(card->dev, GFP_KERNEL, "%s", component->name_prefix); + if (!mic_name) + return -ENOMEM; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s mic:%s-dmic", card->components, + mic_name); + if (!card->components) + return -ENOMEM; + + dev_dbg(card->dev, "card->components: %s\n", card->components); + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_dmic_rtd_init, "SND_SOC_SDW_UTILS"); diff --git a/sound/soc/sdw_utils/soc_sdw_senary_sdca.c b/sound/soc/sdw_utils/soc_sdw_senary_sdca.c new file mode 100644 index 000000000000..5c2cd75307c9 --- /dev/null +++ b/sound/soc/sdw_utils/soc_sdw_senary_sdca.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-only +// This file incorporates work covered by the following copyright notice: +// Copyright (c) 2024 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* dapm routes for SPK will be registered dynamically */ +static const struct snd_soc_dapm_route sn624x_spk_map[] = { + { "Speaker", NULL, "sn624x SPK" }, +}; + +/* Structure to map codec names to respective route arrays and sizes */ +struct codec_route_map { + const char *codec_name; + const struct snd_soc_dapm_route *route_map; + size_t route_size; +}; + +/* Codec route maps array */ +static const struct codec_route_map codec_routes[] = { + { "sn624x", sn624x_spk_map, ARRAY_SIZE(sn624x_spk_map) }, +}; + +static const struct codec_route_map *get_codec_route_map(const char *dai_name) +{ + for (size_t i = 0; i < ARRAY_SIZE(codec_routes); i++) { + if (str_has_prefix(dai_name, codec_routes[i].codec_name)) + return &codec_routes[i]; + } + return NULL; +} + +int asoc_sdw_senary_sdca_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); + int ret; + + /* Match by DAI name prefix (e.g. sn624x-sdca-aif2). */ + const struct codec_route_map *route_map = get_codec_route_map(dai->name); + + if (!route_map) { + dev_err(rtd->dev, "failed to get codec name and route map\n"); + return -EINVAL; + } + + /* Add routes */ + ret = snd_soc_dapm_add_routes(dapm, route_map->route_map, route_map->route_size); + if (ret) + dev_err(rtd->dev, "failed to add rt sdca spk map: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_sdca_spk_rtd_init, "SND_SOC_SDW_UTILS"); diff --git a/sound/soc/sdw_utils/soc_sdw_senary_sdca_jack_common.c b/sound/soc/sdw_utils/soc_sdw_senary_sdca_jack_common.c new file mode 100644 index 000000000000..1dab6f99877d --- /dev/null +++ b/sound/soc/sdw_utils/soc_sdw_senary_sdca_jack_common.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +// This file incorporates work covered by the following copyright notice: +// Copyright (c) 2020 Intel Corporation +// Copyright (c) 2024 Advanced Micro Devices, Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * jack_init / jack_exit / most of rtd_init follow soc_sdw_rt_sdca_jack_common.c + * (same SDCA headset machine-helper pattern). Vendor-specific pieces here are + * mainly the "senarytech,jd-src" property name and senary_sdca_jack_map[]. + * + * Keep a Senary-local file for now to mirror the existing RT layout. Sharing + * parameterised SDCA jack helpers in snd-soc-sdw-utils (jd-src key + common + * init/exit/rtd_init boilerplate) is intentional follow-up work, out of scope + * for the sn624x codec series. + */ + +/* + * Note this MUST be called before snd_soc_register_card(), so that the props + * are in place before the codec component driver's probe function parses them. + */ +static int senary_sdca_jack_add_codec_device_props(struct device *sdw_dev, unsigned long quirk) +{ + struct property_entry props[SOC_SDW_MAX_NO_PROPS] = {}; + struct fwnode_handle *fwnode; + int ret; + + if (!SOC_SDW_JACK_JDSRC(quirk)) + return 0; + + props[0] = PROPERTY_ENTRY_U32("senarytech,jd-src", SOC_SDW_JACK_JDSRC(quirk)); + + fwnode = fwnode_create_software_node(props, NULL); + if (IS_ERR(fwnode)) + return PTR_ERR(fwnode); + + ret = device_add_software_node(sdw_dev, to_software_node(fwnode)); + + fwnode_handle_put(fwnode); + + return ret; +} + +static const struct snd_soc_dapm_route senary_sdca_jack_map[] = { + { "Headphone", NULL, "sn624x HP" }, + { "sn624x MIC2", NULL, "Headset Mic" }, +}; + +static struct snd_soc_jack_pin senary_sdca_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static const char * const need_sdca_suffix[] = { + "sn624x" +}; + +int asoc_sdw_senary_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component; + struct snd_soc_jack *jack; + int ret; + int i; + + component = dai->component; + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s hs:%s", + card->components, component->name_prefix); + if (!card->components) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(need_sdca_suffix); i++) { + if (!strstr(component->name_prefix, need_sdca_suffix[i])) + continue; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s-sdca", card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_dapm_add_routes(dapm, senary_sdca_jack_map, + ARRAY_SIZE(senary_sdca_jack_map)); + if (ret) { + dev_err(card->dev, + "senary sdca jack map addition failed: %d\n", ret); + return ret; + } + break; + } + + if (i == ARRAY_SIZE(need_sdca_suffix)) { + dev_err(card->dev, "%s is not supported\n", component->name_prefix); + return -EINVAL; + } + + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sdw_headset, + senary_sdca_jack_pins, + ARRAY_SIZE(senary_sdca_jack_pins)); + if (ret) { + dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n", + ret); + return ret; + } + + jack = &ctx->sdw_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + 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); + + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) + dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n", + ret); + + return ret; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_sdca_jack_rtd_init, "SND_SOC_SDW_UTILS"); + +int asoc_sdw_senary_sdca_jack_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) +{ + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + + if (!ctx->headset_codec_dev) + return 0; + + if (!SOC_SDW_JACK_JDSRC(ctx->mc_quirk)) + return 0; + + device_remove_software_node(ctx->headset_codec_dev); + put_device(ctx->headset_codec_dev); + ctx->headset_codec_dev = NULL; + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_sdca_jack_exit, "SND_SOC_SDW_UTILS"); + +/* + * Same control flow as asoc_sdw_rt_sdca_jack_init(); see file comment above + * about deferred helper sharing in snd-soc-sdw-utils. + */ +int asoc_sdw_senary_sdca_jack_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, + bool playback) +{ + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct device *sdw_dev; + int ret; + + /* + * Jack detection should be only initialized once for headsets since + * the playback/capture is sharing the same jack + */ + if (ctx->headset_codec_dev) + return 0; + + sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[0].name); + if (!sdw_dev) + return -EPROBE_DEFER; + + ret = senary_sdca_jack_add_codec_device_props(sdw_dev, ctx->mc_quirk); + if (ret < 0) { + put_device(sdw_dev); + return ret; + } + ctx->headset_codec_dev = sdw_dev; + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_sdca_jack_init, "SND_SOC_SDW_UTILS"); diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index a66dcc02fb59..88031238c04e 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -17,6 +17,15 @@ static const struct snd_soc_dapm_widget generic_dmic_widgets[] = { SND_SOC_DAPM_MIC("DMIC", NULL), }; +/* Senary SN624x UCM uses CaptureMixerElem "Dmic"; keep separate from generic DMIC. */ +static const struct snd_soc_dapm_widget sn624x_dmic_widgets[] = { + SND_SOC_DAPM_MIC("Dmic", NULL), +}; + +static const struct snd_kcontrol_new sn624x_dmic_controls[] = { + SOC_DAPM_PIN_SWITCH("Dmic"), +}; + static const struct snd_soc_dapm_widget generic_jack_widgets[] = { SND_SOC_DAPM_HP("Headphone", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), @@ -1268,6 +1277,163 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, .dai_num = 1, }, + /* + * Senary SN6242/6244/6247: same SDCA jack/amp/mic topology; only part_id + * differs. name_prefix stays "sn624x" for UCM/DAI naming. + */ + { + .vendor_id = 0x0496, + .part_id = 0x6244, + .name_prefix = "sn624x", + .ignore_internal_dmic = true, + .dais = { + { + .direction = {true, true}, + .dai_name = "sn624x-sdca-aif", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_senary_sdca_jack_init, + .exit = asoc_sdw_senary_sdca_jack_exit, + .rtd_init = asoc_sdw_senary_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "sn624x-sdca-aif2", + .component_name = "sn624x", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_senary_amp_init, + .exit = asoc_sdw_senary_amp_exit, + .rtd_init = asoc_sdw_senary_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + .quirk = SOC_SDW_CODEC_SPKR, + .quirk_exclude = true, + }, + { + .direction = {false, true}, + .dai_name = "sn624x-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_senary_dmic_rtd_init, + .widgets = sn624x_dmic_widgets, + .num_widgets = ARRAY_SIZE(sn624x_dmic_widgets), + .controls = sn624x_dmic_controls, + .num_controls = ARRAY_SIZE(sn624x_dmic_controls), + .quirk = SOC_SDW_CODEC_MIC, + .quirk_exclude = true, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x0496, + .part_id = 0x6247, + .name_prefix = "sn624x", + .ignore_internal_dmic = true, + .dais = { + { + .direction = {true, true}, + .dai_name = "sn624x-sdca-aif", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_senary_sdca_jack_init, + .exit = asoc_sdw_senary_sdca_jack_exit, + .rtd_init = asoc_sdw_senary_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "sn624x-sdca-aif2", + .component_name = "sn624x", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_senary_amp_init, + .exit = asoc_sdw_senary_amp_exit, + .rtd_init = asoc_sdw_senary_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + .quirk = SOC_SDW_CODEC_SPKR, + .quirk_exclude = true, + }, + { + .direction = {false, true}, + .dai_name = "sn624x-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_senary_dmic_rtd_init, + .widgets = sn624x_dmic_widgets, + .num_widgets = ARRAY_SIZE(sn624x_dmic_widgets), + .controls = sn624x_dmic_controls, + .num_controls = ARRAY_SIZE(sn624x_dmic_controls), + .quirk = SOC_SDW_CODEC_MIC, + .quirk_exclude = true, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x0496, + .part_id = 0x6242, + .name_prefix = "sn624x", + .ignore_internal_dmic = true, + .dais = { + { + .direction = {true, true}, + .dai_name = "sn624x-sdca-aif", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_senary_sdca_jack_init, + .exit = asoc_sdw_senary_sdca_jack_exit, + .rtd_init = asoc_sdw_senary_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "sn624x-sdca-aif2", + .component_name = "sn624x", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_senary_amp_init, + .exit = asoc_sdw_senary_amp_exit, + .rtd_init = asoc_sdw_senary_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + .quirk = SOC_SDW_CODEC_SPKR, + .quirk_exclude = true, + }, + { + .direction = {false, true}, + .dai_name = "sn624x-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_senary_dmic_rtd_init, + .widgets = sn624x_dmic_widgets, + .num_widgets = ARRAY_SIZE(sn624x_dmic_widgets), + .controls = sn624x_dmic_controls, + .num_controls = ARRAY_SIZE(sn624x_dmic_controls), + .quirk = SOC_SDW_CODEC_MIC, + .quirk_exclude = true, + }, + }, + .dai_num = 3, + }, }; EXPORT_SYMBOL_NS(codec_info_list, "SND_SOC_SDW_UTILS"); From 32f9c551a91a37c2ed4f78371bed45c167e76336 Mon Sep 17 00:00:00 2001 From: Qianghua Wang Date: Tue, 1 Sep 2026 11:00:49 +0800 Subject: [PATCH 034/924] ASoC: Intel: soc-acpi: add SN624x entries for ARL/LNL/MTL/PTL Add SoundWire machine match entries for Senary SN624x on Arrow Lake, Lunar Lake, Meteor Lake, and Panther Lake, and select the SN624x SDCA codec from the SOF SoundWire machine Kconfig. Each part ID uses a separate mach entry; name_prefix is "sn624x". Signed-off-by: Qianghua Wang Link: https://patch.msgid.link/14488e51e696df1b91b15a4100f41fa0a6975793.1788230065.git.qianghua.wang@senarytech.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/Kconfig | 1 + .../intel/common/soc-acpi-intel-arl-match.c | 71 ++++++++++++++++ .../intel/common/soc-acpi-intel-lnl-match.c | 71 ++++++++++++++++ .../intel/common/soc-acpi-intel-mtl-match.c | 71 ++++++++++++++++ .../intel/common/soc-acpi-intel-ptl-match.c | 82 +++++++++++++++++++ 5 files changed, 296 insertions(+) diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index cddbd2aa424e..5b80b1770ff0 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -535,6 +535,7 @@ config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH select SND_SOC_ES9356 select SND_SOC_DMIC select SND_SOC_INTEL_HDA_DSP_COMMON + select SND_SOC_SN624X_SDCA_SDW imply SND_SOC_SDW_MOCKUP help Add support for Intel SoundWire-based platforms connected to diff --git a/sound/soc/intel/common/soc-acpi-intel-arl-match.c b/sound/soc/intel/common/soc-acpi-intel-arl-match.c index 1033f2d45c8a..575889a4c69b 100644 --- a/sound/soc/intel/common/soc-acpi-intel-arl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-arl-match.c @@ -531,6 +531,63 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_machines[] = { EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_arl_machines); /* this table is used when there is no I2S codec present */ + +/* + * Senary SN624x on Arrow Lake: one multi-function device on link 0. + * Separate mach entries per part ID (adr_d[] is AND, not OR). Amp endpoint + * is aggregated like the generic SOC_SDW_DAI_TYPE_AMP shape from + * find_acpi_adr_device(). + */ +static const struct snd_soc_acpi_endpoint sn624x_endpoints[] = { + /* Jack */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* Amp */ + { + .num = 1, + .aggregated = 1, + .group_position = 1, + .group_id = 1, + }, + /* DMIC */ + { + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +#define SN624X_ARL_SINGLE_ADR(_sym, _adr) \ +static const struct snd_soc_acpi_adr_device _sym[] = { \ + { \ + .adr = (_adr), \ + .num_endpoints = ARRAY_SIZE(sn624x_endpoints), \ + .endpoints = sn624x_endpoints, \ + .name_prefix = "sn624x", \ + }, \ +} + +#define SN624X_ARL_LINK(_sym, _adr_arr) \ +static const struct snd_soc_acpi_link_adr _sym[] = { \ + { \ + .mask = BIT(0), \ + .num_adr = ARRAY_SIZE(_adr_arr), \ + .adr_d = _adr_arr, \ + }, \ + {} \ +} + +SN624X_ARL_SINGLE_ADR(sn6242_single_adr, 0x0000000496624201ull); +SN624X_ARL_SINGLE_ADR(sn6244_single_adr, 0x0000000496624401ull); + +SN624X_ARL_LINK(arl_link_sn6242, sn6242_single_adr); +SN624X_ARL_LINK(arl_link_sn6244, sn6244_single_adr); + struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { { .link_mask = BIT(0) | BIT(2) | BIT(3), @@ -624,6 +681,20 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { .sof_tplg_filename = "sof-arl-es9356.tplg", .get_function_tplg_files = sof_sdw_get_tplg_files, }, + { + .link_mask = BIT(0), + .links = arl_link_sn6242, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = arl_link_sn6244, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_arl_sdw_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c index 3b9758f73238..f9c47cc1e0c7 100644 --- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c @@ -685,6 +685,63 @@ static const struct snd_soc_acpi_link_adr lnl_sdw_rt712_vb_l2_rt1320_l1[] = { }; /* this table is used when there is no I2S codec present */ + +/* + * Senary SN624x on Lunar Lake: one multi-function device on link 0. + * Separate mach entries per part ID (adr_d[] is AND, not OR). Amp endpoint + * is aggregated like the generic SOC_SDW_DAI_TYPE_AMP shape from + * find_acpi_adr_device(). + */ +static const struct snd_soc_acpi_endpoint sn624x_endpoints[] = { + /* Jack */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* Amp */ + { + .num = 1, + .aggregated = 1, + .group_position = 1, + .group_id = 1, + }, + /* DMIC */ + { + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +#define SN624X_LNL_SINGLE_ADR(_sym, _adr) \ +static const struct snd_soc_acpi_adr_device _sym[] = { \ + { \ + .adr = (_adr), \ + .num_endpoints = ARRAY_SIZE(sn624x_endpoints), \ + .endpoints = sn624x_endpoints, \ + .name_prefix = "sn624x", \ + }, \ +} + +#define SN624X_LNL_LINK(_sym, _adr_arr) \ +static const struct snd_soc_acpi_link_adr _sym[] = { \ + { \ + .mask = BIT(0), \ + .num_adr = ARRAY_SIZE(_adr_arr), \ + .adr_d = _adr_arr, \ + }, \ + {} \ +} + +SN624X_LNL_SINGLE_ADR(sn6242_single_adr, 0x0000000496624201ull); +SN624X_LNL_SINGLE_ADR(sn6244_single_adr, 0x0000000496624401ull); + +SN624X_LNL_LINK(lnl_link_sn6242, sn6242_single_adr); +SN624X_LNL_LINK(lnl_link_sn6244, sn6244_single_adr); + /* this table is used when there is no I2S codec present */ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { /* mockup tests need to be first */ @@ -805,6 +862,20 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { .sof_tplg_filename = "sof-lnl-rt713-l2-rt1320-l13.tplg", .get_function_tplg_files = sof_sdw_get_tplg_files, }, + { + .link_mask = BIT(0), + .links = lnl_link_sn6242, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = lnl_link_sn6244, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_lnl_sdw_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c index 596582053d93..e89c5cfd6ed1 100644 --- a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c @@ -1296,6 +1296,63 @@ static const struct snd_soc_acpi_link_adr mtl_cs35l63_x2_link1_link3_fb[] = { }; /* this table is used when there is no I2S codec present */ + +/* + * Senary SN624x on Meteor Lake: one multi-function device on link 0. + * Separate mach entries per part ID (adr_d[] is AND, not OR). Amp endpoint + * is aggregated like the generic SOC_SDW_DAI_TYPE_AMP shape from + * find_acpi_adr_device(). + */ +static const struct snd_soc_acpi_endpoint sn624x_endpoints[] = { + /* Jack */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* Amp */ + { + .num = 1, + .aggregated = 1, + .group_position = 1, + .group_id = 1, + }, + /* DMIC */ + { + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +#define SN624X_MTL_SINGLE_ADR(_sym, _adr) \ +static const struct snd_soc_acpi_adr_device _sym[] = { \ + { \ + .adr = (_adr), \ + .num_endpoints = ARRAY_SIZE(sn624x_endpoints), \ + .endpoints = sn624x_endpoints, \ + .name_prefix = "sn624x", \ + }, \ +} + +#define SN624X_MTL_LINK(_sym, _adr_arr) \ +static const struct snd_soc_acpi_link_adr _sym[] = { \ + { \ + .mask = BIT(0), \ + .num_adr = ARRAY_SIZE(_adr_arr), \ + .adr_d = _adr_arr, \ + }, \ + {} \ +} + +SN624X_MTL_SINGLE_ADR(sn6242_single_adr, 0x0000000496624201ull); +SN624X_MTL_SINGLE_ADR(sn6244_single_adr, 0x0000000496624401ull); + +SN624X_MTL_LINK(mtl_link_sn6242, sn6242_single_adr); +SN624X_MTL_LINK(mtl_link_sn6244, sn6244_single_adr); + struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { /* mockup tests need to be first */ { @@ -1503,6 +1560,20 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { .sof_tplg_filename = "sof-mtl-sdw-cs42l42-l0-max98363-l2.tplg", .get_function_tplg_files = sof_sdw_get_tplg_files, }, + { + .link_mask = BIT(0), + .links = mtl_link_sn6242, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = mtl_link_sn6244, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_mtl_sdw_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c index 756bbf82a326..dd84e97836c9 100644 --- a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c @@ -438,6 +438,66 @@ static const struct snd_soc_acpi_link_adr ptl_sdw_rt712_vb_l3_rt1320_l3[] = { }; /* this table is used when there is no I2S codec present */ + +/* + * Senary SN624x on Panther Lake: one multi-function device on link 3. + * Separate mach entries per part ID (adr_d[] is AND, not OR). ADR version + * nibble 0x3 matches the silicon SoundWire version. Amp endpoint is + * aggregated like the generic SOC_SDW_DAI_TYPE_AMP shape from + * find_acpi_adr_device(). + */ +static const struct snd_soc_acpi_endpoint sn624x_endpoints[] = { + /* Jack */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* Amp */ + { + .num = 1, + .aggregated = 1, + .group_position = 1, + .group_id = 1, + }, + /* DMIC */ + { + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +#define SN624X_PTL_SINGLE_ADR(_sym, _adr) \ +static const struct snd_soc_acpi_adr_device _sym[] = { \ + { \ + .adr = (_adr), \ + .num_endpoints = ARRAY_SIZE(sn624x_endpoints), \ + .endpoints = sn624x_endpoints, \ + .name_prefix = "sn624x", \ + }, \ +} + +#define SN624X_PTL_LINK(_sym, _adr_arr) \ +static const struct snd_soc_acpi_link_adr _sym[] = { \ + { \ + .mask = BIT(3), \ + .num_adr = ARRAY_SIZE(_adr_arr), \ + .adr_d = _adr_arr, \ + }, \ + {} \ +} + +SN624X_PTL_SINGLE_ADR(sn6242_single_adr, 0x0003300496624201ull); +SN624X_PTL_SINGLE_ADR(sn6244_single_adr, 0x0003300496624401ull); +SN624X_PTL_SINGLE_ADR(sn6247_single_adr, 0x0003300496624701ull); + +SN624X_PTL_LINK(ptl_link_sn6242, sn6242_single_adr); +SN624X_PTL_LINK(ptl_link_sn6244, sn6244_single_adr); +SN624X_PTL_LINK(ptl_link_sn6247, sn6247_single_adr); + struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { /* Order Priority: mockup > most links > most bit link-mask > alphabetical */ { @@ -531,6 +591,28 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { .machine_check = snd_soc_acpi_intel_rt712_vb_no_function_topology, .sof_tplg_filename = "sof-ptl-rt712-l3-rt1320-l3.tplg", }, + + { + .link_mask = BIT(3), + .links = ptl_link_sn6242, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3), + .links = ptl_link_sn6244, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3), + .links = ptl_link_sn6247, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_ptl_sdw_machines); From 6f83e540f9456a0ec63fde75c749f143f9cb8857 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Tue, 1 Sep 2026 09:31:05 +0800 Subject: [PATCH 035/924] ASoC: tas2781: Add register decoded info to I/O error logs TAS2563/TAS2781 use a paged register space, but I/O failures only log the error code. Decode and print book/page/register in error messages to speed up I2C debugging across all read/write/update paths. Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20260901013105.1589-1-shenghao-ding@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2781-comlib-i2c.c | 7 +++++-- sound/soc/codecs/tas2781-comlib.c | 22 +++++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/tas2781-comlib-i2c.c b/sound/soc/codecs/tas2781-comlib-i2c.c index 79b5f5b04c74..dd86fc90a7d0 100644 --- a/sound/soc/codecs/tas2781-comlib-i2c.c +++ b/sound/soc/codecs/tas2781-comlib-i2c.c @@ -2,7 +2,7 @@ // // TAS2563/TAS2781 Common functions for HDA and ASoC Audio drivers based on I2C // -// Copyright 2025 Texas Instruments, Inc. +// Copyright 2025 - 2026 Texas Instruments, Inc. // // Author: Shenghao Ding @@ -131,7 +131,10 @@ int tasdevice_dev_update_bits( ret = regmap_update_bits(map, TASDEVICE_PGRG(reg), mask, value); if (ret < 0) - dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", + __func__, TASDEVICE_BOOK_ID(reg), + TASDEVICE_PAGE_ID(reg), + TASDEVICE_PAGE_REG(reg), ret); } else { dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, chn); diff --git a/sound/soc/codecs/tas2781-comlib.c b/sound/soc/codecs/tas2781-comlib.c index 4cec9f8a00af..e4c8a04a9187 100644 --- a/sound/soc/codecs/tas2781-comlib.c +++ b/sound/soc/codecs/tas2781-comlib.c @@ -2,7 +2,7 @@ // // TAS2563/TAS2781 Common functions for HDA and ASoC Audio drivers // -// Copyright 2023 - 2025 Texas Instruments, Inc. +// Copyright 2023 - 2026 Texas Instruments, Inc. // // Author: Shenghao Ding @@ -34,7 +34,10 @@ int tasdevice_dev_read(struct tasdevice_priv *tas_priv, ret = regmap_read(map, TASDEVICE_PGRG(reg), val); if (ret < 0) - dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", + __func__, TASDEVICE_BOOK_ID(reg), + TASDEVICE_PAGE_ID(reg), + TASDEVICE_PAGE_REG(reg), ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, @@ -62,7 +65,10 @@ int tasdevice_dev_bulk_read(struct tasdevice_priv *tas_priv, ret = regmap_bulk_read(map, TASDEVICE_PGRG(reg), data, len); if (ret < 0) - dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", + __func__, TASDEVICE_BOOK_ID(reg), + TASDEVICE_PAGE_ID(reg), + TASDEVICE_PAGE_REG(reg), ret); } else dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, chn); @@ -88,7 +94,10 @@ int tasdevice_dev_write(struct tasdevice_priv *tas_priv, ret = regmap_write(map, TASDEVICE_PGRG(reg), value); if (ret < 0) - dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", + __func__, TASDEVICE_BOOK_ID(reg), + TASDEVICE_PAGE_ID(reg), + TASDEVICE_PAGE_REG(reg), ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, @@ -118,7 +127,10 @@ int tasdevice_dev_bulk_write( ret = regmap_bulk_write(map, TASDEVICE_PGRG(reg), data, len); if (ret < 0) - dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", + __func__, TASDEVICE_BOOK_ID(reg), + TASDEVICE_PAGE_ID(reg), + TASDEVICE_PAGE_REG(reg), ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, From 951af101c73070dca5241e408decfb302e37a0fa Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 25 Aug 2026 16:52:39 +0800 Subject: [PATCH 036/924] ASoC: report component resume callback errors snd_soc_component_driver::resume() returns an int, but snd_soc_component_resume() discards it. In the deferred resume path, snd_soc_resume() has already returned success to the PM core, so an error cannot be propagated back to that caller. Pass the callback result through the existing ASoC component error helper. This reports negative results with component context while preserving the established best-effort behavior: the component is still marked resumed and the worker continues DAPM resume, digital unmute and card power publication. There are existing callbacks, including tas2562_resume() and atmel_classd_component_resume(), which directly return regcache_sync() errors. Those errors currently disappear at the component wrapper. This only makes errors already returned by component callbacks observable. It does not expose operations that individual callbacks ignore, and it does not add rollback or retry semantics. Callbacks which already report an error may retain their driver-specific message in addition to the common ASoC component context, as with other ASoC component wrappers. This is an RFC to confirm that reporting and continuing is the intended contract for deferred component resume failures. Signed-off-by: Pengpeng Hou Acked-by: Kuninori Morimoto Link: https://patch.msgid.link/20260825085239.82282-1-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/soc-component.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index dc7d203cb76a..463ea227cac8 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -315,8 +315,12 @@ void snd_soc_component_suspend(struct snd_soc_component *component) void snd_soc_component_resume(struct snd_soc_component *component) { - if (component->driver->resume) - component->driver->resume(component); + int ret; + + if (component->driver->resume) { + ret = component->driver->resume(component); + soc_component_ret(component, ret); + } component->suspended = 0; } From 2bba6ea93e48959a0af21bcc4f34ac2ff527e88a Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 1 Sep 2026 13:38:57 +0100 Subject: [PATCH 037/924] ASoC: soc-ops: Clarify kernel doc for snd_soc_info_volsw_xs() The current comment describing the min member of struct soc_mixer_control for SX controls is slightly open to misinterpretation. It currently says "min is the minimum register value", that could be interpreted as the lowest numerical value the register can receive, whereas the intention is the value equating to the lowest possible volume. These are often but not always the same thing. Update the comment to be slightly more explicit. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260901123857.2338909-1-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/soc-ops.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index c935571d43e9..142f407e3939 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -305,8 +305,8 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw); * Callback to provide information about a single mixer control, or a double * mixer control that spans 2 registers of the SX TLV type. SX TLV controls * have a range that represents both positive and negative values either side - * of zero but without a sign bit. min is the minimum register value, max is - * the number of steps. + * of zero but without a sign bit. min is the register value representing the + * minimum volume, max is the number of volume steps. * * Returns 0 for success. */ From 15570f6b54b02bbfc68479c5bfe1b13a3dc4de9c Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Tue, 1 Sep 2026 10:28:38 -0500 Subject: [PATCH 038/924] ASoC: ak4642: Drop "clocks" property presence check of_property_read_bool() should only be used on boolean properties and not used as a presence check. The of_clk_get_parent_name() function returns NULL if the "clocks" property is not present, so there is no need to check for presence first. Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260901152838.795864-1-robh@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ak4642.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 08ec2035b270..4f86728cbe0a 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -607,14 +607,13 @@ static struct clk *ak4642_of_parse_mcko(struct device *dev) struct device_node *np = dev->of_node; struct clk *clk; const char *clk_name = np->name; - const char *parent_clk_name = NULL; + const char *parent_clk_name; u32 rate; if (of_property_read_u32(np, "clock-frequency", &rate)) return NULL; - if (of_property_read_bool(np, "clocks")) - parent_clk_name = of_clk_get_parent_name(np, 0); + parent_clk_name = of_clk_get_parent_name(np, 0); of_property_read_string(np, "clock-output-names", &clk_name); From 29a88f0d6c742288d3b8c97f2f1d1b5743a7e53a Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Tue, 1 Sep 2026 10:28:55 -0500 Subject: [PATCH 039/924] ASoC: twl4030: Fix "ti,hs_extmute" property type The "ti,hs_extmute" property is a boolean, so use of_property_read_bool() rather than of_property_read_u32() to read it. Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260901152855.796769-1-robh@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 9476cdfd4dde..b11ba908ba9f 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -202,13 +202,10 @@ static void twl4030_get_board_param_values(struct twl4030_board_params *board_params, struct device_node *node) { - int value; - of_property_read_u32(node, "ti,digimic_delay", &board_params->digimic_delay); of_property_read_u32(node, "ti,ramp_delay_value", &board_params->ramp_delay_value); of_property_read_u32(node, "ti,offset_cncl_path", &board_params->offset_cncl_path); - if (!of_property_read_u32(node, "ti,hs_extmute", &value)) - board_params->hs_extmute = value; + board_params->hs_extmute = of_property_read_bool(node, "ti,hs_extmute"); if (of_property_present(node, "ti,hs_extmute_gpio")) board_params->hs_extmute = 1; From dcd84a546bebe7d79541a96688200c747acc9004 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Tue, 1 Sep 2026 10:29:04 -0500 Subject: [PATCH 040/924] ASoC: sta3xx: Fix "st,drop-compensation-ns" size The "st,drop-compensation-ns" property size is u32 not u16 based on the schema and one in-tree user. Assisted-by: Codex:gpt-5-5 Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260901152905.797279-1-robh@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/sta32x.c | 6 +++--- sound/soc/codecs/sta350.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c index d6de739212f9..7e73150f6832 100644 --- a/sound/soc/codecs/sta32x.c +++ b/sound/soc/codecs/sta32x.c @@ -1035,7 +1035,7 @@ static int sta32x_probe_dt(struct device *dev, struct sta32x_priv *sta32x) { struct device_node *np = dev->of_node; struct sta32x_platform_data *pdata; - u16 tmp; + u32 tmp; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) @@ -1060,8 +1060,8 @@ static int sta32x_probe_dt(struct device *dev, struct sta32x_priv *sta32x) of_property_read_bool(np, "st,needs_esd_watchdog"); tmp = 140; - of_property_read_u16(np, "st,drop-compensation-ns", &tmp); - pdata->drop_compensation_ns = clamp_t(u16, tmp, 0, 300) / 20; + of_property_read_u32(np, "st,drop-compensation-ns", &tmp); + pdata->drop_compensation_ns = clamp_t(u32, tmp, 0, 300) / 20; /* CONFE */ pdata->max_power_use_mpcc = diff --git a/sound/soc/codecs/sta350.c b/sound/soc/codecs/sta350.c index 2ba35076732b..208bf0e22bec 100644 --- a/sound/soc/codecs/sta350.c +++ b/sound/soc/codecs/sta350.c @@ -1087,7 +1087,7 @@ static int sta350_probe_dt(struct device *dev, struct sta350_priv *sta350) struct device_node *np = dev->of_node; struct sta350_platform_data *pdata; const char *ffx_power_mode; - u16 tmp; + u32 tmp; u8 tmp8; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); @@ -1127,8 +1127,8 @@ static int sta350_probe_dt(struct device *dev, struct sta350_priv *sta350) } tmp = 140; - of_property_read_u16(np, "st,drop-compensation-ns", &tmp); - pdata->drop_compensation_ns = clamp_t(u16, tmp, 0, 300) / 20; + of_property_read_u32(np, "st,drop-compensation-ns", &tmp); + pdata->drop_compensation_ns = clamp_t(u32, tmp, 0, 300) / 20; pdata->oc_warning_adjustment = of_property_read_bool(np, "st,overcurrent-warning-adjustment"); From a95505795efe9b18eb5a6baf6e2c41c6f8764340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan=20Ghe=C8=9Bu?= Date: Wed, 2 Sep 2026 18:53:31 +0300 Subject: [PATCH 041/924] ASoC: SOF: imx8: remove unused regmap field from imx8m_chip_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The regmap member of struct imx8m_chip_data became unused in commit a71b261c19a4 ("ASoC: SOF: imx8m: Use reset controller API to control the DSP"), which converted the runstall handling to the reset controller API and removed all accesses to it. The struct member was left behind. imx8m_probe() only assigns dap and run_stall, and the imx8ulp path stores its regmap directly in common->chip_pdata rather than in this struct. Signed-off-by: Ștefan GheÈ›u Link: https://patch.msgid.link/20260902155332.171140-1-stefanghetu9@gmail.com Signed-off-by: Mark Brown --- sound/soc/sof/imx/imx8.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c index 7e9eab2e3034..c2b4d379a3a3 100644 --- a/sound/soc/sof/imx/imx8.c +++ b/sound/soc/sof/imx/imx8.c @@ -36,7 +36,6 @@ struct imx8m_chip_data { void __iomem *dap; - struct regmap *regmap; struct reset_control *run_stall; }; From 4c4e4be4edd5dbf5f7f6a3ef4fc0c243f2d01b3c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 Sep 2026 17:08:13 +0200 Subject: [PATCH 042/924] ALSA: rawmidi: Add a proper disconnect handling The rawmidi core detaches only exposed devices at disconnection, but it doesn't deal with the pending bytes or gate the further unexpected accesses, leaving naively to each driver dealing with such situations. Let's try to restrict it in the core side for more safety: introduce the disconnected flag to each substream, set it at disconnection call, then trigger down & cancel the pending event work. Link: https://patch.msgid.link/20260903150816.1917831-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/rawmidi.h | 1 + sound/core/rawmidi.c | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index 4154035af414..d812f70725bd 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -86,6 +86,7 @@ struct snd_rawmidi_substream { bool opened; /* open flag */ bool append; /* append flag (merge more streams) */ bool active_sensing; /* send active sensing when close */ + bool disconnected; /* already disconnected */ unsigned int framing; /* whether to frame input data */ unsigned int clock_type; /* clock source to use for input framing */ int use_count; /* use counter (for output) */ diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 34b4c7d6dbe6..2e582688923d 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -208,14 +208,14 @@ static int snd_rawmidi_runtime_free(struct snd_rawmidi_substream *substream) static inline void snd_rawmidi_output_trigger(struct snd_rawmidi_substream *substream, int up) { - if (!substream->opened) + if (!substream->opened || substream->disconnected) return; substream->ops->trigger(substream, up); } static void snd_rawmidi_input_trigger(struct snd_rawmidi_substream *substream, int up) { - if (!substream->opened) + if (!substream->opened || substream->disconnected) return; substream->ops->trigger(substream, up); if (!up) @@ -254,7 +254,8 @@ int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream) scoped_guard(spinlock_irq, &substream->lock) { runtime = substream->runtime; - if (!substream->opened || !runtime || !runtime->buffer) + if (!substream->opened || !runtime || !runtime->buffer || + substream->disconnected) return -EINVAL; snd_rawmidi_buffer_ref(runtime); runtime->drain = 1; @@ -537,7 +538,7 @@ static void close_substream(struct snd_rawmidi *rmidi, if (--substream->use_count) return; - if (cleanup) { + if (cleanup && !substream->disconnected) { if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) snd_rawmidi_input_trigger(substream, 0); else { @@ -1151,7 +1152,7 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, struct snd_rawmidi_runtime *runtime; guard(spinlock_irqsave)(&substream->lock); - if (!substream->opened) + if (!substream->opened || substream->disconnected) return -EBADFD; runtime = substream->runtime; if (!runtime || !runtime->buffer) { @@ -2063,8 +2064,13 @@ static int snd_rawmidi_dev_disconnect(struct snd_device *device) struct snd_rawmidi_substream *s; list_for_each_entry(s, &rmidi->streams[dir].substreams, list) { - if (s->runtime) + scoped_guard(spinlock_irq, &s->lock) + s->disconnected = true; + if (s->runtime) { + s->ops->trigger(s, 0); + cancel_work_sync(&s->runtime->event_work); wake_up(&s->runtime->sleep); + } } } From 9e6ad2ba055f89df9f14930e50dc6a997b71e9ad Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 30 Aug 2026 10:40:47 +0200 Subject: [PATCH 043/924] ASoC: dt-bindings: imx-audmux: Replace tabs with spaces YAML files do not use tabs, even within the values in DTS. No functional change, although dt-style-check will warn about it ("[tab-in-yaml] tab character not allowed in DTS example"). Signed-off-by: Krzysztof Kozlowski Reviewed-by: Frank Li Link: https://patch.msgid.link/20260830084046.14679-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/imx-audmux.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/imx-audmux.yaml b/Documentation/devicetree/bindings/sound/imx-audmux.yaml index dab45c310670..501af1add3c1 100644 --- a/Documentation/devicetree/bindings/sound/imx-audmux.yaml +++ b/Documentation/devicetree/bindings/sound/imx-audmux.yaml @@ -101,10 +101,10 @@ examples: mux-ssi1 { fsl,audmux-port = <0>; fsl,port-config = < - IMX_AUDMUX_V2_PTCR_SYN 0 - IMX_AUDMUX_V2_PTCR_TFSEL(2) 0 - IMX_AUDMUX_V2_PTCR_TCSEL(2) 0 - IMX_AUDMUX_V2_PTCR_TFSDIR 0 + IMX_AUDMUX_V2_PTCR_SYN 0 + IMX_AUDMUX_V2_PTCR_TFSEL(2) 0 + IMX_AUDMUX_V2_PTCR_TCSEL(2) 0 + IMX_AUDMUX_V2_PTCR_TFSDIR 0 IMX_AUDMUX_V2_PTCR_TCLKDIR IMX_AUDMUX_V2_PDCR_RXDSEL(2) >; }; From ea495a3fb58fad347865105a0b5095a3aa08ab30 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:21:21 +0000 Subject: [PATCH 044/924] ASoC: soc-card: don't use card->dai_link[] in for_each_card_prelinks() We can directly use "dai_link" in for_each_card_prelinks() (A), no need to use "card->dai_link[i]" (B) there. tidyup it. static void soc_card_check_tplg_fes(...) { ... (A) for_each_card_prelinks(card, i, dai_link) { ... ^^^^^^^^^^^^^^^^^ dev_dbg(..., (B) card->dai_link[i].name); ... ^^^^^^^^^^^^^^^^^ } } Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/874ig7yvse.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index b9c70268d49f..8149981f0dc2 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1859,7 +1859,7 @@ static void soc_check_tplg_fes(struct snd_soc_card *card) } dev_dbg(card->dev, "info: override BE DAI link %s\n", - card->dai_link[i].name); + dai_link->name); /* override platform component */ if (!dai_link->platforms) { From 3b05666859ba8eddfa146f6dd54ef760744951c7 Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Thu, 3 Sep 2026 11:38:44 +0800 Subject: [PATCH 045/924] ASoC: codecs: nau8360: Fix static declarations and endianness Fix warnings reported by the kernel test robot: 1. Make the 'nau8360_dsp_addr' array static to resolve namespace pollution. 2. Change the variable type to '__be16' to fix the incorrect type. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609021121.a4mHPAyZ-lkp@intel.com/ Signed-off-by: Neo Chang Link: https://patch.msgid.link/20260903033845.76540-1-YLCHANG2@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8360-dsp.c | 2 +- sound/soc/codecs/nau8360.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/nau8360-dsp.c b/sound/soc/codecs/nau8360-dsp.c index 2d6bc2962229..41afccbea2c0 100644 --- a/sound/soc/codecs/nau8360-dsp.c +++ b/sound/soc/codecs/nau8360-dsp.c @@ -20,7 +20,7 @@ #include "nau8360.h" #define NAU8360_DSP_IDLE_RETRY 10 -const unsigned short nau8360_dsp_addr[NAU8360_DSP_FW_NUM] = { +static const unsigned short nau8360_dsp_addr[NAU8360_DSP_FW_NUM] = { NAU8360_RF000_DSP_COMM, NAU8360_RF002_DSP_COMM }; static int nau8360_dsp_chan_kcs_setup(struct snd_soc_component *cp, diff --git a/sound/soc/codecs/nau8360.c b/sound/soc/codecs/nau8360.c index d7af2dffb749..802806d92fc4 100644 --- a/sound/soc/codecs/nau8360.c +++ b/sound/soc/codecs/nau8360.c @@ -409,7 +409,7 @@ static int nau8360_peq_coeff_get(struct snd_kcontrol *kcontrol, struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); struct soc_bytes_ext *params = (void *)kcontrol->private_value; int i, value, reg, ret = 0; - u16 *val = (u16 *)ucontrol->value.bytes.data; + __be16 *val = (__be16 *)ucontrol->value.bytes.data; /* Use the DAPM lock to prevent race conditions during DAPM power-up * state transitions, and check component active status to prohibit From aa19a6de4a08347b55d0ba45c9aba5c35376d1fc Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Thu, 3 Sep 2026 11:38:45 +0800 Subject: [PATCH 046/924] ASoC: codecs: nau8360: Fix kernel-doc issues Fix various warnings reported by the kernel test robot: Correct kernel-doc formatting and warnings. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609021537.D6X4Dz93-lkp@intel.com/ Signed-off-by: Neo Chang Link: https://patch.msgid.link/20260903033845.76540-2-YLCHANG2@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8360-dsp.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/nau8360-dsp.c b/sound/soc/codecs/nau8360-dsp.c index 41afccbea2c0..2e5b61bfc303 100644 --- a/sound/soc/codecs/nau8360-dsp.c +++ b/sound/soc/codecs/nau8360-dsp.c @@ -39,7 +39,7 @@ static int nau8360_dsp_chan_kcs_setup(struct snd_soc_component *cp, .cmd_id = _id, \ } -/** +/* * Preamble Fragment Masks: * FIELD | BITS | SHIFT | MASK | DETAILS * ----------------|--------|-------|------|--------------------------------- @@ -53,7 +53,7 @@ static int nau8360_dsp_chan_kcs_setup(struct snd_soc_component *cp, #define NAU8360_HOST_LEN_LOW_MASK GENMASK(17, 16) #define NAU8360_HOST_PREAMBLE_MASK GENMASK(15, 0) -/** +/* * Payload Fragment Masks: * FIELD | BITS | SHIFT | MASK | DETAILS * ----------------|--------|-------|------|--------------------------------- @@ -63,7 +63,7 @@ static int nau8360_dsp_chan_kcs_setup(struct snd_soc_component *cp, #define NAU8360_HOST_PARAM_SIZE_MASK GENMASK(31, 16) #define NAU8360_HOST_PARAM_OFFSET_MASK GENMASK(15, 0) -/** +/* * Trailing Fragment Masks: * FIELD | BITS | SHIFT | MASK | DETAILS * ----------------|--------|-------|------|--------------------------------- @@ -76,7 +76,7 @@ static int nau8360_dsp_chan_kcs_setup(struct snd_soc_component *cp, #define NAU8360_TRAIL_PAD_MASK GENMASK(13, 12) #define NAU8360_TRAIL_LEN_HIGH_MASK GENMASK(15, 14) -/** +/* * Reply Preamble Masks: * FIELD | BITS | SHIFT | MASK | DETAILS * ----------------|--------|-------|------|--------------------------------- @@ -364,7 +364,7 @@ static int nau8360_reply_from_dsp(struct snd_soc_component *cp, /** * nau8360_send_dsp_command - Send command to DSP * - * @component: component to register + * @cp: component to register * @cmd_id: DSP supported command ID * @kcs_setup: KCS setup structure * @dsp_addr: DSP address @@ -454,7 +454,7 @@ static inline int nau8360_send_dsp_broadcast(struct snd_soc_component *cp, int c /** * nau8360_dsp_kcs_setup - Send KCS setup command to DSP * - * @component: component to register + * @cp: component to register * @offset: address offset relative to KCS start * @size: size of data writen to KCS * @data: data writen to KCS setup From 366e4d4f0ca818fc4518b7eccbe243c6cd3161d2 Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Fri, 4 Sep 2026 14:00:35 +0800 Subject: [PATCH 047/924] ASoC: codecs: nau8360: Add default PEQ band number for DSP init Set a default PEQ band number during DSP initialization to prevent invalid state or uninitialized parameters before user configuration is applied. Signed-off-by: Neo Chang Link: https://patch.msgid.link/20260904060035.196399-3-YLCHANG2@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8360.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/codecs/nau8360.c b/sound/soc/codecs/nau8360.c index 802806d92fc4..89b8ee80d6c8 100644 --- a/sound/soc/codecs/nau8360.c +++ b/sound/soc/codecs/nau8360.c @@ -1602,6 +1602,8 @@ static void nau8360_coeff_set_def(struct nau8360 *nau8360) i * NAU8360_TOT_BAND_COE_RANGE, 0x20); } nau8360_peq_mem_enable(regmap, false); + regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_BAND_MASK, + NAU8360_PEQ_BAND_8 << NAU8360_PEQ_BAND_SFT); mutex_unlock(&nau8360->lock); } From 8351ccada42181a0792ca9b26b244b8748b05061 Mon Sep 17 00:00:00 2001 From: Ricardo Rivera-Matos Date: Fri, 28 Aug 2026 10:46:26 -0500 Subject: [PATCH 048/924] ASoC: dt-bindings: cs35l45: Adds SYNC properties Introduces support for the multidevice synchronization (SYNC) bus. The properties introduced allow for the configuration of the device's transmitter IDs. Signed-off-by: Ricardo Rivera-Matos Link: https://patch.msgid.link/20260828-sound-for-next-cs35l45-sync-v4-v5-1-fe3feba709ff@opensource.cirrus.com Signed-off-by: Mark Brown --- .../bindings/sound/cirrus,cs35l45.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml index 70f6c62aedca..fd0646b609f9 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml @@ -53,6 +53,24 @@ properties: maximum: 3 default: 2 + cirrus,sync-lsw-txid: + description: + Transmitter ID to use for long software (LSW) data packets + on the SYNC bus. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 7 + default: 0 + + cirrus,sync-sw-txid: + description: + Transmitter ID to use for short software (SW) data packets + on the SYNC bus. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 7 + default: 0 + patternProperties: "^cirrus,gpio-ctrl[1-3]$": description: @@ -145,6 +163,8 @@ examples: reset-gpios = <&gpio 110 0>; cirrus,asp-sdout-hiz-ctrl = <(CS35L45_ASP_TX_HIZ_UNUSED | CS35L45_ASP_TX_HIZ_DISABLED)>; + cirrus,sync-lsw-txid = <0x1>; + cirrus,sync-sw-txid = <0x1>; cirrus,gpio-ctrl1 { gpio-ctrl = <0x2>; }; From f3c8f89a1502d10ce3c19be9d28e44deb09b541b Mon Sep 17 00:00:00 2001 From: Ricardo Rivera-Matos Date: Fri, 28 Aug 2026 10:46:27 -0500 Subject: [PATCH 049/924] ASoC: cs35l45: Adds support for SYNC bus Adds support for the multidevice synchronization (SYNC) bus. The SYNC bus is an opaque one wire bus that is shared between Cirrus devices. Initial support only includes software (SW) data packets. Signed-off-by: Ricardo Rivera-Matos Link: https://patch.msgid.link/20260828-sound-for-next-cs35l45-sync-v4-v5-2-fe3feba709ff@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l45-tables.c | 4 ++ sound/soc/codecs/cs35l45.c | 89 +++++++++++++++++++++++++++++-- sound/soc/codecs/cs35l45.h | 23 ++++++++ 3 files changed, 112 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/cs35l45-tables.c b/sound/soc/codecs/cs35l45-tables.c index 764dbaa35042..57982b8ae645 100644 --- a/sound/soc/codecs/cs35l45-tables.c +++ b/sound/soc/codecs/cs35l45-tables.c @@ -51,6 +51,8 @@ static const struct reg_default cs35l45_defaults[] = { { CS35L45_WKI2C_CTL, 0x00000030 }, { CS35L45_REFCLK_INPUT, 0x00000510 }, { CS35L45_GLOBAL_SAMPLE_RATE, 0x00000003 }, + { CS35L45_SYNC_TX_RX_ENABLES, 0x00000200 }, + { CS35L45_SYNC_SW_TX_ID, 0x00000000 }, { CS35L45_ASP_ENABLES1, 0x00000000 }, { CS35L45_ASP_CONTROL1, 0x00000028 }, { CS35L45_ASP_CONTROL2, 0x18180200 }, @@ -134,6 +136,8 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_PWRMGT_STS: case CS35L45_REFCLK_INPUT: case CS35L45_GLOBAL_SAMPLE_RATE: + case CS35L45_SYNC_TX_RX_ENABLES: + case CS35L45_SYNC_SW_TX_ID: case CS35L45_ASP_ENABLES1: case CS35L45_ASP_CONTROL1: case CS35L45_ASP_CONTROL2: diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index a032bb23b4ac..763cd83b5d6d 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -6,6 +6,7 @@ // // Author: James Schulman +#include #include #include #include @@ -195,6 +196,46 @@ static int cs35l45_activate_ctl(struct snd_soc_component *component, return 0; } +static int cs35l45_sync_en_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cs35l45_private *cs35l45 = + snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = cs35l45->sync_en; + + return 0; +} + +static int cs35l45_sync_en_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cs35l45_private *cs35l45 = + snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = + snd_soc_component_to_dapm(component); + + snd_soc_dapm_mutex_lock(dapm); + + if ((bool)ucontrol->value.integer.value[0] == cs35l45->sync_en) { + snd_soc_dapm_mutex_unlock(dapm); + return 0; + } + + if ((bool)ucontrol->value.integer.value[0]) + regmap_set_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES2, CS35L45_SYNC_EN_MASK); + else + regmap_clear_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES2, CS35L45_SYNC_EN_MASK); + + cs35l45->sync_en = (bool)ucontrol->value.integer.value[0]; + + snd_soc_dapm_mutex_unlock(dapm); + + return 1; +} + static int cs35l45_amplifier_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -591,6 +632,18 @@ static SOC_ENUM_SINGLE_DECL(amplifier_mode_enum, SND_SOC_NOPM, 0, static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 1000, 300, 0); static const DECLARE_TLV_DB_SCALE(cs35l45_dig_pcm_vol_tlv, -10225, 25, true); +static const struct snd_kcontrol_new cs35l45_sync_controls[] = { + SOC_SINGLE_BOOL_EXT("SYNC Switch", 0, cs35l45_sync_en_get, cs35l45_sync_en_put), + SOC_SINGLE("SYNC LSW RX Switch", CS35L45_SYNC_TX_RX_ENABLES, + CS35L45_SYNC_LSW_RX_EN_SHIFT, 1, 0), + SOC_SINGLE("SYNC LSW TX Switch", CS35L45_SYNC_TX_RX_ENABLES, + CS35L45_SYNC_LSW_TX_EN_SHIFT, 1, 0), + SOC_SINGLE("SYNC SW RX Switch", CS35L45_SYNC_TX_RX_ENABLES, + CS35L45_SYNC_SW_RX_EN_SHIFT, 1, 0), + SOC_SINGLE("SYNC SW TX Switch", CS35L45_SYNC_TX_RX_ENABLES, + CS35L45_SYNC_SW_TX_EN_SHIFT, 1, 0), +}; + static const struct snd_kcontrol_new cs35l45_controls[] = { SOC_ENUM_EXT("Amplifier Mode", amplifier_mode_enum, cs35l45_amplifier_mode_get, cs35l45_amplifier_mode_put), @@ -880,6 +933,19 @@ static struct snd_soc_dai_driver cs35l45_dai[] = { static int cs35l45_component_probe(struct snd_soc_component *component) { struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_soc_add_component_controls(component, cs35l45_controls, + ARRAY_SIZE(cs35l45_controls)); + if (ret < 0) + return ret; + + if (cs35l45->sync_pin_set) { + ret = snd_soc_add_component_controls(component, cs35l45_sync_controls, + ARRAY_SIZE(cs35l45_sync_controls)); + if (ret < 0) + return ret; + } return wm_adsp2_component_probe(&cs35l45->dsp, component); } @@ -901,9 +967,6 @@ static const struct snd_soc_component_driver cs35l45_component = { .dapm_routes = cs35l45_dapm_routes, .num_dapm_routes = ARRAY_SIZE(cs35l45_dapm_routes), - .controls = cs35l45_controls, - .num_controls = ARRAY_SIZE(cs35l45_controls), - .name = "cs35l45", .endianness = 1, @@ -1064,6 +1127,19 @@ static int cs35l45_sys_resume(struct device *dev) return 0; } +static void cs35l45_apply_sync_property_config(struct cs35l45_private *cs35l45) +{ + unsigned int val; + + if (device_property_read_u32(cs35l45->dev, "cirrus,sync-lsw-txid", &val) == 0) + regmap_update_bits(cs35l45->regmap, CS35L45_SYNC_SW_TX_ID, + CS35L45_SYNC_LSW_TXID_MASK, val << CS35L45_SYNC_LSW_TXID_SHIFT); + + if (device_property_read_u32(cs35l45->dev, "cirrus,sync-sw-txid", &val) == 0) + regmap_update_bits(cs35l45->regmap, CS35L45_SYNC_SW_TX_ID, + CS35L45_SYNC_SW_TXID_MASK, val); +} + static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) { struct device_node *node = cs35l45->dev->of_node; @@ -1110,10 +1186,15 @@ static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) val << CS35L45_GPIO_POL_SHIFT); ret = of_property_read_u32(child, "gpio-ctrl", &val); - if (!ret) + if (!ret) { + if ((i == 0) && (val == CS35L45_GP1_CTRL_MDSYNC)) { + cs35l45->sync_pin_set = true; + cs35l45_apply_sync_property_config(cs35l45); + } regmap_update_bits(cs35l45->regmap, pad_regs[i], CS35L45_GPIO_CTRL_MASK, val << CS35L45_GPIO_CTRL_SHIFT); + } ret = of_property_read_u32(child, "gpio-invert", &val); if (!ret) { diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h index 7a790d2acac7..f8a4713ae92f 100644 --- a/sound/soc/codecs/cs35l45.h +++ b/sound/soc/codecs/cs35l45.h @@ -35,6 +35,8 @@ #define CS35L45_PWRMGT_STS 0x0000290C #define CS35L45_REFCLK_INPUT 0x00002C04 #define CS35L45_GLOBAL_SAMPLE_RATE 0x00002C0C +#define CS35L45_SYNC_TX_RX_ENABLES 0x00003400 +#define CS35L45_SYNC_SW_TX_ID 0x00003408 #define CS35L45_BOOST_CCM_CFG 0x00003808 #define CS35L45_BOOST_DCM_CFG 0x0000380C #define CS35L45_BOOST_OV_CFG 0x0000382C @@ -183,6 +185,8 @@ #define CS35L45_ASP_EN_SHIFT 27 #define CS35L45_AMP_DRE_EN_SHIFT 20 #define CS35L45_AMP_DRE_EN_MASK BIT(20) +#define CS35L45_SYNC_EN_SHIFT 8 +#define CS35L45_SYNC_EN_MASK BIT(8) #define CS35L45_MEM_RDY_SHIFT 1 #define CS35L45_MEM_RDY_MASK BIT(1) @@ -220,6 +224,22 @@ #define CS35L45_44P100_KHZ 0x0B #define CS35L45_88P200_KHZ 0x0C +/* SYNC_TX_RX_ENABLES */ +#define CS35L45_SYNC_LSW_RX_EN_SHIFT 19 +#define CS35L45_SYNC_LSW_RX_EN_MASK BIT(19) +#define CS35L45_SYNC_LSW_TX_EN_SHIFT 18 +#define CS35L45_SYNC_LSW_TX_EN_MASK BIT(18) +#define CS35L45_SYNC_SW_RX_EN_SHIFT 17 +#define CS35L45_SYNC_SW_RX_EN_MASK BIT(17) +#define CS35L45_SYNC_SW_TX_EN_SHIFT 16 +#define CS35L45_SYNC_SW_TX_EN_MASK BIT(16) + +/* SYNC_SW_TX_ID */ +#define CS35L45_SYNC_LSW_TXID_SHIFT 8 +#define CS35L45_SYNC_LSW_TXID_MASK GENMASK(10, 8) +#define CS35L45_SYNC_SW_TXID_SHIFT 0 +#define CS35L45_SYNC_SW_TXID_MASK GENMASK(2, 0) + /* ASP_ENABLES_1 */ #define CS35L45_ASP_RX2_EN_SHIFT 17 #define CS35L45_ASP_RX1_EN_SHIFT 16 @@ -320,6 +340,7 @@ #define CS35L45_GPIO_CTRL_MASK GENMASK(22, 20) #define CS35L45_GPIO_INVERT_SHIFT 19 #define CS35L45_GPIO_INVERT_MASK BIT(19) +#define CS35L45_GP1_CTRL_MDSYNC 0x2 /* CS35L45_IRQ1_EINT_1 */ #define CS35L45_BST_UVP_ERR_SHIFT 7 @@ -493,6 +514,8 @@ struct cs35l45_private { struct regulator *vdd_a; bool initialized; bool sysclk_set; + bool sync_en; + bool sync_pin_set; u8 slot_width; u8 slot_count; int amplifier_mode; From 88be49b2f8482a438068243e36b50debc56c4d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan=20Ghe=C8=9Bu?= Date: Wed, 2 Sep 2026 20:55:17 +0300 Subject: [PATCH 050/924] ASoC: SOF: imx8: check imx_sc_pm_cpu_start() return value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit imx8_run(), imx8x_run() and imx8_shutdown() check the return value of every imx_sc_misc_set_control() call but ignore the one from imx_sc_pm_cpu_start(), which propagates errors from imx_scu_call_rpc() and can fail with -ETIMEDOUT, -EINVAL or -EPERM. Reporting success after a failed start leaves the caller loading firmware onto a core that is not running, and a failed shutdown is similarly hidden from imx_chip_core_shutdown() callers, which do act on the return value. Propagate the error, matching imx95_core_shutdown() in imx9.c. Signed-off-by: Ștefan GheÈ›u Link: https://patch.msgid.link/20260902175517.209406-1-stefanghetu9@gmail.com Signed-off-by: Mark Brown --- sound/soc/sof/imx/imx8.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c index c2b4d379a3a3..0820724dbb34 100644 --- a/sound/soc/sof/imx/imx8.c +++ b/sound/soc/sof/imx/imx8.c @@ -41,13 +41,18 @@ struct imx8m_chip_data { static int imx8_shutdown(struct snd_sof_dev *sdev) { + int ret; /* * Force the DSP to stall. After the firmware image is loaded, * the stall will be removed during run() by a matching * imx_sc_pm_cpu_start() call. */ - imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, false, - RESET_VECTOR_VADDR); + ret = imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, false, + RESET_VECTOR_VADDR); + if (ret < 0) { + dev_err(sdev->dev, "Error stalling DSP core %d\n", ret); + return ret; + } return 0; } @@ -88,8 +93,12 @@ static int imx8x_run(struct snd_sof_dev *sdev) return ret; } - imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, true, - RESET_VECTOR_VADDR); + ret = imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, true, + RESET_VECTOR_VADDR); + if (ret < 0) { + dev_err(sdev->dev, "Error starting DSP %d\n", ret); + return ret; + } return 0; } @@ -105,8 +114,12 @@ static int imx8_run(struct snd_sof_dev *sdev) return ret; } - imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, true, - RESET_VECTOR_VADDR); + ret = imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, true, + RESET_VECTOR_VADDR); + if (ret < 0) { + dev_err(sdev->dev, "Error starting DSP %d\n", ret); + return ret; + } return 0; } From 201af7ba51a1211fde591b19e260a342396ac5cc Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 10:23:29 +0530 Subject: [PATCH 051/924] ASoC: codecs: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260907045331.16932-2-hemanth.selam@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8962.c | 4 ++-- sound/soc/codecs/wm8996.c | 2 +- sound/soc/codecs/wm_hubs.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index 8a9598161b35..af4716051220 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -3430,7 +3430,7 @@ static int wm8962_gpio_request(struct gpio_chip *chip, unsigned offset) /* The WM8962 GPIOs aren't linearly numbered. For simplicity * we export linear numbers and error out if the unsupported - * ones are requsted. + * ones are requested. */ switch (offset + 1) { case 2: @@ -3813,7 +3813,7 @@ static int wm8962_i2c_probe(struct i2c_client *i2c) regmap_update_bits(wm8962->regmap, WM8962_EQ1, WM8962_EQ_SHARED_COEFF, 0); - /* Don't debouce interrupts so we don't need SYSCLK */ + /* Don't debounce interrupts so we don't need SYSCLK */ regmap_update_bits(wm8962->regmap, WM8962_IRQ_DEBOUNCE, WM8962_FLL_LOCK_DB | WM8962_PLL3_LOCK_DB | WM8962_PLL2_LOCK_DB | WM8962_TEMP_SHUT_DB, diff --git a/sound/soc/codecs/wm8996.c b/sound/soc/codecs/wm8996.c index dface555f928..e7a20ce39940 100644 --- a/sound/soc/codecs/wm8996.c +++ b/sound/soc/codecs/wm8996.c @@ -2106,7 +2106,7 @@ static int wm8996_set_fll(struct snd_soc_component *component, int fll_id, int s if (i2c->irq) timeout *= 10; else - /* ensure timeout of atleast 1 jiffies */ + /* ensure timeout of at least 1 jiffies */ timeout = (timeout/2) ? : 1; for (retry = 0; retry < 10; retry++) { diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index f80dfc488e3c..019940589e51 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -338,7 +338,7 @@ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, ret = snd_soc_put_volsw(kcontrol, ucontrol); /* If we're applying an offset correction then updating the - * callibration would be likely to introduce further offsets. */ + * calibration would be likely to introduce further offsets. */ if (hubs->dcs_codes_l || hubs->dcs_codes_r || hubs->no_series_update) return ret; From 20a812a1f664ca8ab224a257fdcf0e73e8c9e44e Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 10:23:30 +0530 Subject: [PATCH 052/924] firmware: cirrus: fix typo "upto" in comment Correct "upto" to "up to", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260907045331.16932-3-hemanth.selam@gmail.com Signed-off-by: Mark Brown --- drivers/firmware/cirrus/cs_dsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index bb93b24c659d..04015280ac5d 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -3505,7 +3505,7 @@ EXPORT_SYMBOL_NS_GPL(cs_dsp_chunk_write, "FW_CS_DSP"); * * As cs_dsp_chunk_write only writes data when a whole DSP word is ready to * be written out it is possible that some data will remain in the cache, this - * function will pad that data with zeros upto a whole DSP word and write out. + * function will pad that data with zeros up to a whole DSP word and write out. * * Return: Zero for success, a negative number on error. */ From 349e94679968785803cc32c1d2e41e000e8afd5f Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 6 Sep 2026 11:42:14 +0800 Subject: [PATCH 053/924] ASoC: es8326: report resume restore errors es8326_resume() ignores the status-register read, the direct clock-state write and the register-cache replay. A read failure can also leave reg uninitialized before it selects the reset path, and a cache failure is followed by jack IRQ processing. Return these errors before later resume work. The ASoC component wrapper reports the callback failure while retaining its best-effort resume contract. The issue was found by our static-analysis tool and manually reviewed. Fixes: 5c439937775d ("ASoC: codecs: add support for ES8326") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260906034217.85696-2-hppiscas@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8326.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c index c5460589a88b..ab0280b06e5c 100644 --- a/sound/soc/codecs/es8326.c +++ b/sound/soc/codecs/es8326.c @@ -1118,18 +1118,28 @@ static int es8326_resume(struct snd_soc_component *component) { struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); unsigned int reg; + int ret; regcache_cache_only(es8326->regmap, false); regcache_cache_bypass(es8326->regmap, true); - regmap_read(es8326->regmap, ES8326_CLK_RESAMPLE, ®); + ret = regmap_read(es8326->regmap, ES8326_CLK_RESAMPLE, ®); regcache_cache_bypass(es8326->regmap, false); - /* reset internal clock state */ - if (reg == 0x05) - regmap_write(es8326->regmap, ES8326_CLK_CTL, ES8326_CLK_ON); - else - es8326_init(component); + if (ret) + return ret; - regcache_sync(es8326->regmap); + /* reset internal clock state */ + if (reg == 0x05) { + ret = regmap_write(es8326->regmap, ES8326_CLK_CTL, + ES8326_CLK_ON); + if (ret) + return ret; + } else { + es8326_init(component); + } + + ret = regcache_sync(es8326->regmap); + if (ret) + return ret; es8326_irq(es8326->irq, es8326); return 0; From 8f43acfd3addf10fbde8d21dd44b1a61e1936ba7 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 6 Sep 2026 11:42:15 +0800 Subject: [PATCH 054/924] ASoC: es8375: report resume restore errors es8375_resume() ignores the clock-status read, bias restoration and register-cache replay. A read failure can leave reg uninitialized before it selects the initialization path, and later resume processing cannot observe a failed restore. Return the fallible operation results to the ASoC component wrapper, which reports callback failures while retaining its best-effort resume contract. The issue was found by our static-analysis tool and manually reviewed. Fixes: de2b3119f9f7 ("ASoC: codecs: add support for ES8375") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260906034217.85696-3-hppiscas@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8375.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/es8375.c b/sound/soc/codecs/es8375.c index e8747bc24433..2c58225feddd 100644 --- a/sound/soc/codecs/es8375.c +++ b/sound/soc/codecs/es8375.c @@ -577,20 +577,24 @@ static int es8375_resume(struct snd_soc_component *component) { struct es8375_priv *es8375 = snd_soc_component_get_drvdata(component); unsigned int reg; + int ret; regcache_cache_only(es8375->regmap, false); regcache_cache_bypass(es8375->regmap, true); - regmap_read(es8375->regmap, ES8375_CLK_MGR2, ®); + ret = regmap_read(es8375->regmap, ES8375_CLK_MGR2, ®); regcache_cache_bypass(es8375->regmap, false); + if (ret) + return ret; - if (reg == 0x00) + if (reg == 0x00) { es8375_init(component); - else - es8375_set_bias_level(component, SND_SOC_BIAS_ON); + } else { + ret = es8375_set_bias_level(component, SND_SOC_BIAS_ON); + if (ret) + return ret; + } - regcache_sync(es8375->regmap); - - return 0; + return regcache_sync(es8375->regmap); } static int es8375_codec_probe(struct snd_soc_component *component) From 8f7d23eb8ffb817ba69049577ab8ca55b93c77cf Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 6 Sep 2026 11:42:16 +0800 Subject: [PATCH 055/924] ASoC: es8389: report resume restore errors es8389_resume() ignores the reset-register read, bias restoration and register-cache replay. A failed read can leave regv uninitialized before it selects the initialization path. Check those operations and always leave cache bypass before returning an error. The ASoC component wrapper reports callback failures while retaining its best-effort resume contract. The issue was found by our static-analysis tool and manually reviewed. Fixes: 0319c26889f7 ("ASoC: codecs: add support for ES8389") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260906034217.85696-4-hppiscas@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8389.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c index 80efce3e0a22..204fe6731140 100644 --- a/sound/soc/codecs/es8389.c +++ b/sound/soc/codecs/es8389.c @@ -1026,20 +1026,28 @@ static int es8389_resume(struct snd_soc_component *component) { struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); unsigned int regv; + int ret; regcache_cache_only(es8389->regmap, false); regcache_cache_bypass(es8389->regmap, true); - regmap_read(es8389->regmap, ES8389_RESET, ®v); + ret = regmap_read(es8389->regmap, ES8389_RESET, ®v); + if (ret) + goto disable_bypass; - if (regv == 0xff) + if (regv == 0xff) { es8389_init(component); - else - es8389_set_bias_level(component, SND_SOC_BIAS_ON); + } else { + ret = es8389_set_bias_level(component, SND_SOC_BIAS_ON); + if (ret) + goto disable_bypass; + } +disable_bypass: regcache_cache_bypass(es8389->regmap, false); - regcache_sync(es8389->regmap); + if (ret) + return ret; - return 0; + return regcache_sync(es8389->regmap); } static int es8389_probe(struct snd_soc_component *component) From aa7d985d85529049fc8c39c168b300f59de4fdf8 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 6 Sep 2026 11:42:17 +0800 Subject: [PATCH 056/924] ASoC: fs210x: report register restore errors fs210x_init_chip() marks the register cache dirty and replays it after reset and scene setup, but ignores a replay failure and publishes is_inited. Its resume caller already returns the helper status. Keep is_inited clear and return a cache replay failure so the ASoC component wrapper can report the incomplete restoration. The issue was found by our static-analysis tool and manually reviewed. Fixes: 756117701779 ("ASoC: codecs: Add FourSemi FS2104/5S audio amplifier driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260906034217.85696-5-hppiscas@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/fs210x.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c index 510cbeea5b62..316f3fe80d93 100644 --- a/sound/soc/codecs/fs210x.c +++ b/sound/soc/codecs/fs210x.c @@ -610,8 +610,9 @@ static int fs210x_init_chip(struct fs210x_priv *fs210x) if (!ret) { regcache_cache_only(fs210x->regmap, false); regcache_mark_dirty(fs210x->regmap); - regcache_sync(fs210x->regmap); - fs210x->is_inited = true; + ret = regcache_sync(fs210x->regmap); + if (!ret) + fs210x->is_inited = true; } return ret; From 4ee51cf8141396e2b59c27cd983c1d5c9fd48297 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Tue, 8 Sep 2026 03:34:39 +0800 Subject: [PATCH 057/924] ALSA: usb-audio: Convert snd_usb_apply_interface_quirk() into a switch statement A switch statement is more readable than the current if blocks. Also sort the cases in an ascending order. As an interesting effect, this shrinks the size of quirks.o by 64 bytes (GCC 16 -O2 x86_64): text data bss total filename (before) 15266 12279 0 27545 quirks.o text data bss total filename (before) 15202 12279 0 27481 quirks.o Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260908-uac-hp-elite-x3-lap-dock-v1-1-e226cfe4038e@rong.moe --- sound/usb/quirks.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 4936d66f9591..61cb3f769012 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -1590,18 +1590,20 @@ int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip, int iface, int altno) { - /* audiophile usb: skip altsets incompatible with device_setup */ - if (chip->usb_id == USB_ID(0x0763, 0x2003)) - return audiophile_skip_setting_quirk(chip, iface, altno); + switch (chip->usb_id) { /* quattro usb: skip altsets incompatible with device_setup */ - if (chip->usb_id == USB_ID(0x0763, 0x2001)) + case USB_ID(0x0763, 0x2001): return quattro_skip_setting_quirk(chip, iface, altno); + /* audiophile usb: skip altsets incompatible with device_setup */ + case USB_ID(0x0763, 0x2003): + return audiophile_skip_setting_quirk(chip, iface, altno); /* fasttrackpro usb: skip altsets incompatible with device_setup */ - if (chip->usb_id == USB_ID(0x0763, 0x2012)) + case USB_ID(0x0763, 0x2012): return fasttrackpro_skip_setting_quirk(chip, iface, altno); /* presonus studio 1810c: skip altsets incompatible with device_setup */ - if (chip->usb_id == USB_ID(0x194f, 0x010c)) + case USB_ID(0x194f, 0x010c): return s1810c_skip_setting_quirk(chip, iface, altno); + } return 0; } From 8dbd4900598a2c0f486a2cee8cc548b018f75b3b Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Tue, 8 Sep 2026 03:34:40 +0800 Subject: [PATCH 058/924] ALSA: usb-audio: Add quirks for HP Elite x3 Lap Dock The HP Elite x3 Lap Dock is a laptop-like external display device with a FHD screen, a keyboard, and a MT touchpad. It can be connected via USB-C or Miracast. As an e-waste collector, I recently brought one for 69 CNY (10.2 USD). The price already indicates how broken the device's design and compatibility are ;-P When connecting via USB-C, the video source is, of course, DisplayPort Alternate Mode. However, the audio source has nothing to do with the DisplayPort signal and is actually from a builtin UAC device. The builtin UAC device provides three Alternate Settings: - 1:1 - Capture, S16_LE, 8000/32000/44100/48000Hz - 2:1 - Playback, S16_LE, 48000Hz - 2:2 - Playback, S24_3LE, 48000Hz Unfortunately, the last one is broken because: - it doesn't accept SET_CUR(SAMPLE_RATE). QUIRK_FLAG_FIXED_RATE works around it, however... - it constantly produces severe harmonic distortion once the capture stream is also opened. The interface 2 must be closed and reopened to make it recover. IOW, simply closing the capture stream makes no difference. Considering that S24_3LE offers no additional benefit on small speakers compared to S16_LE, and 2:1 is always usable as an alternative, skip 2:2 to get rid of the trouble. Setting chip->setup to any non-default value disables the fixup and reenables 2:2 (in this case QUIRK_FLAG_FIXED_RATE is required). Quirky device sample: usb 7-1.4: new full-speed USB device number 50 using xhci_hcd usb 7-1.4: New USB device found, idVendor=03f0, idProduct=0c56, bcdDevice= 0.00 usb 7-1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=0 usb 7-1.4: Product: HP Elite x3 Lap Dock usb 7-1.4: Manufacturer: HP usb 7-1.4: Found last interface = 0 usb 7-1.4: 1:1: add audio endpoint 0x81 usb 7-1.4: Creating new data endpoint #81 usb 7-1.4: 1:1 Set sample rate 48000, clock 0 usb 7-1.4: 2:1: add audio endpoint 0x1 usb 7-1.4: Creating new data endpoint #1 usb 7-1.4: 2:1 Set sample rate 48000, clock 0 usb 7-1.4: 2:2: add audio endpoint 0x1 usb 7-1.4: 2:2 Set sample rate 48000, clock 0 usb 7-1.4: 2:2: cannot set freq 48000 to ep 0x1 usb 7-1.4: [9] FU [Sidetone Playback Switch] ch = 1, val = 0/1/1 usb 7-1.4: cannot set ctl value: req = 0x4, wValue = 0x200, wIndex = 0x900, type = 4, data = 0x40/0x0 usb 7-1.4: [9] FU [Sidetone Playback Volume] ch = 1, val = -17664/0/128 usb 7-1.4: [2] FU [Headset Playback Switch] ch = 1, val = 0/1/1 usb 7-1.4: [2] FU [Headset Playback Volume] ch = 2, val = -18944/0/1 usb 7-1.4: [6] FU [Headset Capture Switch] ch = 1, val = 0/1/1 usb 7-1.4: [6] FU [Headset Capture Volume] ch = 2, val = -18944/0/1 input: HP HP Elite x3 Lap Dock Consumer Control as /devices/pci0000:00/0000:00:08.3/0000:c9:00.4/usb7/7-1/7-1.4/7-1.4:1.3/0003:03F0:0C56.0033/input/input129 input: HP HP Elite x3 Lap Dock as /devices/pci0000:00/0000:00:08.3/0000:c9:00.4/usb7/7-1/7-1.4/7-1.4:1.3/0003:03F0:0C56.0033/input/input130 hid-generic 0003:03F0:0C56.0033: input,hiddev100,hidraw8: USB HID v1.11 Device [HP HP Elite x3 Lap Dock] on usb-0000:c9:00.4-1.4/input3 Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260908-uac-hp-elite-x3-lap-dock-v1-2-e226cfe4038e@rong.moe --- sound/usb/quirks.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 61cb3f769012..8c6593ed6d2e 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -1586,11 +1586,47 @@ static int s1810c_skip_setting_quirk(struct snd_usb_audio *chip, return 0; } +static int hp_elite_x3_lap_dock_skip_setting_quirk(struct snd_usb_audio *chip, + int iface, int altno) +{ + /* + * 1:1 - Capture, S16_LE, 8000/32000/44100/48000Hz + * 2:1 - Playback, S16_LE, 48000Hz + * 2:2 - Playback, S24_3LE, 48000Hz + * + * 2:2 is broken because: + * - it doesn't accept SET_CUR(SAMPLE_RATE). QUIRK_FLAG_FIXED_RATE works + * around it, however... + * - it constantly produces severe harmonic distortion once the capture + * stream is also opened. The interface 2 must be closed and reopened + * to make it recover. IOW, simply closing the capture stream makes no + * difference. + * + * Considering that S24_3LE offers no additional benefit on small + * speakers compared to S16_LE, and 2:1 is always usable as an + * alternative, skip 2:2 to get rid of the trouble. + * + * Setting chip->setup to any non-default value disables the fixup and + * reenables 2:2 (in this case QUIRK_FLAG_FIXED_RATE is required). + */ + if (!chip->setup && iface == 2 && altno == 2) { + usb_audio_info(chip, + "%d:%d: skipping broken altsetting on HP Elite x3 Lap Dock\n", + iface, altno); + return 1; + } + + return 0; +} + int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip, int iface, int altno) { switch (chip->usb_id) { + /* HP Elite x3 Lap Dock: skip broken altsets */ + case USB_ID(0x03f0, 0x0c56): + return hp_elite_x3_lap_dock_skip_setting_quirk(chip, iface, altno); /* quattro usb: skip altsets incompatible with device_setup */ case USB_ID(0x0763, 0x2001): return quattro_skip_setting_quirk(chip, iface, altno); @@ -2289,6 +2325,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x0124, 0x0c21, /* Generic USB Headphone */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x03f0, 0x0c56, /* HP Elite x3 Lap Dock */ + QUIRK_FLAG_FIXED_RATE), DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ From 29863d75b9f0c9df012ae1fac18d72767ef20467 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 8 Sep 2026 07:36:43 +0100 Subject: [PATCH 059/924] ASoC: SDCA: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in class function driver Add SDCA_FUNCTION_TYPE_SIMPLE_JACK to the class function driver's auxiliary id table so a SimpleJack auxdev is bound and enumerated as an ASoC component. Wire set_jack for this function type so the machine driver can register an ASoC jack against it. Reviewed-by: Charles Keepax Reviewed-by: Pierre-Louis Bossart Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260908063644.463819-2-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class_function.c | 5 +++++ sound/soc/sdca/sdca_functions.c | 2 ++ 2 files changed, 7 insertions(+) diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index cc7045dc26e6..1d7fd6603882 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -367,6 +367,7 @@ static int class_function_probe(struct auxiliary_device *auxdev, switch (drv->function->desc->type) { case SDCA_FUNCTION_TYPE_UAJ: case SDCA_FUNCTION_TYPE_RJ: + case SDCA_FUNCTION_TYPE_SIMPLE_JACK: cmp_drv->set_jack = class_function_set_jack; break; default: @@ -552,6 +553,10 @@ static const struct auxiliary_device_id class_function_id_table[] = { .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_RJ_NAME, .driver_data = SDCA_FUNCTION_TYPE_RJ, }, + { + .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_SIMPLE_NAME, + .driver_data = SDCA_FUNCTION_TYPE_SIMPLE_JACK, + }, {}, }; MODULE_DEVICE_TABLE(auxiliary, class_function_id_table); diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index 32d9b7f30a4f..30fa0324648a 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -78,6 +78,8 @@ static const char *get_sdca_function_name(u32 function_type) return SDCA_FUNCTION_TYPE_SPEAKER_MIC_NAME; case SDCA_FUNCTION_TYPE_RJ: return SDCA_FUNCTION_TYPE_RJ_NAME; + case SDCA_FUNCTION_TYPE_SIMPLE_JACK: + return SDCA_FUNCTION_TYPE_SIMPLE_NAME; case SDCA_FUNCTION_TYPE_COMPANION_AMP: return SDCA_FUNCTION_TYPE_COMPANION_AMP_NAME; case SDCA_FUNCTION_TYPE_IMP_DEF: From 1a6a763a6237161598ba69e1e24ff1f28f668cf4 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 8 Sep 2026 07:36:44 +0100 Subject: [PATCH 060/924] ASoC: SDCA: make find_sdca_control_reset() return void find_sdca_control_reset() only ever returns 0 -- it just sets control->has_reset / control->reset for the framework-known (entity type, control selector) mappings. The int return + caller error check in find_sdca_entity_control() are dead code. Convert to void and drop the check. No functional change. Reviewed-by: Charles Keepax Reviewed-by: Pierre-Louis Bossart Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260908063644.463819-3-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_functions.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index 30fa0324648a..b55e3ad8ea34 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -920,8 +920,8 @@ static int find_sdca_control_value(struct device *dev, struct sdca_entity *entit return 0; } -static int find_sdca_control_reset(const struct sdca_entity *entity, - struct sdca_control *control) +static void find_sdca_control_reset(const struct sdca_entity *entity, + struct sdca_control *control) { switch (SDCA_CTL_TYPE(entity->type, control->sel)) { case SDCA_CTL_TYPE_S(FU, AGC): @@ -948,8 +948,6 @@ static int find_sdca_control_reset(const struct sdca_entity *entity, default: break; } - - return 0; } static int find_sdca_entity_control(struct device *dev, struct sdca_entity *entity, @@ -1030,9 +1028,7 @@ static int find_sdca_entity_control(struct device *dev, struct sdca_entity *enti control->is_volatile = find_sdca_control_volatile(entity, control); - ret = find_sdca_control_reset(entity, control); - if (ret) - return ret; + find_sdca_control_reset(entity, control); ret = find_sdca_control_range(dev, control_node, &control->range); if (ret) { From 5ee5987fadf6ae15678a654b4d909225c97f0299 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 12:11:43 +0700 Subject: [PATCH 061/924] ASoC: mediatek: mt8186: Fix APLL mux setting error handling The APLL mux setup functions return immediately when a clock operation fails, without undoing the clock changes already made. This can leave clocks enabled or muxes configured to an unexpected parent. Restore the affected muxes to CLK26M and disable previously enabled clocks on error before propagating the original error. Fixes: 55b423d5623c ("ASoC: mediatek: mt8186: support audio clock control in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903051150.178057-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8186/mt8186-afe-clk.c | 50 ++++++++++++++++------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-clk.c b/sound/soc/mediatek/mt8186/mt8186-afe-clk.c index daaca36a2d08..aa8b2eaf7b95 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-clk.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-clk.c @@ -98,7 +98,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], ret); - return ret; + goto ERR_ENABLE_CLK_TOP_MUX_AUD_1; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], afe_priv->clk[CLK_TOP_APLL1_CK]); @@ -106,7 +106,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_TOP_APLL1_CK], ret); - return ret; + goto ERR_SELECT_CLK_TOP_MUX_AUD_1; } /* 180.6336 / 8 = 22.5792MHz */ @@ -114,7 +114,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], ret); - return ret; + goto ERR_ENABLE_CLK_TOP_MUX_AUD_ENG1; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1], afe_priv->clk[CLK_TOP_APLL1_D8]); @@ -122,7 +122,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_TOP_APLL1_D8], ret); - return ret; + goto ERR_SELECT_CLK_TOP_MUX_AUD_ENG1; } } else { ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1], @@ -131,7 +131,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_CLK26M], ret); - return ret; + goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); @@ -141,12 +141,25 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_CLK26M], ret); - return ret; + goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); } return 0; + +ERR_SELECT_CLK_TOP_MUX_AUD_ENG1: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1], + afe_priv->clk[CLK_CLK26M]); + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); +ERR_ENABLE_CLK_TOP_MUX_AUD_ENG1: +ERR_SELECT_CLK_TOP_MUX_AUD_1: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], + afe_priv->clk[CLK_CLK26M]); + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); +ERR_ENABLE_CLK_TOP_MUX_AUD_1: +EXIT: + return ret; } static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) @@ -159,7 +172,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], ret); - return ret; + goto ERR_ENABLE_CLK_TOP_MUX_AUD_2; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], afe_priv->clk[CLK_TOP_APLL2_CK]); @@ -167,7 +180,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_TOP_APLL2_CK], ret); - return ret; + goto ERR_SELECT_CLK_TOP_MUX_AUD_2; } /* 196.608 / 8 = 24.576MHz */ @@ -175,7 +188,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], ret); - return ret; + goto ERR_ENABLE_CLK_TOP_MUX_AUD_ENG2; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2], afe_priv->clk[CLK_TOP_APLL2_D8]); @@ -183,7 +196,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_TOP_APLL2_D8], ret); - return ret; + goto ERR_SELECT_CLK_TOP_MUX_AUD_ENG2; } } else { ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2], @@ -192,7 +205,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_CLK26M], ret); - return ret; + goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); @@ -202,12 +215,25 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_CLK26M], ret); - return ret; + goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); } return 0; + +ERR_SELECT_CLK_TOP_MUX_AUD_ENG2: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2], + afe_priv->clk[CLK_CLK26M]); + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); +ERR_ENABLE_CLK_TOP_MUX_AUD_ENG2: +ERR_SELECT_CLK_TOP_MUX_AUD_2: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], + afe_priv->clk[CLK_CLK26M]); + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); +ERR_ENABLE_CLK_TOP_MUX_AUD_2: +EXIT: + return ret; } int mt8186_afe_enable_cgs(struct mtk_base_afe *afe) From d70109f5510b423e72894dadfbab27087a7b26b0 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 12:11:44 +0700 Subject: [PATCH 062/924] ASoC: mediatek: mt8186: Fix clock handling in mux disable path In the disable path of the appl*_mux_setting() functions, clk_disable_unprepare() is called after clk_set_parent(). If clk_set_parent() fails, clk_disable_unprepare() is skipped, potentially leaving one of the clocks enabled. Remove the goto exit paths so that all teardown steps are attempted and any errors are only reported. Fixes: 55b423d5623c ("ASoC: mediatek: mt8186: support audio clock control in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903051150.178057-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8186/mt8186-afe-clk.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-clk.c b/sound/soc/mediatek/mt8186/mt8186-afe-clk.c index aa8b2eaf7b95..3edd0dd20eed 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-clk.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-clk.c @@ -131,7 +131,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); @@ -141,7 +140,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); } @@ -158,7 +156,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) afe_priv->clk[CLK_CLK26M]); clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); ERR_ENABLE_CLK_TOP_MUX_AUD_1: -EXIT: return ret; } @@ -205,7 +202,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); @@ -215,7 +211,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); } @@ -232,7 +227,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) afe_priv->clk[CLK_CLK26M]); clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); ERR_ENABLE_CLK_TOP_MUX_AUD_2: -EXIT: return ret; } From eb72060a1966baf9a6ed753186621ee6e39dc889 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 12:11:45 +0700 Subject: [PATCH 063/924] ASoC: mediatek: mt8186: Fix AFE clock error handling The error paths in mt8186_afe_enable_clock() do not consistently undo the clock operations performed before an error. Fix the goto targets and cleanup order so that the audio internal bus parent is restored to CLK26M and all previously enabled clocks are disabled before returning the error. Fixes: 55b423d5623c ("ASoC: mediatek: mt8186: support audio clock control in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903051150.178057-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8186/mt8186-afe-clk.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-clk.c b/sound/soc/mediatek/mt8186/mt8186-afe-clk.c index 3edd0dd20eed..685dc27c7780 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-clk.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-clk.c @@ -288,7 +288,7 @@ int mt8186_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIO], aud_clks[CLK_CLK26M], ret); - goto clk_mux_audio_err; + goto clk_mux_audio_intbus_err; } ret = clk_prepare_enable(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); @@ -315,24 +315,22 @@ int mt8186_afe_enable_clock(struct mtk_base_afe *afe) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_AFE], ret); - goto clk_afe_err; + goto clk_mux_audio_h_parent_err; } return 0; -clk_afe_err: - clk_disable_unprepare(afe_priv->clk[CLK_AFE]); clk_mux_audio_h_parent_err: -clk_mux_audio_intbus_parent_err: mt8186_set_audio_int_bus_parent(afe, CLK_CLK26M); -clk_mux_audio_intbus_err: +clk_mux_audio_intbus_parent_err: clk_disable_unprepare(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); -clk_mux_audio_err: +clk_mux_audio_intbus_err: clk_disable_unprepare(afe_priv->clk[CLK_MUX_AUDIO]); -clk_infra_sys_audio_err: - clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUDIO]); -clk_infra_audio_26m_err: +clk_mux_audio_err: clk_disable_unprepare(afe_priv->clk[CLK_INFRA_AUDIO_26M]); +clk_infra_audio_26m_err: + clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUDIO]); +clk_infra_sys_audio_err: return ret; } From cb552c6b9abe02f254d44d8c29d0dce1ae21f69f Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 12:11:46 +0700 Subject: [PATCH 064/924] ASoC: mediatek: mt8186: Fix APLL enable error handling Currently, the mt8186_apll*_enable() functions call mux_setting(afe, true) but do not check its return value to handle failures. In addition, the cleanup paths of mt8186_apll*_enable() do not call mux_setting(afe, false) when the enable operation fails, while the mt8186_apll*_disable() functions do. Add error handling for apll*_mux_setting() and call mux_setting(afe, false) in the cleanup paths when mt8186_apll*_enable() fails. Fixes: 55b423d5623c ("ASoC: mediatek: mt8186: support audio clock control in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903051150.178057-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8186/mt8186-afe-clk.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-clk.c b/sound/soc/mediatek/mt8186/mt8186-afe-clk.c index 685dc27c7780..77a0cbc8ee24 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-clk.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-clk.c @@ -353,7 +353,9 @@ int mt8186_apll1_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll1_mux_setting(afe, true); + ret = apll1_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL22M]); if (ret) { @@ -378,9 +380,9 @@ int mt8186_apll1_enable(struct mtk_base_afe *afe) return 0; err_clk_apll1_tuner: - clk_disable_unprepare(afe_priv->clk[CLK_APLL1_TUNER]); -err_clk_apll22m: clk_disable_unprepare(afe_priv->clk[CLK_APLL22M]); +err_clk_apll22m: + apll1_mux_setting(afe, false); return ret; } @@ -406,7 +408,9 @@ int mt8186_apll2_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll2_mux_setting(afe, true); + ret = apll2_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL24M]); if (ret) { @@ -431,9 +435,9 @@ int mt8186_apll2_enable(struct mtk_base_afe *afe) return 0; err_clk_apll2_tuner: - clk_disable_unprepare(afe_priv->clk[CLK_APLL2_TUNER]); -err_clk_apll24m: clk_disable_unprepare(afe_priv->clk[CLK_APLL24M]); +err_clk_apll24m: + apll2_mux_setting(afe, false); return ret; } From 996469c1092e8fa81fac98d819174ce56baff821 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 12:11:47 +0700 Subject: [PATCH 065/924] ASoC: mediatek: mt8186: Fix MCK error handling The mt8186_mck_enable() function returns immediately when a clock operation fails, leaving previously enabled clocks active. Add error paths to disable previously enabled clocks before returning the error. Fixes: 55b423d5623c ("ASoC: mediatek: mt8186: support audio clock control in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903051150.178057-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8186/mt8186-afe-clk.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-clk.c b/sound/soc/mediatek/mt8186/mt8186-afe-clk.c index 77a0cbc8ee24..62b008b7caca 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-clk.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-clk.c @@ -520,7 +520,7 @@ int mt8186_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[m_sel_id], ret); - return ret; + goto ERR_ENABLE_MCLK; } ret = clk_set_parent(afe_priv->clk[m_sel_id], afe_priv->clk[apll_clk_id]); @@ -528,7 +528,7 @@ int mt8186_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) dev_err(afe->dev, "%s(), clk_set_parent %s-%s fail %d\n", __func__, aud_clks[m_sel_id], aud_clks[apll_clk_id], ret); - return ret; + goto ERR_SELECT_MCLK; } } @@ -537,16 +537,25 @@ int mt8186_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[div_clk_id], ret); - return ret; + goto ERR_ENABLE_MCLK_DIV; } ret = clk_set_rate(afe_priv->clk[div_clk_id], rate); if (ret) { dev_err(afe->dev, "%s(), clk_set_rate %s, rate %d, fail %d\n", __func__, aud_clks[div_clk_id], rate, ret); - return ret; + goto ERR_SET_MCLK_RATE; } return 0; + +ERR_SET_MCLK_RATE: + clk_disable_unprepare(afe_priv->clk[div_clk_id]); +ERR_ENABLE_MCLK_DIV: +ERR_SELECT_MCLK: + if (m_sel_id >= 0) + clk_disable_unprepare(afe_priv->clk[m_sel_id]); +ERR_ENABLE_MCLK: + return ret; } void mt8186_mck_disable(struct mtk_base_afe *afe, int mck_id) From d87989ec1c2a65646bcf6c8aaea46836b93e12ea Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 12:11:48 +0700 Subject: [PATCH 066/924] ASoC: mediatek: mt8186: Fix gpio rollback on adda dl/ul partial failure If the second mt8186_afe_gpio_select() call in adda_dl()/adda_ul() fails, the first one's state is left applied, leaving CLK/DAT in an inconsistent state. Roll back the first select on failure. Also fix a copy-pasted "CLK ON" debug message that should say "CLK OFF" in the _dl disable path. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903051150.178057-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8186/mt8186-afe-gpio.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-gpio.c b/sound/soc/mediatek/mt8186/mt8186-afe-gpio.c index aced8e7e920c..1cff2a63ef37 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-gpio.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-gpio.c @@ -143,6 +143,7 @@ static int mt8186_afe_gpio_adda_dl(struct device *dev, bool enable) ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_DAT_MOSI_ON); if (ret) { dev_dbg(dev, "%s(), MOSI DAT ON select fail!\n", __func__); + mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_CLK_MOSI_OFF); return ret; } } else { @@ -154,7 +155,8 @@ static int mt8186_afe_gpio_adda_dl(struct device *dev, bool enable) ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_CLK_MOSI_OFF); if (ret) { - dev_dbg(dev, "%s(), MOSI CLK ON select fail!\n", __func__); + dev_dbg(dev, "%s(), MOSI CLK OFF select fail!\n", __func__); + mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_DAT_MOSI_ON); return ret; } } @@ -176,6 +178,7 @@ static int mt8186_afe_gpio_adda_ul(struct device *dev, bool enable) ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_DAT_MISO_ON); if (ret) { dev_dbg(dev, "%s(), MISO DAT ON select fail!\n", __func__); + mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_CLK_MISO_OFF); return ret; } } else { @@ -188,6 +191,7 @@ static int mt8186_afe_gpio_adda_ul(struct device *dev, bool enable) ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_CLK_MISO_OFF); if (ret) { dev_dbg(dev, "%s(), MISO CLK OFF select fail!\n", __func__); + mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_DAT_MISO_ON); return ret; } } From 0830625cacf94ae66f68b4e9d2e926a152a90dca Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 12:11:49 +0700 Subject: [PATCH 067/924] ASoC: mediatek: mt8186: Handle regcache sync failure Check the return value of regcache_sync() and restore cache-only mode if synchronization fails. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903051150.178057-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8186/mt8186-afe-pcm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c b/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c index 44a521c3a610..143386c54dd7 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c @@ -2743,7 +2743,11 @@ static int mt8186_afe_runtime_resume(struct device *dev) goto skip_regmap; regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) { + regcache_cache_only(afe->regmap, true); + return ret; + } /* enable audio sys DCM for power saving */ regmap_update_bits(afe_priv->infracfg, PERI_BUS_DCM_CTRL, BIT(29), BIT(29)); From 5ca0b1c5e32a85ef3ab7fd2b9174a54f6678d405 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 12:11:50 +0700 Subject: [PATCH 068/924] ASoC: mediatek: mt8186: Drop redundant probe error messages The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() calls are redundant and can be removed. platform_get_irq() returns a negative value on error and does not return zero. Therefore, only check for a negative error code and return it directly. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903051150.178057-9-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8186/mt8186-afe-pcm.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c b/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c index 143386c54dd7..4bf098657fb4 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c @@ -2852,10 +2852,8 @@ static int mt8186_afe_pcm_dev_probe(struct platform_device *pdev) /* init audio related clock */ ret = mt8186_init_clock(afe); - if (ret) { - dev_err(dev, "init clock error, ret %d\n", ret); + if (ret) return ret; - } /* init memif */ afe->memif_32bit_supported = 0; @@ -2885,15 +2883,14 @@ static int mt8186_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); - if (irq_id <= 0) - return dev_err_probe(dev, irq_id < 0 ? irq_id : -ENXIO, - "no irq found"); + if (irq_id < 0) + return irq_id; ret = devm_request_irq(dev, irq_id, mt8186_afe_irq_handler, IRQF_TRIGGER_NONE, "Afe_ISR_Handle", (void *)afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n"); + return ret; ret = enable_irq_wake(irq_id); if (ret < 0) @@ -2957,10 +2954,8 @@ static int mt8186_afe_pcm_dev_probe(struct platform_device *pdev) &mt8186_afe_component, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_err(dev, "err_dai_component\n"); + if (ret) goto err_pm_disable; - } ret = pm_runtime_put_sync(dev); if (ret) { From e967d479b65c778cd2394ab185e289beefcf996d Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 21 Aug 2026 19:28:38 +0700 Subject: [PATCH 069/924] ASoC: mediatek: mt6797: fix wrong unwind order and error code in enable_clock The error paths in mt6797_afe_enable_clock() use incorrect goto labels, causing clocks that failed to enable to be disabled during cleanup. Fix the goto labels to only unwind clocks that were successfully enabled, and return the actual error code. Fixes: c5e7fca928d3 ("ASoC: mt6797: add structure define and clock control function for 6797") Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260821122840.41266-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt6797/mt6797-afe-clk.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sound/soc/mediatek/mt6797/mt6797-afe-clk.c b/sound/soc/mediatek/mt6797/mt6797-afe-clk.c index 6f3e6acfcfab..0ac04d73b777 100644 --- a/sound/soc/mediatek/mt6797/mt6797-afe-clk.c +++ b/sound/soc/mediatek/mt6797/mt6797-afe-clk.c @@ -63,21 +63,21 @@ int mt6797_afe_enable_clock(struct mtk_base_afe *afe) if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_INFRA_SYS_AUD], ret); - goto CLK_INFRA_SYS_AUDIO_ERR; + return ret; } ret = clk_prepare_enable(afe_priv->clk[CLK_INFRA_SYS_AUD_26M]); if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_INFRA_SYS_AUD_26M], ret); - goto CLK_INFRA_SYS_AUD_26M_ERR; + goto CLK_INFRA_SYS_AUDIO_ERR; } ret = clk_prepare_enable(afe_priv->clk[CLK_TOP_MUX_AUD]); if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD], ret); - goto CLK_MUX_AUDIO_ERR; + goto CLK_INFRA_SYS_AUD_26M_ERR; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD], @@ -93,13 +93,11 @@ int mt6797_afe_enable_clock(struct mtk_base_afe *afe) if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_BUS], ret); - goto CLK_MUX_AUDIO_INTBUS_ERR; + goto CLK_MUX_AUDIO_ERR; } - return ret; + return 0; -CLK_MUX_AUDIO_INTBUS_ERR: - clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_BUS]); CLK_MUX_AUDIO_ERR: clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD]); CLK_INFRA_SYS_AUD_26M_ERR: @@ -107,7 +105,7 @@ int mt6797_afe_enable_clock(struct mtk_base_afe *afe) CLK_INFRA_SYS_AUDIO_ERR: clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUD]); - return 0; + return ret; } int mt6797_afe_disable_clock(struct mtk_base_afe *afe) From 2e4b0d5f52dac992fa2bfc8a8ec5617527288769 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 21 Aug 2026 19:28:39 +0700 Subject: [PATCH 070/924] ASoC: mediatek: mt6797: Use dev_err_probe() for error handling Replace dev_err() with dev_err_probe() to prevent log spam when probe returns -EPROBE_DEFER. Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260821122840.41266-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt6797/mt6797-afe-clk.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt6797/mt6797-afe-clk.c b/sound/soc/mediatek/mt6797/mt6797-afe-clk.c index 0ac04d73b777..2326496f8b27 100644 --- a/sound/soc/mediatek/mt6797/mt6797-afe-clk.c +++ b/sound/soc/mediatek/mt6797/mt6797-afe-clk.c @@ -43,12 +43,9 @@ int mt6797_init_clock(struct mtk_base_afe *afe) for (i = 0; i < CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "%s(), devm_clk_get %s fail, ret %ld\n", - __func__, aud_clks[i], - PTR_ERR(afe_priv->clk[i])); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } return 0; From 1e5e1344b36e1acc62bdbca192e6ae317909725f Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 21 Aug 2026 19:28:40 +0700 Subject: [PATCH 071/924] ASoC: mediatek: mt6797: Drop redundant probe error messages The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() calls are redundant and can be removed. Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260821122840.41266-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt6797/mt6797-afe-pcm.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c b/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c index f62a32f2f2b6..dfe67727fe47 100644 --- a/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c +++ b/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c @@ -754,10 +754,8 @@ static int mt6797_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt6797_init_clock(afe); - if (ret) { - dev_err(dev, "init clock error\n"); + if (ret) return ret; - } /* regmap init */ afe->base_addr = devm_platform_ioremap_resource(pdev, 0); @@ -800,10 +798,8 @@ static int mt6797_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt6797_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); - if (ret) { - dev_err(dev, "could not request_irq for asys-isr\n"); + if (ret) return ret; - } /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); From 233d36a96009682208cc4ca1139b9e5641a8bf09 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 8 Sep 2026 09:28:01 +0100 Subject: [PATCH 072/924] ASoC: codecs: lpass-wsa-macro: use v2.5 Compander1 addresses on v2.5+ wsa_macro_set_spkr_mode() unconditionally writes CDC_WSA_COMPANDER1_CTL3 (0x5CC) and CDC_WSA_COMPANDER1_CTL7 (0x5DC). On v2.5+ silicon these registers moved to CDC_2_5_WSA_COMPANDER1_CTL3 (0x5EC) and CDC_2_5_WSA_COMPANDER1_CTL7 (0x5FC); the v2.1 addresses are dead on that hardware, so the update_bits() calls take a bus error: wsa_macro 6c90000.codec: ASoC error (-5): at snd_soc_component_update_bits() on 6c90000.codec for register: [0x000005dc] The flat regcache had been hiding the read side of this by returning the zero-initialised cache slot instead of doing a bus read, so the error only becomes visible once the cache reports the miss honestly and regmap falls back to a real bus read. Select the correct address for Compander1 CTL3/CTL7 based on codec_version. The Compander0 and Boost registers used in the same function have the same address across versions, so they stay unchanged. Also collapse the two mode branches to a single sequence with mode-selected values, which is what the switch was doing anyway. Fixes: 727de4fbc546 ("ASoC: codecs: lpass-wsa-macro: Correct support for newer v2.5 version") Cc: stable@vger.kernel.org Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260908082806.648797-2-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/lpass-wsa-macro.c | 42 ++++++++++++++++++------------ 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c index cfd2ac0a6cda..7a2b0cf9398a 100644 --- a/sound/soc/codecs/lpass-wsa-macro.c +++ b/sound/soc/codecs/lpass-wsa-macro.c @@ -1129,27 +1129,35 @@ static const struct regmap_config wsa_regmap_config = { int wsa_macro_set_spkr_mode(struct snd_soc_component *component, int mode) { struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + unsigned int comp1_ctl3, comp1_ctl7; + u8 comp_val, boost_val; wsa->spkr_mode = mode; - switch (mode) { - case WSA_MACRO_SPKR_MODE_1: - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL3, 0x80, 0x00); - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER1_CTL3, 0x80, 0x00); - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL7, 0x01, 0x00); - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER1_CTL7, 0x01, 0x00); - snd_soc_component_update_bits(component, CDC_WSA_BOOST0_BOOST_CTL, 0x7C, 0x44); - snd_soc_component_update_bits(component, CDC_WSA_BOOST1_BOOST_CTL, 0x7C, 0x44); - break; - default: - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL3, 0x80, 0x80); - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER1_CTL3, 0x80, 0x80); - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL7, 0x01, 0x01); - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER1_CTL7, 0x01, 0x01); - snd_soc_component_update_bits(component, CDC_WSA_BOOST0_BOOST_CTL, 0x7C, 0x58); - snd_soc_component_update_bits(component, CDC_WSA_BOOST1_BOOST_CTL, 0x7C, 0x58); - break; + /* Compander1 CTL3/CTL7 moved on v2.5+ silicon. */ + if (wsa->codec_version >= LPASS_CODEC_VERSION_2_5) { + comp1_ctl3 = CDC_2_5_WSA_COMPANDER1_CTL3; + comp1_ctl7 = CDC_2_5_WSA_COMPANDER1_CTL7; + } else { + comp1_ctl3 = CDC_WSA_COMPANDER1_CTL3; + comp1_ctl7 = CDC_WSA_COMPANDER1_CTL7; } + + if (mode == WSA_MACRO_SPKR_MODE_1) { + comp_val = 0x00; + boost_val = 0x44; + } else { + comp_val = 0x80; + boost_val = 0x58; + } + + snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL3, 0x80, comp_val); + snd_soc_component_update_bits(component, comp1_ctl3, 0x80, comp_val); + snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL7, 0x01, comp_val ? 0x01 : 0x00); + snd_soc_component_update_bits(component, comp1_ctl7, 0x01, comp_val ? 0x01 : 0x00); + snd_soc_component_update_bits(component, CDC_WSA_BOOST0_BOOST_CTL, 0x7C, boost_val); + snd_soc_component_update_bits(component, CDC_WSA_BOOST1_BOOST_CTL, 0x7C, boost_val); + return 0; } EXPORT_SYMBOL(wsa_macro_set_spkr_mode); From 246c785491bb265b9716a512057cee8f1a5a3280 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 8 Sep 2026 09:28:02 +0100 Subject: [PATCH 073/924] ASoC: codecs: lpass-{rx,wsa}-macro: sort reg_defaults before regmap init Both drivers build the reg_defaults array by concatenating a base table with a codec-version-specific one; the resulting array is not in register-address order because the version-specific ranges overlap the base range. Regmap detects this and prints wsa_macro 6c90000.codec: Driver needs fixing: Unsorted reg_defaults, sorting the copy rx_macro 6a70000.codec: Driver needs fixing: Unsorted reg_defaults, sorting the copy then sorts its own copy at runtime. Call regcache_sort_defaults() on the concatenated array before handing it to regmap so the warning goes away and regmap does not have to sort a second time. Keeping the base/version-specific split is deliberate for readability, so a static reorder is not appropriate here. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260908082806.648797-3-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/lpass-rx-macro.c | 2 ++ sound/soc/codecs/lpass-wsa-macro.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/sound/soc/codecs/lpass-rx-macro.c b/sound/soc/codecs/lpass-rx-macro.c index 282a6aaa9986..45d8d51e55ad 100644 --- a/sound/soc/codecs/lpass-rx-macro.c +++ b/sound/soc/codecs/lpass-rx-macro.c @@ -3852,6 +3852,8 @@ static int rx_macro_probe(struct platform_device *pdev) return -EINVAL; } + regcache_sort_defaults(reg_defaults, def_count); + struct regmap_config *reg_config __free(kfree) = kmemdup(&rx_regmap_config, sizeof(*reg_config), GFP_KERNEL); diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c index 7a2b0cf9398a..4d7e7e30f460 100644 --- a/sound/soc/codecs/lpass-wsa-macro.c +++ b/sound/soc/codecs/lpass-wsa-macro.c @@ -2763,6 +2763,8 @@ static int wsa_macro_probe(struct platform_device *pdev) return -EINVAL; } + regcache_sort_defaults(reg_defaults, def_count); + struct regmap_config *reg_config __free(kfree) = kmemdup(&wsa_regmap_config, sizeof(*reg_config), GFP_KERNEL); From ce7164c89b5c9c9afe12b97ee3fae93250fdf814 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 8 Sep 2026 09:28:03 +0100 Subject: [PATCH 074/924] ASoC: codecs: lpass-wsa-macro: switch cache to REGCACHE_MAPLE REGCACHE_FLAT hides missing-default reads by returning the zero- initialised cache slot instead of doing a bus read. The caller can't tell that value apart from a real hardware value, so any un-defaulted, readable, non-volatile register that gets read silently returns wrong data. Since commit e062bdfdd6ad ("regmap: warn users about uninitialized flat cache") regmap dev_warn_once()s the first such miss, but any subsequent miss stays silent. Switch to REGCACHE_MAPLE. A cache miss returns -ENOENT and the regmap core falls back to a real bus read, so the caller always gets the true hardware value. Write-cache behaviour is unchanged. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260908082806.648797-4-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/lpass-wsa-macro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c index 4d7e7e30f460..d254518634cf 100644 --- a/sound/soc/codecs/lpass-wsa-macro.c +++ b/sound/soc/codecs/lpass-wsa-macro.c @@ -1109,7 +1109,7 @@ static const struct regmap_config wsa_regmap_config = { .reg_bits = 16, .val_bits = 32, /* 8 but with 32 bit read/write */ .reg_stride = 4, - .cache_type = REGCACHE_FLAT, + .cache_type = REGCACHE_MAPLE, /* .reg_defaults and .num_reg_defaults set in probe() */ .max_register = WSA_MAX_OFFSET, .writeable_reg = wsa_is_writeable_register, From 384bd7cf40530b32e838a38977902de6bc7f4d19 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 8 Sep 2026 09:28:04 +0100 Subject: [PATCH 075/924] ASoC: codecs: lpass-rx-macro: switch cache to REGCACHE_MAPLE REGCACHE_FLAT hides missing-default reads by returning the zero- initialised cache slot instead of doing a bus read. The caller can't tell that value apart from a real hardware value, so any un-defaulted, readable, non-volatile register that gets read silently returns wrong data. Since commit e062bdfdd6ad ("regmap: warn users about uninitialized flat cache") regmap dev_warn_once()s the first such miss, but any subsequent miss stays silent. Switch to REGCACHE_MAPLE. A cache miss returns -ENOENT and the regmap core falls back to a real bus read, so the caller always gets the true hardware value. Write-cache behaviour is unchanged. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260908082806.648797-5-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/lpass-rx-macro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/lpass-rx-macro.c b/sound/soc/codecs/lpass-rx-macro.c index 45d8d51e55ad..30c7a0a70aeb 100644 --- a/sound/soc/codecs/lpass-rx-macro.c +++ b/sound/soc/codecs/lpass-rx-macro.c @@ -1673,7 +1673,7 @@ static const struct regmap_config rx_regmap_config = { .reg_bits = 16, .val_bits = 32, /* 8 but with 32 bit read/write */ .reg_stride = 4, - .cache_type = REGCACHE_FLAT, + .cache_type = REGCACHE_MAPLE, .max_register = RX_MAX_OFFSET, .writeable_reg = rx_is_writeable_register, .volatile_reg = rx_is_volatile_register, From 8ed9306885eb7e242ad0f859e1fbc2d822d3d0eb Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 8 Sep 2026 09:28:05 +0100 Subject: [PATCH 076/924] ASoC: codecs: lpass-tx-macro: switch cache to REGCACHE_MAPLE REGCACHE_FLAT hides missing-default reads by returning the zero- initialised cache slot instead of doing a bus read. The caller can't tell that value apart from a real hardware value, so any un-defaulted, readable, non-volatile register that gets read silently returns wrong data. Since commit e062bdfdd6ad ("regmap: warn users about uninitialized flat cache") regmap dev_warn_once()s the first such miss, but any subsequent miss stays silent. Switch to REGCACHE_MAPLE. A cache miss returns -ENOENT and the regmap core falls back to a real bus read, so the caller always gets the true hardware value. Write-cache behaviour is unchanged. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260908082806.648797-6-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/lpass-tx-macro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/lpass-tx-macro.c b/sound/soc/codecs/lpass-tx-macro.c index a8e83eb60ba2..6f362b196f47 100644 --- a/sound/soc/codecs/lpass-tx-macro.c +++ b/sound/soc/codecs/lpass-tx-macro.c @@ -602,7 +602,7 @@ static const struct regmap_config tx_regmap_config = { .reg_bits = 16, .val_bits = 32, .reg_stride = 4, - .cache_type = REGCACHE_FLAT, + .cache_type = REGCACHE_MAPLE, .max_register = TX_MAX_OFFSET, .reg_defaults = tx_defaults, .num_reg_defaults = ARRAY_SIZE(tx_defaults), From 322a9e0850b41a628b59c29be89f499acba9f1c8 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 8 Sep 2026 09:28:06 +0100 Subject: [PATCH 077/924] ASoC: codecs: lpass-va-macro: switch cache to REGCACHE_MAPLE REGCACHE_FLAT hides missing-default reads by returning the zero- initialised cache slot instead of doing a bus read. The caller can't tell that value apart from a real hardware value, so any un-defaulted, readable, non-volatile register that gets read silently returns wrong data. Since commit e062bdfdd6ad ("regmap: warn users about uninitialized flat cache") regmap dev_warn_once()s the first such miss, but any subsequent miss stays silent. Switch to REGCACHE_MAPLE. A cache miss returns -ENOENT and the regmap core falls back to a real bus read, so the caller always gets the true hardware value. Write-cache behaviour is unchanged. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260908082806.648797-7-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/lpass-va-macro.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c index dbc5795b9273..f0797715ab54 100644 --- a/sound/soc/codecs/lpass-va-macro.c +++ b/sound/soc/codecs/lpass-va-macro.c @@ -454,7 +454,7 @@ static const struct regmap_config va_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, - .cache_type = REGCACHE_FLAT, + .cache_type = REGCACHE_MAPLE, .reg_defaults = va_defaults, .num_reg_defaults = ARRAY_SIZE(va_defaults), .max_register = VA_MAX_OFFSET, From af0f4f7a042e39bf70fa7406351b26efead18dff Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 21:44:28 +0800 Subject: [PATCH 078/924] ASoC: codecs: sma1307: validate setting firmware layout sma1307_setting_loaded() checks a byte count against an element count, then reads an eight-int header and a fixed default table from the firmware. It also trusts the mode count while indexing a five-entry mode_set array. Validate the byte-to-int conversion, fixed header/default extent, mode count, and exact mode table layout before parsing the setting file. Fixes: 576c57e6b4c1 ("ASoC: sma1307: Add driver for Iron Device SMA1307") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830134428.9550-1-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/sma1307.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/sma1307.c b/sound/soc/codecs/sma1307.c index adb369a29b9d..653cbafee23e 100644 --- a/sound/soc/codecs/sma1307.c +++ b/sound/soc/codecs/sma1307.c @@ -1701,7 +1701,9 @@ static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *fil __func__, setting_file, ERR_PTR(ret)); sma1307->set.status = false; return; - } else if ((fw->size) < SMA1307_SETTING_HEADER_SIZE) { + } else if (fw->size % sizeof(int) || + fw->size < (SMA1307_SETTING_HEADER_SIZE + + SMA1307_SETTING_DEFAULT_SIZE) * sizeof(int)) { dev_err(sma1307->dev, "%s: Invalid file\n", __func__); sma1307->set.status = false; return; @@ -1720,6 +1722,10 @@ static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *fil sma1307->set.checksum = data[sma1307->set.header_size - 2]; sma1307->set.num_mode = data[sma1307->set.header_size - 1]; num_mode = sma1307->set.num_mode; + if (num_mode < 0 || num_mode > ARRAY_SIZE(sma1307->set.mode_set)) { + sma1307->set.status = false; + return; + } sma1307->set.header = devm_kmalloc_array(sma1307->dev, sma1307->set.header_size, sizeof(int), @@ -1755,7 +1761,11 @@ static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *fil /* MODE */ offset = sma1307->set.header_size + sma1307->set.def_size; - sma1307->set.mode_size = DIV_ROUND_CLOSEST(size - offset, num_mode + 1); + if ((size - offset) % (num_mode + 1)) { + sma1307->set.status = false; + return; + } + sma1307->set.mode_size = (size - offset) / (num_mode + 1); for (int i = 0; i < num_mode; i++) { sma1307->set.mode_set[i] = devm_kzalloc(sma1307->dev, From 3df6761104162d3428aacbc23daf6243cdf7bdd5 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 24 Aug 2026 13:30:24 -0700 Subject: [PATCH 079/924] ASoC: xilinx: formatter_pcm: use more devm in probe Use devm_clk_get_enabled() to let the clock be enabled automatically and cleaned up on unbind, dropping the manual clk_prepare_enable() and clk_disable_unprepare() calls in probe and remove. As a result, move the clk struct member into probe, which is the only place it's used. Simplify the probe error paths by returning directly instead of jumping to clk_err, and use devm_snd_soc_register_component() return value directly. Also remove error message from ioremap as it throws one itself. Same with devm_request_irq(). Assisted-by: opencode:deepseek-v4-flash-free Signed-off-by: Rosen Penev Reviewed-by: Michal Simek Reviewed-by: Vincenzo Frascino Link: https://patch.msgid.link/20260824203024.15574-1-rosenp@gmail.com Signed-off-by: Mark Brown --- sound/soc/xilinx/xlnx_formatter_pcm.c | 79 ++++++++------------------- 1 file changed, 22 insertions(+), 57 deletions(-) diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c index 95312b4ddc5b..304220d9aaf5 100644 --- a/sound/soc/xilinx/xlnx_formatter_pcm.c +++ b/sound/soc/xilinx/xlnx_formatter_pcm.c @@ -81,7 +81,6 @@ struct xlnx_pcm_drv_data { int mm2s_irq; struct snd_pcm_substream *play_stream; struct snd_pcm_substream *capture_stream; - struct clk *axi_clk; unsigned int sysclk; }; @@ -595,100 +594,68 @@ static int xlnx_formatter_pcm_probe(struct platform_device *pdev) u32 val; struct xlnx_pcm_drv_data *aud_drv_data; struct device *dev = &pdev->dev; + struct clk *axi_clk; + + axi_clk = devm_clk_get_enabled(dev, "s_axi_lite_aclk"); + if (IS_ERR(axi_clk)) + return dev_err_probe(dev, PTR_ERR(axi_clk), "failed to get s_axi_lite_aclk"); aud_drv_data = devm_kzalloc(dev, sizeof(*aud_drv_data), GFP_KERNEL); if (!aud_drv_data) return -ENOMEM; - aud_drv_data->axi_clk = devm_clk_get(dev, "s_axi_lite_aclk"); - if (IS_ERR(aud_drv_data->axi_clk)) { - ret = PTR_ERR(aud_drv_data->axi_clk); - dev_err(dev, "failed to get s_axi_lite_aclk(%d)\n", ret); - return ret; - } - ret = clk_prepare_enable(aud_drv_data->axi_clk); - if (ret) { - dev_err(dev, - "failed to enable s_axi_lite_aclk(%d)\n", ret); - return ret; - } - aud_drv_data->mmio = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(aud_drv_data->mmio)) { - dev_err(dev, "audio formatter ioremap failed\n"); - ret = PTR_ERR(aud_drv_data->mmio); - goto clk_err; - } + if (IS_ERR(aud_drv_data->mmio)) + return PTR_ERR(aud_drv_data->mmio); val = readl(aud_drv_data->mmio + XLNX_AUD_CORE_CONFIG); if (val & AUD_CFG_MM2S_MASK) { aud_drv_data->mm2s_presence = true; ret = xlnx_formatter_pcm_reset(aud_drv_data->mmio + XLNX_MM2S_OFFSET); - if (ret) { - dev_err(dev, "audio formatter reset failed\n"); - goto clk_err; - } + if (ret) + return dev_err_probe(dev, ret, "audio formatter reset failed\n"); xlnx_formatter_disable_irqs(aud_drv_data->mmio + XLNX_MM2S_OFFSET, SNDRV_PCM_STREAM_PLAYBACK); aud_drv_data->mm2s_irq = platform_get_irq_byname(pdev, "irq_mm2s"); - if (aud_drv_data->mm2s_irq < 0) { - ret = aud_drv_data->mm2s_irq; - goto clk_err; - } + if (aud_drv_data->mm2s_irq < 0) + return aud_drv_data->mm2s_irq; + ret = devm_request_irq(dev, aud_drv_data->mm2s_irq, xlnx_mm2s_irq_handler, 0, "xlnx_formatter_pcm_mm2s_irq", aud_drv_data); - if (ret) { - dev_err(dev, "xlnx audio mm2s irq request failed\n"); - goto clk_err; - } + if (ret) + return ret; } if (val & AUD_CFG_S2MM_MASK) { aud_drv_data->s2mm_presence = true; ret = xlnx_formatter_pcm_reset(aud_drv_data->mmio + XLNX_S2MM_OFFSET); - if (ret) { - dev_err(dev, "audio formatter reset failed\n"); - goto clk_err; - } + if (ret) + return dev_err_probe(dev, ret, "audio formatter reset failed\n"); xlnx_formatter_disable_irqs(aud_drv_data->mmio + XLNX_S2MM_OFFSET, SNDRV_PCM_STREAM_CAPTURE); aud_drv_data->s2mm_irq = platform_get_irq_byname(pdev, "irq_s2mm"); - if (aud_drv_data->s2mm_irq < 0) { - ret = aud_drv_data->s2mm_irq; - goto clk_err; - } + if (aud_drv_data->s2mm_irq < 0) + return aud_drv_data->s2mm_irq; + ret = devm_request_irq(dev, aud_drv_data->s2mm_irq, xlnx_s2mm_irq_handler, 0, "xlnx_formatter_pcm_s2mm_irq", aud_drv_data); - if (ret) { - dev_err(dev, "xlnx audio s2mm irq request failed\n"); - goto clk_err; - } + if (ret) + return ret; } dev_set_drvdata(dev, aud_drv_data); - ret = devm_snd_soc_register_component(dev, &xlnx_asoc_component, - NULL, 0); - if (ret) { - dev_err(dev, "pcm platform device register failed\n"); - goto clk_err; - } - - return 0; - -clk_err: - clk_disable_unprepare(aud_drv_data->axi_clk); - return ret; + return devm_snd_soc_register_component(dev, &xlnx_asoc_component, NULL, 0); } static void xlnx_formatter_pcm_remove(struct platform_device *pdev) @@ -705,8 +672,6 @@ static void xlnx_formatter_pcm_remove(struct platform_device *pdev) if (ret) dev_err(&pdev->dev, "audio formatter reset failed\n"); - - clk_disable_unprepare(adata->axi_clk); } static const struct of_device_id xlnx_formatter_pcm_of_match[] = { From 3e56757584d631fb954332f55698bbb55ee93dc9 Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Sat, 15 Aug 2026 14:40:00 +0300 Subject: [PATCH 080/924] ASoC: tas2783-sdw: make multi-byte MBQ registers reachable tas2783_sdca_mbq_size() declares registers of one, two and four bytes, but the regmap is created with val_bits = 8. The SoundWire MBQ bus rejects any register whose declared size exceeds the configured value width: ctx->val_size = config->val_bits / BITS_PER_BYTE; ... size = ctx->cfg.mbq_size(ctx->dev, reg); if (!size || size > ctx->val_size) return -EINVAL; Both regmap_sdw_mbq_read() and regmap_sdw_mbq_write() bail out on that, so every register the driver itself declares as two or four bytes wide is unreachable through the regmap, in both directions, and the failure happens before anything reaches the bus. That is measurable. On an ASUS ProArt PX13 HN7306EAC with two TAS2783 aggregated on one link, a regmap read of XU22 Control 0x07, declared two bytes, and of XU22 Control 0x12, declared four bytes, returns -EINVAL on both amplifiers. With val_bits widened the same reads reach the peripheral, which answers -ENODATA for both, powered or not - this part appears not to implement them. So the change makes the registers addressable; whether a given peripheral answers is up to the peripheral. Widen val_bits to cover the largest size the callback returns. The callback keeps deciding the size of each individual transfer, so single-byte registers are unaffected. The calibration path needs adjusting along with it. regmap_bulk_write(..., buf, sizeof(u32)) relied on val_bits = 8 to spread one calibration value over four consecutive single-byte registers; with a four-byte value width the same call would ask for four four-byte values and read past the end of the four-byte buffer. Write the four registers explicitly instead, keeping the most-significant-byte -first layout. Read back over the bus, bypassing the cache, the five calibration values of both amplifiers are byte for byte what they were before this patch. What this does not do is repair regcache_sync(). On this machine a sync aborts earlier, at a single-byte Control - the FU23 Mute of channel 0, which tas2783_reg_default[] gives 0x1 while the driver's init sequence writes 0x00, so the sync tries to restore it and the peripheral answers -ENODATA - and no multi-byte register is reached at all. Signed-off-by: Andrey Golovko Link: https://patch.msgid.link/20260815112000.4180-2-andrey.golovko@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2783-sdw.c | 37 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index eaebb0ebbe0d..de04941bc6de 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -564,7 +564,13 @@ static bool tas2783_volatile_register(struct device *dev, u32 reg) static const struct regmap_config tas_regmap = { .reg_bits = 32, - .val_bits = 8, + /* + * tas2783_sdca_mbq_size() declares registers of one, two and four + * bytes. val_bits has to cover the widest of those: the SoundWire MBQ + * bus rejects any register whose declared size exceeds val_bits, which + * would make every multi-byte register unreachable in both directions. + */ + .val_bits = 32, .readable_reg = tas2783_readable_register, .writeable_reg = tas2783_writeable_register, .volatile_reg = tas2783_volatile_register, @@ -667,9 +673,9 @@ static s32 tas2783_validate_calibdata(struct tas2783_prv *tas_dev, static void tas2783_set_calib_params_to_device(struct tas2783_prv *tas_dev, u32 *cali_data) { - u32 dev_count, offset, i, device_num; + u32 dev_count, offset, i, j, device_num; u32 reg_value; - u8 buf[4]; + s32 ret; dev_count = cali_data[1]; offset = 3; @@ -683,12 +689,25 @@ static void tas2783_set_calib_params_to_device(struct tas2783_prv *tas_dev, u32 for (i = 0; i < ARRAY_SIZE(tas2783_cali_reg); i++) { reg_value = cali_data[offset + i]; - buf[0] = reg_value >> 24; - buf[1] = reg_value >> 16; - buf[2] = reg_value >> 8; - buf[3] = reg_value & 0xff; - regmap_bulk_write(tas_dev->regmap, tas2783_cali_reg[i], - buf, sizeof(u32)); + + /* + * A calibration value occupies four consecutive + * single-byte registers, most significant byte first. + * Write them one by one: these registers are declared + * as one byte wide by tas2783_sdca_mbq_size(), so a + * multi-byte regmap access would not match the layout. + */ + for (j = 0; j < sizeof(u32); j++) { + ret = regmap_write(tas_dev->regmap, + tas2783_cali_reg[i] + j, + (reg_value >> (24 - 8 * j)) & 0xff); + if (ret) { + dev_err(tas_dev->dev, + "calib write to 0x%x failed, err=%d\n", + tas2783_cali_reg[i] + j, ret); + break; + } + } } break; } From f4ffa38209499d6991fdade6ec1d2cc8741127e4 Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Sat, 15 Aug 2026 14:40:00 +0300 Subject: [PATCH 081/924] ASoC: tas2783-sdw: do not cache read-only Controls The read-only SDCA Controls are readings of device state, but the regmap caches them and tas2783_reg_default[] gives each one a placeholder, so a read is answered from the cache and never reaches the peripheral. The answer is whatever the cache happens to hold: on an ASUS ProArt PX13 a read of the PDE23 actual power state returns the 0x3 placeholder, i.e. PS3, while the Function is powered and a cache-bypassing read of the same Control over the bus answers 0x0. Once the cache has been given a value it keeps it, so the Control reports a state the device left long ago. Mark those Controls volatile and drop their entries from the defaults. regcache_read() and regcache_write() skip volatile registers, so a read-only Control no longer enters the cache at all: reads reach the peripheral, and a sync cannot attempt to write a reading back. That is what regcache_sync() asks for in the first place - "any registers that should not be synced should be marked as volatile". The list is the same one the writeable_reg callback needs, so move it into a helper both callbacks use. The File Download Status is not on it, because the host writes that Control, but it is volatile all the same: the device raises its own bits on its own schedule, and sdca_fdl_process() reads the Control to see them. A cached answer would hide the device half of the handshake. Nothing in the driver reads any of these Controls - it performs no register reads at all - so the change is visible only through the regmap. Signed-off-by: Andrey Golovko Link: https://patch.msgid.link/20260815112000.4180-3-andrey.golovko@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2783-sdw.c | 68 ++++++++++++++-------------------- 1 file changed, 27 insertions(+), 41 deletions(-) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index de04941bc6de..1d31b3bfb1ad 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -185,35 +185,17 @@ static const struct reg_default tas2783_reg_default[] = { {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x01, 1), 0x1}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x0b, 1), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x01, 0), 0x1}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x06, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x07, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x09, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x0a, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS24, 0x02, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS21, 0x02, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS26, 0x02, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS28, 0x02, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, 0x1, 0), 0x3}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_SAPU29, 0x05, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x06, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU26, 0x06, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT21, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT21, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT29, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT29, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT26, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT26, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT28, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT28, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT24, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT24, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT23, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT23, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT25, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT25, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT28, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT28, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x01, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x01, 1), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x01, 2), 0x0}, @@ -222,40 +204,28 @@ static const struct reg_default tas2783_reg_default[] = { {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x01, 5), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x01, 6), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x01, 7), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x06, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x01, 0), 0x1}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x01, 1), 0x1}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x01, 2), 0x1}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x0b, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x0b, 1), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x0b, 2), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS127, 0x02, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x01, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x04, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x05, 0), 0x1}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x01, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x04, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x05, 0), 0x1}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x08, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU23, 0x10, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x10, 0), 0x1}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x12, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x13, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x14, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x15, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x16, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS24, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS21, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS26, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS28, 0x10, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, 0x10, 0), 0x3}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_UDMPU23, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_SAPU29, 0x10, 0), 0x1}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_SAPU29, 0x11, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_SAPU29, 0x12, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x11, 0), 0x0}, @@ -286,7 +256,6 @@ static const struct reg_default tas2783_reg_default[] = { {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x12, 5), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x12, 6), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x12, 7), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS127, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x11, 0), 0x0}, @@ -495,15 +464,15 @@ static bool tas2783_readable_register(struct device *dev, unsigned int reg) return tas2783_sdca_mbq_size(dev, reg) > 0; } -static bool tas2783_writeable_register(struct device *dev, unsigned int reg) +/* + * The read-only SDCA Controls the driver describes: the Latency of every + * Entity, the Clock Valid of every Clock Source, the actual power state of + * the Power Domain Entity, the protection status, the algorithm ready flag + * and the Extension Unit id, version and firmware download status. None of + * them is a setting; every one is a reading of device state. + */ +static bool tas2783_read_only_control(unsigned int reg) { - /* - * The Latency Control of every Entity, together with the Power Domain - * actual state and the protection status, is read-only. They are - * listed in tas2783_reg_default[] with a placeholder value, so without - * this a regcache_sync() would try to write them back and the - * peripheral would reject the transaction, aborting the sync. - */ switch (reg) { case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21, 0x10, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU23, 0x10, 0): @@ -538,16 +507,33 @@ static bool tas2783_writeable_register(struct device *dev, unsigned int reg) case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x04, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x04, 0): - return false; + return true; default: - return tas2783_sdca_mbq_size(dev, reg) > 0; + return false; } } +static bool tas2783_writeable_register(struct device *dev, unsigned int reg) +{ + if (tas2783_read_only_control(reg)) + return false; + + return tas2783_sdca_mbq_size(dev, reg) > 0; +} + static bool tas2783_volatile_register(struct device *dev, u32 reg) { + if (tas2783_read_only_control(reg)) + return true; + switch (reg) { + /* + * The File Download Status is a handshake: the device raises its own + * bits on its own schedule and the host writes back a response, so it + * is writable, but it must never be answered from the cache. + */ + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x14, 0): case 0x000 ... 0x080: /* Data port 0. */ case 0x100 ... 0x140: /* Data port 1. */ case 0x200 ... 0x240: /* Data port 2. */ From 3e71dc4508bc84862dc3644e3b6a5b966bff22ad Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 22:15:21 +0800 Subject: [PATCH 082/924] ASoC: sta32x: make power restoration transactional sta32x_set_bias_level() enables the codec supplies, restores the coefficient and register state, and then starts the watchdog. Register access errors in the mute and coefficient helpers and the cache replay are discarded, allowing DAPM to publish STANDBY for a partially restored codec. Propagate every operation in the local restore transaction, including the final standby-state write. Start the watchdog only after that write succeeds. On failure, hold reset, mark the cache dirty, disable the supplies and report a cleanup failure while preserving the primary error. The DAPM bias callback consumes the returned error independently of component resume handling. Fixes: 54dc6cabe684 ("ASoC: sta32x: preserve coefficient RAM") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830141521.33246-1-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/sta32x.c | 101 +++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 24 deletions(-) diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c index 7e73150f6832..e36681258a55 100644 --- a/sound/soc/codecs/sta32x.c +++ b/sound/soc/codecs/sta32x.c @@ -342,27 +342,48 @@ static int sta32x_sync_coef_shadow(struct snd_soc_component *component) { struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); unsigned int cfud; - int i; + int i, ret; /* preserve reserved bits in STA32X_CFUD */ - regmap_read(sta32x->regmap, STA32X_CFUD, &cfud); + ret = regmap_read(sta32x->regmap, STA32X_CFUD, &cfud); + if (ret) + return ret; + cfud &= 0xf0; for (i = 0; i < STA32X_COEF_COUNT; i++) { - regmap_write(sta32x->regmap, STA32X_CFADDR2, i); - regmap_write(sta32x->regmap, STA32X_B1CF1, - (sta32x->coef_shadow[i] >> 16) & 0xff); - regmap_write(sta32x->regmap, STA32X_B1CF2, - (sta32x->coef_shadow[i] >> 8) & 0xff); - regmap_write(sta32x->regmap, STA32X_B1CF3, - (sta32x->coef_shadow[i]) & 0xff); + ret = regmap_write(sta32x->regmap, STA32X_CFADDR2, i); + if (ret) + return ret; + + ret = regmap_write(sta32x->regmap, STA32X_B1CF1, + (sta32x->coef_shadow[i] >> 16) & 0xff); + if (ret) + return ret; + + ret = regmap_write(sta32x->regmap, STA32X_B1CF2, + (sta32x->coef_shadow[i] >> 8) & 0xff); + if (ret) + return ret; + + ret = regmap_write(sta32x->regmap, STA32X_B1CF3, + sta32x->coef_shadow[i] & 0xff); + if (ret) + return ret; + /* * chip documentation does not say if the bits are * self-clearing, so do it explicitly */ - regmap_write(sta32x->regmap, STA32X_CFUD, cfud); - regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x01); + ret = regmap_write(sta32x->regmap, STA32X_CFUD, cfud); + if (ret) + return ret; + + ret = regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x01); + if (ret) + return ret; } + return 0; } @@ -370,15 +391,24 @@ static int sta32x_cache_sync(struct snd_soc_component *component) { struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); unsigned int mute; - int rc; + int restore_ret, ret; /* mute during register sync */ - regmap_read(sta32x->regmap, STA32X_MMUTE, &mute); - regmap_write(sta32x->regmap, STA32X_MMUTE, mute | STA32X_MMUTE_MMUTE); - sta32x_sync_coef_shadow(component); - rc = regcache_sync(sta32x->regmap); - regmap_write(sta32x->regmap, STA32X_MMUTE, mute); - return rc; + ret = regmap_read(sta32x->regmap, STA32X_MMUTE, &mute); + if (ret) + return ret; + + ret = regmap_write(sta32x->regmap, STA32X_MMUTE, + mute | STA32X_MMUTE_MMUTE); + if (ret) + return ret; + + ret = sta32x_sync_coef_shadow(component); + if (!ret) + ret = regcache_sync(sta32x->regmap); + + restore_ret = regmap_write(sta32x->regmap, STA32X_MMUTE, mute); + return ret ?: restore_ret; } /* work around ESD issue where sta32x resets and loses all configuration */ @@ -794,9 +824,9 @@ static int sta32x_startup_sequence(struct sta32x_priv *sta32x) static int sta32x_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { - int ret; struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + int cleanup_ret, ret; dev_dbg(component->dev, "level = %d\n", level); switch (level) { @@ -820,15 +850,28 @@ static int sta32x_set_bias_level(struct snd_soc_component *component, return ret; } - sta32x_startup_sequence(sta32x); - sta32x_cache_sync(component); + ret = sta32x_startup_sequence(sta32x); + if (!ret) + ret = sta32x_cache_sync(component); + if (ret) + goto restore_failed; + + ret = regmap_update_bits(sta32x->regmap, STA32X_CONFF, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD, + 0); + if (ret) + goto restore_failed; + sta32x_watchdog_start(sta32x); + break; } /* Power down */ - regmap_update_bits(sta32x->regmap, STA32X_CONFF, - STA32X_CONFF_PWDN | STA32X_CONFF_EAPD, - 0); + ret = regmap_update_bits(sta32x->regmap, STA32X_CONFF, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD, + 0); + if (ret) + return ret; break; @@ -846,6 +889,16 @@ static int sta32x_set_bias_level(struct snd_soc_component *component, break; } return 0; + +restore_failed: + gpiod_set_value(sta32x->gpiod_nreset, 0); + regcache_mark_dirty(sta32x->regmap); + cleanup_ret = regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), + sta32x->supplies); + if (cleanup_ret) + dev_warn(component->dev, "Failed to disable supplies: %d\n", + cleanup_ret); + return ret; } static const struct snd_soc_dai_ops sta32x_dai_ops = { From 9c343445b349422bddebb4cb9668c246bc6f08de Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 22:18:28 +0800 Subject: [PATCH 083/924] ASoC: nau8822: keep regcache offline while supplies are off nau8822_suspend() disables all codec supplies and marks the register cache dirty, but leaves regmap in live I/O mode. Resume then attempts the cache replay without first leaving a corresponding cache-only state. Enter cache-only mode after the regulators have been disabled successfully, leave it after the supplies are restored, and stop the local resume sequence if cache replay fails. Do not power the codec back down on a resume error, because deferred component resume remains best effort and continues after the callback. regulator_bulk_disable() restores regulators already disabled by the same bulk operation if a later disable fails. Only publish cache-only state after the whole power-off operation succeeds, so the regmap state continues to match the hardware state on either outcome. Fixes: 1e3cb6c321be ("ASoC: nau8822: new codec driver") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830141828.34419-1-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/nau8822.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/nau8822.c b/sound/soc/codecs/nau8822.c index 830164e991a7..889c3b5f6d04 100644 --- a/sound/soc/codecs/nau8822.c +++ b/sound/soc/codecs/nau8822.c @@ -1059,10 +1059,14 @@ static int nau8822_suspend(struct snd_soc_component *component) { struct nau8822 *nau8822 = snd_soc_component_get_drvdata(component); struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + int ret; snd_soc_dapm_force_bias_level(dapm, SND_SOC_BIAS_OFF); - regulator_bulk_disable(NAU8822_NUM_SUPPLIES, nau8822->supplies); + ret = regulator_bulk_disable(NAU8822_NUM_SUPPLIES, nau8822->supplies); + if (ret) + return ret; + regcache_cache_only(nau8822->regmap, true); regcache_mark_dirty(nau8822->regmap); return 0; @@ -1072,7 +1076,9 @@ static int nau8822_resume(struct snd_soc_component *component) { struct nau8822 *nau8822 = snd_soc_component_get_drvdata(component); struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); - int ret = regulator_bulk_enable(NAU8822_NUM_SUPPLIES, nau8822->supplies); + int ret; + + ret = regulator_bulk_enable(NAU8822_NUM_SUPPLIES, nau8822->supplies); if (ret) { dev_err(component->dev, @@ -1082,7 +1088,10 @@ static int nau8822_resume(struct snd_soc_component *component) fsleep(100); - regcache_sync(nau8822->regmap); + regcache_cache_only(nau8822->regmap, false); + ret = regcache_sync(nau8822->regmap); + if (ret) + return ret; snd_soc_dapm_force_bias_level(dapm, SND_SOC_BIAS_STANDBY); From a27da8f9fceb12ec4cf438f111f9a8c93d9c4eab Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:06 +0000 Subject: [PATCH 084/924] ASoC: soc-core: make fmt_{single/multiple}_name() non static DAI/Component will be capsuled soon, but fmt_{single/multiple}_name() will be used form soc-dai.c / soc-component.c. Let's makes them to non static function. ASoC functions / macros are defined at linux/include/sound/xxx.h (A). ASoC framework and each vender drivers are including it. Some functions / macros which are only for ASoC framework. It shouldn't be used from vender drivers. But we can't foce it because sharing same headers (A). This patch adds new soc-internal.h (B) which is for ASoC framework internal use. Since this patch, we can separate global functions (for vender drivers) and local function (for ASoC framework internal use). Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/871pbeu75e.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 12 ++++++------ sound/soc/soc-internal.h | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 sound/soc/soc-internal.h diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index b9c70268d49f..d58fd8be03a9 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -41,6 +41,7 @@ #include #include #include +#include "soc-internal.h" #define CREATE_TRACE_POINTS #include @@ -2480,7 +2481,7 @@ EXPORT_SYMBOL_GPL(snd_soc_unregister_card); * Simplify DAI link configuration by removing ".-1" from device names * and sanitizing names. */ -static char *fmt_single_name(struct device *dev, int *id) +char *snd_soc_fmt_single_name(struct device *dev, int *id) { const char *devname = dev_name(dev); char *found, *name; @@ -2529,8 +2530,7 @@ static char *fmt_single_name(struct device *dev, int *id) * Simplify DAI link naming for single devices with multiple DAIs by removing * any ".-1" and using the DAI name (instead of device name). */ -static inline char *fmt_multiple_name(struct device *dev, - struct snd_soc_dai_driver *dai_drv) +char *snd_soc_fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *dai_drv) { if (dai_drv->name == NULL) { dev_err(dev, @@ -2586,9 +2586,9 @@ struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component, */ if (legacy_dai_naming && (dai_drv->id == 0 || dai_drv->name == NULL)) { - dai->name = fmt_single_name(dev, &dai->id); + dai->name = snd_soc_fmt_single_name(dev, &dai->id); } else { - dai->name = fmt_multiple_name(dev, dai_drv); + dai->name = snd_soc_fmt_multiple_name(dev, dai_drv); if (dai_drv->id) dai->id = dai_drv->id; else @@ -2724,7 +2724,7 @@ static int soc_component_initialize(struct snd_soc_component *component, mutex_init(&component->io_mutex); if (!component->name) { - component->name = fmt_single_name(dev, NULL); + component->name = snd_soc_fmt_single_name(dev, NULL); if (!component->name) { dev_err(dev, "ASoC: Failed to allocate name\n"); return -ENOMEM; diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h new file mode 100644 index 000000000000..6f6a7be0487a --- /dev/null +++ b/sound/soc/soc-internal.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * soc-internal.h + * + * Copyright (c) 2026 Kuninori Morimoto + */ +#ifndef __SOC_INTERNAL_H +#define __SOC_INTERNAL_H + +/* + * This header is for ALSA SoC Framework internal, not for Vender drivers. + * The ALSA SoC functions for Vender drivers are defined in linux/include/sound/xxx.h + * as snd_soc_xxx(); + */ + +/* + * In soc-core + */ +char *snd_soc_fmt_single_name(struct device *dev, int *id); +char *snd_soc_fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *dai_drv); + +#endif /* __SOC_INTERNAL_H */ From ad9bcf81fdcc53b28a9363efe15d944d4a6bec30 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:10 +0000 Subject: [PATCH 085/924] ASoC: soc-dai: move snd_soc_add_dai_controls() snd_soc_add_dai_controls() is DAI related function. And its parameter will be capsuled soon, will be not enable to access from soc-core.c. Move it to soc-dai.c It renames function name - snd_soc_add_dai_controls() + snd_soc_dai_add_controls() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87zey2ssku.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 3 ++- include/sound/soc.h | 7 +++++-- sound/soc/soc-core.c | 26 +++----------------------- sound/soc/soc-dai.c | 20 ++++++++++++++++++++ sound/soc/soc-internal.h | 3 +++ 5 files changed, 33 insertions(+), 26 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index ba3ae56c6b06..4d825b1541e3 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -213,7 +213,8 @@ int snd_soc_dai_get_channel_map(const struct snd_soc_dai *dai, unsigned int *rx_num, unsigned int *rx_slot); int snd_soc_dai_is_dummy(const struct snd_soc_dai *dai); - +int snd_soc_dai_add_controls(struct snd_soc_dai *dai, + const struct snd_kcontrol_new *controls, int num_controls); int snd_soc_dai_hw_params(struct snd_soc_dai *dai, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); diff --git a/include/sound/soc.h b/include/sound/soc.h index f46b2bc2a022..e7bed64375bf 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -555,8 +555,6 @@ int snd_soc_add_component_controls(struct snd_soc_component *component, const struct snd_kcontrol_new *controls, unsigned int num_controls); int snd_soc_add_card_controls(struct snd_soc_card *soc_card, const struct snd_kcontrol_new *controls, int num_controls); -int snd_soc_add_dai_controls(struct snd_soc_dai *dai, - const struct snd_kcontrol_new *controls, int num_controls); int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo); int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, @@ -1550,4 +1548,9 @@ static inline void _snd_soc_dpcm_mutex_assert_held_r(struct snd_soc_pcm_runtime #include #include +/* + * REMOVE ME + */ +#define snd_soc_add_dai_controls snd_soc_dai_add_controls + #endif diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index d58fd8be03a9..764ce12a694b 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2344,9 +2344,9 @@ struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template, } EXPORT_SYMBOL_GPL(snd_soc_cnew); -static int snd_soc_add_controls(struct snd_card *card, struct device *dev, - const struct snd_kcontrol_new *controls, int num_controls, - const char *prefix, void *data) +int snd_soc_add_controls(struct snd_card *card, struct device *dev, + const struct snd_kcontrol_new *controls, int num_controls, + const char *prefix, void *data) { int i; @@ -2403,26 +2403,6 @@ int snd_soc_add_card_controls(struct snd_soc_card *soc_card, } EXPORT_SYMBOL_GPL(snd_soc_add_card_controls); -/** - * snd_soc_add_dai_controls - add an array of controls to a DAI. - * Convenience function to add a list of controls. - * - * @dai: DAI to add controls to - * @controls: array of controls to add - * @num_controls: number of elements in the array - * - * Return 0 for success, else error. - */ -int snd_soc_add_dai_controls(struct snd_soc_dai *dai, - const struct snd_kcontrol_new *controls, int num_controls) -{ - struct snd_card *card = dai->component->card->snd_card; - - return snd_soc_add_controls(card, dai->dev, controls, num_controls, - NULL, dai); -} -EXPORT_SYMBOL_GPL(snd_soc_add_dai_controls); - /** * snd_soc_register_card - Register a card with the ASoC core * diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index b098d1100b4b..38244d8ac0eb 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -9,6 +9,7 @@ #include #include #include +#include "soc-internal.h" #define soc_dai_ret(dai, ret) _soc_dai_ret(dai, __func__, ret) static inline int _soc_dai_ret(const struct snd_soc_dai *dai, @@ -989,3 +990,22 @@ int snd_soc_dai_compr_get_metadata(struct snd_soc_dai *dai, return soc_dai_ret(dai, ret); } EXPORT_SYMBOL_GPL(snd_soc_dai_compr_get_metadata); + +/** + * snd_soc_dai_add_controls - add an array of controls to a DAI. + * Convenience function to add a list of controls. + * + * @dai: DAI to add controls to + * @controls: array of controls to add + * @num_controls: number of elements in the array + * + * Return 0 for success, else error. + */ +int snd_soc_dai_add_controls(struct snd_soc_dai *dai, + const struct snd_kcontrol_new *controls, int num_controls) +{ + struct snd_card *card = dai->component->card->snd_card; + + return snd_soc_add_controls(card, dai->dev, controls, num_controls, NULL, dai); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_add_controls); diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 6f6a7be0487a..7dc296e94ce6 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -18,5 +18,8 @@ */ char *snd_soc_fmt_single_name(struct device *dev, int *id); char *snd_soc_fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *dai_drv); +int snd_soc_add_controls(struct snd_card *card, struct device *dev, + const struct snd_kcontrol_new *controls, int num_controls, + const char *prefix, void *data); #endif /* __SOC_INTERNAL_H */ From e913f9b77f391762574a72a81e4b4e5b439b942d Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:15 +0000 Subject: [PATCH 086/924] ASoC: soc-dai: move snd_soc_dai_name_get() snd_soc_dai_name_get() is DAI related function. And its parameter will be capsuled soon, will be not enable to access from soc-core.c. Move it to soc-dai.c It renames function name - snd_soc_dai_name_get() + snd_soc_dai_name() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87y0dmssko.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 2 +- sound/soc/generic/simple-card-utils.c | 2 +- sound/soc/generic/simple-card.c | 2 +- sound/soc/soc-core.c | 20 ++------------------ sound/soc/soc-dai.c | 16 ++++++++++++++++ 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 4d825b1541e3..6cbec0994a31 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -278,7 +278,7 @@ int snd_soc_dai_compr_get_metadata(struct snd_soc_dai *dai, struct snd_compr_stream *cstream, struct snd_compr_metadata *metadata); -const char *snd_soc_dai_name_get(const struct snd_soc_dai *dai); +const char *snd_soc_dai_name(const struct snd_soc_dai *dai); struct snd_soc_dai_ops { /* DAI driver callbacks */ diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 5f3423129b13..7df5253499cd 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -1116,7 +1116,7 @@ int graph_util_parse_dai(struct simple_util_priv *priv, struct device_node *ep, args.np = ep; dai = snd_soc_get_dai_via_args(&args); if (dai) { - const char *dai_name = snd_soc_dai_name_get(dai); + const char *dai_name = snd_soc_dai_name(dai); const struct of_phandle_args *dai_args = snd_soc_copy_dai_args(dev, &args); ret = -ENOMEM; diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index e7ad7af04714..0a769c177149 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -92,7 +92,7 @@ static int simple_parse_dai(struct simple_util_priv *priv, dai = snd_soc_get_dai_via_args(&args); if (dai) { ret = -ENOMEM; - dlc->dai_name = snd_soc_dai_name_get(dai); + dlc->dai_name = snd_soc_dai_name(dai); dlc->dai_args = snd_soc_copy_dai_args(dev, &args); if (!dlc->dai_args) goto end; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 764ce12a694b..4d0496833b2b 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -276,7 +276,7 @@ static int snd_soc_is_matching_dai(const struct snd_soc_dai_link_component *dlc, if (!dlc->dai_name) return 1; - /* see snd_soc_dai_name_get() */ + /* see snd_soc_dai_name() */ if (dai->driver->name && strcmp(dlc->dai_name, dai->driver->name) == 0) @@ -292,22 +292,6 @@ static int snd_soc_is_matching_dai(const struct snd_soc_dai_link_component *dlc, return 0; } -const char *snd_soc_dai_name_get(const struct snd_soc_dai *dai) -{ - /* see snd_soc_is_matching_dai() */ - if (dai->driver->name) - return dai->driver->name; - - if (dai->name) - return dai->name; - - if (dai->component->name) - return dai->component->name; - - return NULL; -} -EXPORT_SYMBOL_GPL(snd_soc_dai_name_get); - static int snd_soc_rtd_add_component(struct snd_soc_pcm_runtime *rtd, struct snd_soc_component *component) { @@ -3457,7 +3441,7 @@ int snd_soc_get_dlc(const struct of_phandle_args *args, struct snd_soc_dai_link_ id--; } - dlc->dai_name = snd_soc_dai_name_get(dai); + dlc->dai_name = snd_soc_dai_name(dai); } else if (ret) { /* * if another error than ENOTSUPP is returned go on and diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 38244d8ac0eb..6620abee5e81 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1009,3 +1009,19 @@ int snd_soc_dai_add_controls(struct snd_soc_dai *dai, return snd_soc_add_controls(card, dai->dev, controls, num_controls, NULL, dai); } EXPORT_SYMBOL_GPL(snd_soc_dai_add_controls); + +const char *snd_soc_dai_name(const struct snd_soc_dai *dai) +{ + /* see snd_soc_is_matching_dai() */ + if (dai->driver->name) + return dai->driver->name; + + if (dai->name) + return dai->name; + + if (dai->component->name) + return dai->component->name; + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_name); From eee9ed47dfa5dbf5667c08d30c037edf6ac107a3 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:19 +0000 Subject: [PATCH 087/924] ASoC: soc-dai: move snd_soc_is_match_dai_args() snd_soc_is_match_dai_args() is DAI related function. And its parameter will be capsuled soon, will be not enable to access from soc-core.c. Move it to soc-dai.c It renames function name - snd_soc_is_match_dai_args() + snd_soc_dai_matches_args() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87wlt6sskk.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 2 ++ sound/soc/soc-core.c | 20 ++------------------ sound/soc/soc-dai.c | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 6cbec0994a31..e2d995bb1c78 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -278,6 +278,8 @@ int snd_soc_dai_compr_get_metadata(struct snd_soc_dai *dai, struct snd_compr_stream *cstream, struct snd_compr_metadata *metadata); +int snd_soc_dai_matches_args(const struct snd_soc_dai *dai, + const struct of_phandle_args *args2); const char *snd_soc_dai_name(const struct snd_soc_dai *dai); struct snd_soc_dai_ops { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 4d0496833b2b..9d67db679336 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -233,22 +233,6 @@ static inline void snd_soc_debugfs_exit(void) { } #endif -static int snd_soc_is_match_dai_args(const struct of_phandle_args *args1, - const struct of_phandle_args *args2) -{ - if (!args1 || !args2) - return 0; - - if (args1->np != args2->np) - return 0; - - for (int i = 0; i < args1->args_count; i++) - if (args1->args[i] != args2->args[i]) - return 0; - - return 1; -} - static inline int snd_soc_dlc_component_is_empty(struct snd_soc_dai_link_component *dlc) { return !(dlc->dai_args || dlc->name || dlc->of_node); @@ -271,7 +255,7 @@ static int snd_soc_is_matching_dai(const struct snd_soc_dai_link_component *dlc, return 0; if (dlc->dai_args) - return snd_soc_is_match_dai_args(dai->driver->dai_args, dlc->dai_args); + return snd_soc_dai_matches_args(dai, dlc->dai_args); if (!dlc->dai_name) return 1; @@ -3516,7 +3500,7 @@ struct snd_soc_dai *snd_soc_get_dai_via_args(const struct of_phandle_args *dai_a for_each_component(component) { for_each_component_dais(component, dai) - if (snd_soc_is_match_dai_args(dai->driver->dai_args, dai_args)) + if (snd_soc_dai_matches_args(dai, dai_args)) return dai; } return NULL; diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 6620abee5e81..ac815aa212d3 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1010,6 +1010,24 @@ int snd_soc_dai_add_controls(struct snd_soc_dai *dai, } EXPORT_SYMBOL_GPL(snd_soc_dai_add_controls); +int snd_soc_dai_matches_args(const struct snd_soc_dai *dai, + const struct of_phandle_args *args2) +{ + const struct of_phandle_args *args1 = dai->driver->dai_args; + + if (!args1 || !args2) + return 0; + + if (args1->np != args2->np) + return 0; + + for (int i = 0; i < args1->args_count; i++) + if (args1->args[i] != args2->args[i]) + return 0; + + return 1; +} + const char *snd_soc_dai_name(const struct snd_soc_dai *dai) { /* see snd_soc_is_matching_dai() */ From 6c668f07513d7f742f370664a666137706445794 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:22 +0000 Subject: [PATCH 088/924] ASoC: soc-dai: move snd_soc_is_matching_dai() snd_soc_is_matching_dai() is DAI related function. And its parameter will be capsuled soon, will be not enable to access from soc-core.c. Move it to soc-dai.c It renames function name - snd_soc_is_matching_dai() + snd_soc_dai_matches_dlc() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87v78qsskh.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 2 ++ sound/soc/soc-core.c | 32 ++------------------------------ sound/soc/soc-dai.c | 30 +++++++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index e2d995bb1c78..dd6ac8ac4130 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -280,6 +280,8 @@ int snd_soc_dai_compr_get_metadata(struct snd_soc_dai *dai, int snd_soc_dai_matches_args(const struct snd_soc_dai *dai, const struct of_phandle_args *args2); +int snd_soc_dai_matches_dlc(struct snd_soc_dai *dai, + const struct snd_soc_dai_link_component *dlc); const char *snd_soc_dai_name(const struct snd_soc_dai *dai); struct snd_soc_dai_ops { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 9d67db679336..7d8b1103f40f 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -248,34 +248,6 @@ static inline int snd_soc_dlc_dai_is_empty(struct snd_soc_dai_link_component *dl return !(dlc->dai_args || dlc->dai_name); } -static int snd_soc_is_matching_dai(const struct snd_soc_dai_link_component *dlc, - struct snd_soc_dai *dai) -{ - if (!dlc) - return 0; - - if (dlc->dai_args) - return snd_soc_dai_matches_args(dai, dlc->dai_args); - - if (!dlc->dai_name) - return 1; - - /* see snd_soc_dai_name() */ - - if (dai->driver->name && - strcmp(dlc->dai_name, dai->driver->name) == 0) - return 1; - - if (strcmp(dlc->dai_name, dai->name) == 0) - return 1; - - if (dai->component->name && - strcmp(dlc->dai_name, dai->component->name) == 0) - return 1; - - return 0; -} - static int snd_soc_rtd_add_component(struct snd_soc_pcm_runtime *rtd, struct snd_soc_component *component) { @@ -837,7 +809,7 @@ static int snd_soc_is_matching_component( struct snd_soc_dai *dai; for_each_component_dais(component, dai) - if (snd_soc_is_matching_dai(dlc, dai)) + if (snd_soc_dai_matches_dlc(dai, dlc)) return 1; return 0; } @@ -897,7 +869,7 @@ struct snd_soc_dai *snd_soc_find_dai( for_each_component(component) if (snd_soc_is_matching_component(dlc, component)) for_each_component_dais(component, dai) - if (snd_soc_is_matching_dai(dlc, dai)) + if (snd_soc_dai_matches_dlc(dai, dlc)) return dai; return NULL; diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index ac815aa212d3..d22815656d14 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1028,9 +1028,37 @@ int snd_soc_dai_matches_args(const struct snd_soc_dai *dai, return 1; } +int snd_soc_dai_matches_dlc(struct snd_soc_dai *dai, + const struct snd_soc_dai_link_component *dlc) +{ + if (!dlc) + return 0; + + if (dlc->dai_args) + return snd_soc_dai_matches_args(dai, dlc->dai_args); + + if (!dlc->dai_name) + return 1; + + /* see snd_soc_dai_name() */ + + if (dai->driver->name && + strcmp(dlc->dai_name, dai->driver->name) == 0) + return 1; + + if (strcmp(dlc->dai_name, dai->name) == 0) + return 1; + + if (dai->component->name && + strcmp(dlc->dai_name, dai->component->name) == 0) + return 1; + + return 0; +} + const char *snd_soc_dai_name(const struct snd_soc_dai *dai) { - /* see snd_soc_is_matching_dai() */ + /* see snd_soc_dai_matches_dlc() */ if (dai->driver->name) return dai->driver->name; From 6d2c9a5caa11f635a503b33a75dc0b75825f10fd Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:26 +0000 Subject: [PATCH 089/924] ASoC: soc-dai: move snd_soc_{un}register_dai() snd_soc_{un}register_dai() is DAI related function. And its parameter will be capsuled soon, will be not enable to access from soc-core.c. Move it to soc-dai.c Note1 snd_soc_{un}register_dai() needs to use client_mutex which is soc-core.c local. This patch removes static from it, and allow to use it from ASoC internal. It renames function name - snd_soc_register_dai() + snd_soc_dai_register() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87tsoasske.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 5 +++ include/sound/soc.h | 6 ++-- sound/soc/soc-core.c | 71 ++-------------------------------------- sound/soc/soc-dai.c | 68 ++++++++++++++++++++++++++++++++++++++ sound/soc/soc-internal.h | 2 ++ 5 files changed, 79 insertions(+), 73 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index dd6ac8ac4130..ea5182344c0d 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -610,4 +610,9 @@ static inline void *snd_soc_dai_get_stream(struct snd_soc_dai *dai, return ERR_PTR(-ENOTSUPP); } +struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, + struct snd_soc_dai_driver *dai_drv, + bool legacy_dai_naming); +void snd_soc_dai_unregister(struct snd_soc_dai *dai); + #endif diff --git a/include/sound/soc.h b/include/sound/soc.h index e7bed64375bf..e49d1573ace5 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1386,10 +1386,6 @@ void snd_soc_dlc_use_cpu_as_platform(struct snd_soc_dai_link_component *platform struct of_phandle_args *snd_soc_copy_dai_args(struct device *dev, const struct of_phandle_args *args); struct snd_soc_dai *snd_soc_get_dai_via_args(const struct of_phandle_args *dai_args); -struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component, - struct snd_soc_dai_driver *dai_drv, - bool legacy_dai_naming); -void snd_soc_unregister_dai(struct snd_soc_dai *dai); struct snd_soc_dai *snd_soc_find_dai( const struct snd_soc_dai_link_component *dlc); @@ -1552,5 +1548,7 @@ static inline void _snd_soc_dpcm_mutex_assert_held_r(struct snd_soc_pcm_runtime * REMOVE ME */ #define snd_soc_add_dai_controls snd_soc_dai_add_controls +#define snd_soc_register_dai snd_soc_dai_register +#define snd_soc_unregister_dai snd_soc_dai_unregister #endif diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 7d8b1103f40f..16d652993b2f 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -46,7 +46,8 @@ #define CREATE_TRACE_POINTS #include -static DEFINE_MUTEX(client_mutex); +DEFINE_MUTEX(client_mutex); + static LIST_HEAD(component_list); static LIST_HEAD(unbind_card_list); @@ -2462,74 +2463,6 @@ char *snd_soc_fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *d return devm_kstrdup(dev, dai_drv->name, GFP_KERNEL); } -void snd_soc_unregister_dai(struct snd_soc_dai *dai) -{ - lockdep_assert_held(&client_mutex); - - dev_dbg(dai->dev, "ASoC: Unregistered DAI '%s'\n", dai->name); - list_del(&dai->list); -} -EXPORT_SYMBOL_GPL(snd_soc_unregister_dai); - -/** - * snd_soc_register_dai - Register a DAI dynamically & create its widgets - * - * @component: The component the DAIs are registered for - * @dai_drv: DAI driver to use for the DAI - * @legacy_dai_naming: if %true, use legacy single-name format; - * if %false, use multiple-name format; - * - * Topology can use this API to register DAIs when probing a component. - * These DAIs's widgets will be freed in the card cleanup and the DAIs - * will be freed in the component cleanup. - */ -struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component, - struct snd_soc_dai_driver *dai_drv, - bool legacy_dai_naming) -{ - struct device *dev = component->dev; - struct snd_soc_dai *dai; - - lockdep_assert_held(&client_mutex); - - dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL); - if (dai == NULL) - return NULL; - - /* - * Back in the old days when we still had component-less DAIs, - * instead of having a static name, component-less DAIs would - * inherit the name of the parent device so it is possible to - * register multiple instances of the DAI. We still need to keep - * the same naming style even though those DAIs are not - * component-less anymore. - */ - if (legacy_dai_naming && - (dai_drv->id == 0 || dai_drv->name == NULL)) { - dai->name = snd_soc_fmt_single_name(dev, &dai->id); - } else { - dai->name = snd_soc_fmt_multiple_name(dev, dai_drv); - if (dai_drv->id) - dai->id = dai_drv->id; - else - dai->id = component->num_dai; - } - if (!dai->name) - return NULL; - - dai->component = component; - dai->dev = dev; - dai->driver = dai_drv; - - /* see for_each_component_dais */ - list_add_tail(&dai->list, &component->dai_list); - component->num_dai++; - - dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name); - return dai; -} -EXPORT_SYMBOL_GPL(snd_soc_register_dai); - /** * snd_soc_unregister_dais - Unregister DAIs from the ASoC core * diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index d22815656d14..f9bb4fa356d7 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1071,3 +1071,71 @@ const char *snd_soc_dai_name(const struct snd_soc_dai *dai) return NULL; } EXPORT_SYMBOL_GPL(snd_soc_dai_name); + +void snd_soc_dai_unregister(struct snd_soc_dai *dai) +{ + lockdep_assert_held(&client_mutex); + + dev_dbg(dai->dev, "ASoC: Unregistered DAI '%s'\n", dai->name); + list_del(&dai->list); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_unregister); + +/** + * snd_soc_dai_register - Register a DAI dynamically & create its widgets + * + * @component: The component the DAIs are registered for + * @dai_drv: DAI driver to use for the DAI + * @legacy_dai_naming: if %true, use legacy single-name format; + * if %false, use multiple-name format; + * + * Topology can use this API to register DAIs when probing a component. + * These DAIs's widgets will be freed in the card cleanup and the DAIs + * will be freed in the component cleanup. + */ +struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, + struct snd_soc_dai_driver *dai_drv, + bool legacy_dai_naming) +{ + struct device *dev = component->dev; + struct snd_soc_dai *dai; + + lockdep_assert_held(&client_mutex); + + dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL); + if (dai == NULL) + return NULL; + + /* + * Back in the old days when we still had component-less DAIs, + * instead of having a static name, component-less DAIs would + * inherit the name of the parent device so it is possible to + * register multiple instances of the DAI. We still need to keep + * the same naming style even though those DAIs are not + * component-less anymore. + */ + if (legacy_dai_naming && + (dai_drv->id == 0 || dai_drv->name == NULL)) { + dai->name = snd_soc_fmt_single_name(dev, &dai->id); + } else { + dai->name = snd_soc_fmt_multiple_name(dev, dai_drv); + if (dai_drv->id) + dai->id = dai_drv->id; + else + dai->id = component->num_dai; + } + if (!dai->name) + return NULL; + + dai->component = component; + dai->dev = dev; + dai->driver = dai_drv; + + /* see for_each_component_dais */ + list_add_tail(&dai->list, &component->dai_list); + component->num_dai++; + + dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name); + return dai; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_register); diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 7dc296e94ce6..1c43a06332c2 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -16,6 +16,8 @@ /* * In soc-core */ +extern struct mutex client_mutex; + char *snd_soc_fmt_single_name(struct device *dev, int *id); char *snd_soc_fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *dai_drv); int snd_soc_add_controls(struct snd_card *card, struct device *dev, From b143c6c1348056049575554bf64ab4d27786785c Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:29 +0000 Subject: [PATCH 090/924] ASoC: soc-dai: move soc_pcm_set_dai_params() soc_pcm_set_dai_params() is DAI related function. And its parameter will be capsuled soon, will be not enable to access from soc-pcm.c. Move it to soc-dai.c It renames function name - soc_pcm_set_dai_params() + snd_soc_dai_symmetric_set_params() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87se3usska.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc.h | 3 --- sound/soc/soc-compress.c | 5 +++-- sound/soc/soc-dai.c | 15 +++++++++++++++ sound/soc/soc-internal.h | 6 ++++++ sound/soc/soc-pcm.c | 25 ++++++------------------- 5 files changed, 30 insertions(+), 24 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index e49d1573ace5..25fecbe50d99 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1392,9 +1392,6 @@ struct snd_soc_dai *snd_soc_find_dai( struct snd_soc_dai *snd_soc_find_dai_with_mutex( const struct snd_soc_dai_link_component *dlc); -void soc_pcm_set_dai_params(struct snd_soc_dai *dai, - struct snd_pcm_hw_params *params); - #include static inline diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index b8402802ae78..093a70a93d2b 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -20,6 +20,7 @@ #include #include #include +#include "soc-internal.h" static int snd_soc_compr_components_open(struct snd_compr_stream *cstream) { @@ -69,10 +70,10 @@ static int soc_compr_clean(struct snd_compr_stream *cstream, int rollback) snd_soc_dai_digital_mute(codec_dai, 1, stream); if (!snd_soc_dai_active(cpu_dai)) - soc_pcm_set_dai_params(cpu_dai, NULL); + snd_soc_dai_symmetric_set_params(cpu_dai, NULL); if (!snd_soc_dai_active(codec_dai)) - soc_pcm_set_dai_params(codec_dai, NULL); + snd_soc_dai_symmetric_set_params(codec_dai, NULL); snd_soc_link_compr_shutdown(cstream, rollback); diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index f9bb4fa356d7..b8b70c533070 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -6,6 +6,7 @@ // Kuninori Morimoto // +#include #include #include #include @@ -1139,3 +1140,17 @@ struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, return dai; } EXPORT_SYMBOL_GPL(snd_soc_dai_register); + +void snd_soc_dai_symmetric_set_params(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + if (params) { + dai->symmetric_rate = params_rate(params); + dai->symmetric_channels = params_channels(params); + dai->symmetric_sample_bits = snd_pcm_format_physical_width(params_format(params)); + } else { + dai->symmetric_rate = 0; + dai->symmetric_channels = 0; + dai->symmetric_sample_bits = 0; + } +} diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 1c43a06332c2..ce68402bde52 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -24,4 +24,10 @@ int snd_soc_add_controls(struct snd_card *card, struct device *dev, const struct snd_kcontrol_new *controls, int num_controls, const char *prefix, void *data); +/* + * In soc-dai + */ +void snd_soc_dai_symmetric_set_params(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params); + #endif /* __SOC_INTERNAL_H */ diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 0e49290a8c90..04d7079c338f 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -26,6 +26,7 @@ #include #include #include +#include "soc-internal.h" DEFINE_GUARD(snd_soc_card_mutex, struct snd_soc_card *, @@ -427,20 +428,6 @@ void dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, int event) snd_soc_dapm_stream_event(fe, dir, event); } -void soc_pcm_set_dai_params(struct snd_soc_dai *dai, - struct snd_pcm_hw_params *params) -{ - if (params) { - dai->symmetric_rate = params_rate(params); - dai->symmetric_channels = params_channels(params); - dai->symmetric_sample_bits = snd_pcm_format_physical_width(params_format(params)); - } else { - dai->symmetric_rate = 0; - dai->symmetric_channels = 0; - dai->symmetric_sample_bits = 0; - } -} - static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream, struct snd_soc_dai *soc_dai) { @@ -589,7 +576,7 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, unsigned int symmetry, i; d.name = __func__; - soc_pcm_set_dai_params(&d, params); + snd_soc_dai_symmetric_set_params(&d, params); #define __soc_pcm_params_symmetry(xxx) \ symmetry = rtd->dai_link->symmetric_##xxx; \ @@ -879,7 +866,7 @@ static int soc_pcm_clean(struct snd_soc_pcm_runtime *rtd, /* Make sure DAI parameters cleared if the DAI becomes inactive */ for_each_rtd_dais(rtd, i, dai) { if (snd_soc_dai_active(dai) == 0) - soc_pcm_set_dai_params(dai, NULL); + snd_soc_dai_symmetric_set_params(dai, NULL); } } @@ -1135,7 +1122,7 @@ static int soc_pcm_hw_clean(struct snd_soc_pcm_runtime *rtd, /* clear the corresponding DAIs parameters when going to be inactive */ for_each_rtd_dais(rtd, i, dai) { if (snd_soc_dai_active(dai) == 1) - soc_pcm_set_dai_params(dai, NULL); + snd_soc_dai_symmetric_set_params(dai, NULL); if (snd_soc_dai_stream_active(dai, substream->stream) == 1) { if (!snd_soc_dai_mute_is_ctrled_at_trigger(dai)) @@ -1237,7 +1224,7 @@ static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, if(ret < 0) goto out; - soc_pcm_set_dai_params(codec_dai, &tmp_params); + snd_soc_dai_symmetric_set_params(codec_dai, &tmp_params); snd_soc_dapm_update_dai(substream, &tmp_params, codec_dai); } @@ -1275,7 +1262,7 @@ static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, goto out; /* store the parameters for each DAI */ - soc_pcm_set_dai_params(cpu_dai, &tmp_params); + snd_soc_dai_symmetric_set_params(cpu_dai, &tmp_params); snd_soc_dapm_update_dai(substream, &tmp_params, cpu_dai); } From 579d17faf16371954c419c4306fdcaa3d848d8ed Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:32 +0000 Subject: [PATCH 091/924] ASoC: soc-dai: move soc_pcm_{apply/params/update}_symmetry() soc_pcm_{apply/params/update}_symmetry() are DAI related function. And its parameter will be capsuled soon, will be not enable to access from soc-pcm.c. Move it to soc-dai.c It renames function name - soc_pcm_xxx_symmetry() + snd_soc_dai_symmetric_xxx() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87qzjessk7.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-dai.c | 85 +++++++++++++++++++++++++++++++++ sound/soc/soc-internal.h | 3 ++ sound/soc/soc-pcm.c | 100 +++------------------------------------ 3 files changed, 95 insertions(+), 93 deletions(-) diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index b8b70c533070..73ef977d23cc 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1154,3 +1154,88 @@ void snd_soc_dai_symmetric_set_params(struct snd_soc_dai *dai, dai->symmetric_sample_bits = 0; } } + +int snd_soc_dai_symmetric_apply(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + int ret; + + if (!snd_soc_dai_active(dai)) + return 0; + +#define __symmetric_apply(name, NAME) \ + if (dai->symmetric_##name && \ + (dai->driver->symmetric_##name || rtd->dai_link->symmetric_##name)) { \ + dev_dbg(dai->dev, "ASoC: Symmetry forces %s to %d\n", \ + #name, dai->symmetric_##name); \ + \ + ret = snd_pcm_hw_constraint_single(substream->runtime, \ + SNDRV_PCM_HW_PARAM_##NAME, \ + dai->symmetric_##name); \ + if (ret < 0) \ + return snd_soc_ret(dai->dev, ret, \ + "Unable to apply %s constraint\n", #name); \ + } + + __symmetric_apply(rate, RATE); + __symmetric_apply(channels, CHANNELS); + __symmetric_apply(sample_bits, SAMPLE_BITS); + + return 0; +} + +int snd_soc_dai_symmetric_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 d; + struct snd_soc_dai *dai; + struct snd_soc_dai *cpu_dai; + unsigned int symmetry, i; + + d.name = __func__; + snd_soc_dai_symmetric_set_params(&d, params); + +#define __symmetric_params(xxx) \ + symmetry = rtd->dai_link->symmetric_##xxx; \ + for_each_rtd_dais(rtd, i, dai) \ + symmetry |= dai->driver->symmetric_##xxx; \ + \ + if (symmetry) \ + for_each_rtd_cpu_dais(rtd, i, cpu_dai) \ + if (!snd_soc_dai_is_dummy(cpu_dai) && \ + cpu_dai->symmetric_##xxx && \ + cpu_dai->symmetric_##xxx != d.symmetric_##xxx) \ + return snd_soc_ret(rtd->dev, -EINVAL, \ + "unmatched %s symmetry: %s:%d - %s:%d\n", \ + #xxx, cpu_dai->name, cpu_dai->symmetric_##xxx, \ + d.name, d.symmetric_##xxx); + + /* reject unmatched parameters when applying symmetry */ + __symmetric_params(rate); + __symmetric_params(channels); + __symmetric_params(sample_bits); + + return 0; +} + +void snd_soc_dai_symmetric_update(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai_link *link = rtd->dai_link; + struct snd_soc_dai *dai; + unsigned int symmetry, i; + + symmetry = link->symmetric_rate || + link->symmetric_channels || + link->symmetric_sample_bits; + + for_each_rtd_dais(rtd, i, dai) + symmetry = symmetry || + dai->driver->symmetric_rate || + dai->driver->symmetric_channels || + dai->driver->symmetric_sample_bits; + + if (symmetry) + substream->runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; +} diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index ce68402bde52..3afc50d5760e 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -29,5 +29,8 @@ int snd_soc_add_controls(struct snd_card *card, struct device *dev, */ void snd_soc_dai_symmetric_set_params(struct snd_soc_dai *dai, struct snd_pcm_hw_params *params); +int snd_soc_dai_symmetric_apply(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); +int snd_soc_dai_symmetric_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); +void snd_soc_dai_symmetric_update(struct snd_pcm_substream *substream); #endif /* __SOC_INTERNAL_H */ diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 04d7079c338f..a446e9c1c988 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -428,36 +428,6 @@ void dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, int event) snd_soc_dapm_stream_event(fe, dir, event); } -static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream, - struct snd_soc_dai *soc_dai) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - int ret; - - if (!snd_soc_dai_active(soc_dai)) - return 0; - -#define __soc_pcm_apply_symmetry(name, NAME) \ - if (soc_dai->symmetric_##name && \ - (soc_dai->driver->symmetric_##name || rtd->dai_link->symmetric_##name)) { \ - dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %s to %d\n",\ - #name, soc_dai->symmetric_##name); \ - \ - ret = snd_pcm_hw_constraint_single(substream->runtime, \ - SNDRV_PCM_HW_PARAM_##NAME,\ - soc_dai->symmetric_##name); \ - if (ret < 0) \ - return snd_soc_ret(soc_dai->dev, ret, \ - "Unable to apply %s constraint\n", #name); \ - } - - __soc_pcm_apply_symmetry(rate, RATE); - __soc_pcm_apply_symmetry(channels, CHANNELS); - __soc_pcm_apply_symmetry(sample_bits, SAMPLE_BITS); - - return 0; -} - /* * Shared BCLK constraint: when multiple DAIs share the same physical BCLK, * constrain hw_params so that the BCLK rate (rate * channels * sample_bits, @@ -566,62 +536,6 @@ static int soc_pcm_apply_shared_bclk(struct snd_pcm_substream *substream, -1); } -static int soc_pcm_params_symmetry(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 d; - struct snd_soc_dai *dai; - struct snd_soc_dai *cpu_dai; - unsigned int symmetry, i; - - d.name = __func__; - snd_soc_dai_symmetric_set_params(&d, params); - -#define __soc_pcm_params_symmetry(xxx) \ - symmetry = rtd->dai_link->symmetric_##xxx; \ - for_each_rtd_dais(rtd, i, dai) \ - symmetry |= dai->driver->symmetric_##xxx; \ - \ - if (symmetry) \ - for_each_rtd_cpu_dais(rtd, i, cpu_dai) \ - if (!snd_soc_dai_is_dummy(cpu_dai) && \ - cpu_dai->symmetric_##xxx && \ - cpu_dai->symmetric_##xxx != d.symmetric_##xxx) \ - return snd_soc_ret(rtd->dev, -EINVAL, \ - "unmatched %s symmetry: %s:%d - %s:%d\n", \ - #xxx, cpu_dai->name, cpu_dai->symmetric_##xxx, \ - d.name, d.symmetric_##xxx); - - /* reject unmatched parameters when applying symmetry */ - __soc_pcm_params_symmetry(rate); - __soc_pcm_params_symmetry(channels); - __soc_pcm_params_symmetry(sample_bits); - - return 0; -} - -static void soc_pcm_update_symmetry(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - struct snd_soc_dai_link *link = rtd->dai_link; - struct snd_soc_dai *dai; - unsigned int symmetry, i; - - symmetry = link->symmetric_rate || - link->symmetric_channels || - link->symmetric_sample_bits; - - for_each_rtd_dais(rtd, i, dai) - symmetry = symmetry || - dai->driver->symmetric_rate || - dai->driver->symmetric_channels || - dai->driver->symmetric_sample_bits; - - if (symmetry) - substream->runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; -} - static void soc_pcm_set_msb(struct snd_pcm_substream *substream, int bits) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); @@ -988,7 +902,7 @@ static int __soc_pcm_open(struct snd_soc_pcm_runtime *rtd, /* Check that the codec and cpu DAIs are compatible */ soc_pcm_init_runtime_hw(substream); - soc_pcm_update_symmetry(substream); + snd_soc_dai_symmetric_update(substream); ret = soc_hw_sanity_check(substream); if (ret < 0) @@ -998,7 +912,7 @@ static int __soc_pcm_open(struct snd_soc_pcm_runtime *rtd, /* Symmetry only applies if we've already got an active stream. */ for_each_rtd_dais(rtd, i, dai) { - ret = soc_pcm_apply_symmetry(substream, dai); + ret = snd_soc_dai_symmetric_apply(substream, dai); if (ret != 0) goto err; } @@ -1184,7 +1098,7 @@ static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, snd_soc_dpcm_mutex_assert_held(rtd); - ret = soc_pcm_params_symmetry(substream, params); + ret = snd_soc_dai_symmetric_params(substream, params); if (ret) goto out; @@ -2011,11 +1925,11 @@ static int dpcm_apply_symmetry(struct snd_pcm_substream *fe_substream, int i; /* apply symmetry for FE */ - soc_pcm_update_symmetry(fe_substream); + snd_soc_dai_symmetric_update(fe_substream); for_each_rtd_cpu_dais (fe, i, fe_cpu_dai) { /* Symmetry only applies if we've got an active stream. */ - err = soc_pcm_apply_symmetry(fe_substream, fe_cpu_dai); + err = snd_soc_dai_symmetric_apply(fe_substream, fe_cpu_dai); if (err < 0) goto error; } @@ -2036,11 +1950,11 @@ static int dpcm_apply_symmetry(struct snd_pcm_substream *fe_substream, if (rtd->dai_link->be_hw_params_fixup) continue; - soc_pcm_update_symmetry(be_substream); + snd_soc_dai_symmetric_update(be_substream); /* Symmetry only applies if we've got an active stream. */ for_each_rtd_dais(rtd, i, dai) { - err = soc_pcm_apply_symmetry(fe_substream, dai); + err = snd_soc_dai_symmetric_apply(fe_substream, dai); if (err < 0) goto error; } From 4df2e9cf77edd705440a91714c73ec6a77accb6a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:35 +0000 Subject: [PATCH 092/924] ASoC: soc-dai: move snd_soc_dai_get_pcm_stream() to soc-dai.c DAI parameter will be capsuled soon, will be not enable to access from soc-dai.h. Move it to soc-dai.c It renames function name snd_soc_dai_get_pcm_stream() snd_soc_dai_pcm_stream_get() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87pkyyssk4.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 13 ++++++++++--- sound/soc/soc-dai.c | 8 ++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index ea5182344c0d..e52ca6e289cb 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -494,12 +494,16 @@ struct snd_soc_dai { void *priv; }; +const struct snd_soc_pcm_stream * +snd_soc_dai_pcm_stream_get_i(const struct snd_soc_dai *dai, int stream); static inline const struct snd_soc_pcm_stream * -snd_soc_dai_get_pcm_stream(const struct snd_soc_dai *dai, int stream) +snd_soc_dai_pcm_stream_get_s(const struct snd_soc_dai *dai, struct snd_pcm_substream *substream) { - return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? - &dai->driver->playback : &dai->driver->capture; + return snd_soc_dai_pcm_stream_get_i(dai, substream->stream); } +#define snd_soc_dai_pcm_stream_get(dai, x) _Generic((x), \ + int : snd_soc_dai_pcm_stream_get_i, \ + struct snd_pcm_substream * : snd_soc_dai_pcm_stream_get_s)(dai, x) #define snd_soc_dai_get_widget_playback(dai) snd_soc_dai_get_widget(dai, SNDRV_PCM_STREAM_PLAYBACK) #define snd_soc_dai_get_widget_capture(dai) snd_soc_dai_get_widget(dai, SNDRV_PCM_STREAM_CAPTURE) @@ -615,4 +619,7 @@ struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, bool legacy_dai_naming); void snd_soc_dai_unregister(struct snd_soc_dai *dai); +/* REMOVE ME */ +#define snd_soc_dai_get_pcm_stream snd_soc_dai_pcm_stream_get + #endif diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 73ef977d23cc..de3cfdcf2c84 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1073,6 +1073,14 @@ const char *snd_soc_dai_name(const struct snd_soc_dai *dai) } EXPORT_SYMBOL_GPL(snd_soc_dai_name); +const struct snd_soc_pcm_stream * +snd_soc_dai_pcm_stream_get_i(const struct snd_soc_dai *dai, int stream) +{ + return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? + &dai->driver->playback : &dai->driver->capture; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_pcm_stream_get_i); + void snd_soc_dai_unregister(struct snd_soc_dai *dai) { lockdep_assert_held(&client_mutex); From a1b4963762c8d051e89d5c495838cdf04008ccea Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:39 +0000 Subject: [PATCH 093/924] ASoC: soc-dai: move snd_soc_dai_{get/set}_widget() to soc-dai.c DAI parameter will be capsuled soon, will be not enable to access from soc-dai.h. Move it to soc-dai.c It renames function name snd_soc_dai_get_widget() -> snd_soc_dai_stream_widget_get() snd_soc_dai_set_widget() -> snd_soc_dai_stream_widget_set() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87o6eissk1.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 26 ++++++++++++-------------- sound/soc/soc-dai.c | 12 ++++++++++++ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index e52ca6e289cb..232f43ff71f0 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -505,21 +505,13 @@ snd_soc_dai_pcm_stream_get_s(const struct snd_soc_dai *dai, struct snd_pcm_subst int : snd_soc_dai_pcm_stream_get_i, \ struct snd_pcm_substream * : snd_soc_dai_pcm_stream_get_s)(dai, x) -#define snd_soc_dai_get_widget_playback(dai) snd_soc_dai_get_widget(dai, SNDRV_PCM_STREAM_PLAYBACK) -#define snd_soc_dai_get_widget_capture(dai) snd_soc_dai_get_widget(dai, SNDRV_PCM_STREAM_CAPTURE) -static inline -struct snd_soc_dapm_widget *snd_soc_dai_get_widget(struct snd_soc_dai *dai, int stream) -{ - return dai->stream[stream].widget; -} +#define snd_soc_dai_stream_widget_get_playback(dai) snd_soc_dai_stream_widget_get(dai, SNDRV_PCM_STREAM_PLAYBACK) +#define snd_soc_dai_stream_widget_get_capture(dai) snd_soc_dai_stream_widget_get(dai, SNDRV_PCM_STREAM_CAPTURE) +struct snd_soc_dapm_widget *snd_soc_dai_stream_widget_get(struct snd_soc_dai *dai, int stream); -#define snd_soc_dai_set_widget_playback(dai, widget) snd_soc_dai_set_widget(dai, SNDRV_PCM_STREAM_PLAYBACK, widget) -#define snd_soc_dai_set_widget_capture(dai, widget) snd_soc_dai_set_widget(dai, SNDRV_PCM_STREAM_CAPTURE, widget) -static inline -void snd_soc_dai_set_widget(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_widget *widget) -{ - dai->stream[stream].widget = widget; -} +#define snd_soc_dai_stream_widget_set_playback(dai, widget) snd_soc_dai_stream_widget_set(dai, SNDRV_PCM_STREAM_PLAYBACK, widget) +#define snd_soc_dai_stream_widget_set_capture(dai, widget) snd_soc_dai_stream_widget_set(dai, SNDRV_PCM_STREAM_CAPTURE, widget) +void snd_soc_dai_stream_widget_set(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_widget *widget); #define snd_soc_dai_dma_data_get_playback(dai) snd_soc_dai_dma_data_get(dai, SNDRV_PCM_STREAM_PLAYBACK) #define snd_soc_dai_dma_data_get_capture(dai) snd_soc_dai_dma_data_get(dai, SNDRV_PCM_STREAM_CAPTURE) @@ -621,5 +613,11 @@ void snd_soc_dai_unregister(struct snd_soc_dai *dai); /* REMOVE ME */ #define snd_soc_dai_get_pcm_stream snd_soc_dai_pcm_stream_get +#define snd_soc_dai_get_widget_playback(dai) snd_soc_dai_stream_widget_get(dai, SNDRV_PCM_STREAM_PLAYBACK) +#define snd_soc_dai_get_widget_capture(dai) snd_soc_dai_stream_widget_get(dai, SNDRV_PCM_STREAM_CAPTURE) +#define snd_soc_dai_get_widget snd_soc_dai_stream_widget_get +#define snd_soc_dai_set_widget_playback(dai, widget) snd_soc_dai_stream_widget_set(dai, SNDRV_PCM_STREAM_PLAYBACK, widget) +#define snd_soc_dai_set_widget_capture(dai, widget) snd_soc_dai_stream_widget_set(dai, SNDRV_PCM_STREAM_CAPTURE, widget) +#define snd_soc_dai_set_widget snd_soc_dai_stream_widget_set #endif diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index de3cfdcf2c84..54d8e0eea7a1 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1081,6 +1081,18 @@ snd_soc_dai_pcm_stream_get_i(const struct snd_soc_dai *dai, int stream) } EXPORT_SYMBOL_GPL(snd_soc_dai_pcm_stream_get_i); +struct snd_soc_dapm_widget *snd_soc_dai_stream_widget_get(struct snd_soc_dai *dai, int stream) +{ + return dai->stream[stream].widget; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_widget_get); + +void snd_soc_dai_stream_widget_set(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_widget *widget) +{ + dai->stream[stream].widget = widget; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_widget_set); + void snd_soc_dai_unregister(struct snd_soc_dai *dai) { lockdep_assert_held(&client_mutex); From ff1e6b54805fd84e7f904f210ae3ef9449a5d672 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:42 +0000 Subject: [PATCH 094/924] ASoC: soc-dai: move snd_soc_dai_dma_data_{get/set}() to soc-dai.c DAI parameter will be capsuled soon, will be not enable to access from soc-dai.h. Move it to soc-dai.c These macros are very similar. Will be handled by same macro snd_soc_dai_dma_data_get() -> snd_soc_dai_stream_dma_data_get() snd_soc_dai_dma_data_set() -> snd_soc_dai_stream_dma_data_set() snd_soc_dai_get_dma_data() -> snd_soc_dai_stream_dma_data_get() snd_soc_dai_set_dma_data() -> snd_soc_dai_stream_dma_data_set() This macro is not much helps code, will be removed snd_soc_dai_init_dma_data() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87mru2ssjx.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 45 ++++++++++++++++++++++++++--------------- sound/soc/soc-dai.c | 12 +++++++++++ 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 232f43ff71f0..c75b41b773ba 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -513,27 +513,27 @@ struct snd_soc_dapm_widget *snd_soc_dai_stream_widget_get(struct snd_soc_dai *da #define snd_soc_dai_stream_widget_set_capture(dai, widget) snd_soc_dai_stream_widget_set(dai, SNDRV_PCM_STREAM_CAPTURE, widget) void snd_soc_dai_stream_widget_set(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_widget *widget); -#define snd_soc_dai_dma_data_get_playback(dai) snd_soc_dai_dma_data_get(dai, SNDRV_PCM_STREAM_PLAYBACK) -#define snd_soc_dai_dma_data_get_capture(dai) snd_soc_dai_dma_data_get(dai, SNDRV_PCM_STREAM_CAPTURE) -#define snd_soc_dai_get_dma_data(dai, ss) snd_soc_dai_dma_data_get(dai, ss->stream) -static inline void *snd_soc_dai_dma_data_get(const struct snd_soc_dai *dai, int stream) +#define snd_soc_dai_stream_dma_data_get_playback(dai) snd_soc_dai_stream_dma_data_get(dai, SNDRV_PCM_STREAM_PLAYBACK) +#define snd_soc_dai_stream_dma_data_get_capture(dai) snd_soc_dai_stream_dma_data_get(dai, SNDRV_PCM_STREAM_CAPTURE) +void *snd_soc_dai_stream_dma_data_get_i(const struct snd_soc_dai *dai, int stream); +static inline void *snd_soc_dai_stream_dma_data_get_s(const struct snd_soc_dai *dai, struct snd_pcm_substream *substream) { - return dai->stream[stream].dma_data; + return snd_soc_dai_stream_dma_data_get_i(dai, substream->stream); } +#define snd_soc_dai_stream_dma_data_get(dai, x) _Generic((x), \ + int : snd_soc_dai_stream_dma_data_get_i, \ + struct snd_pcm_substream * : snd_soc_dai_stream_dma_data_get_s)(dai, x) -#define snd_soc_dai_dma_data_set_playback(dai, data) snd_soc_dai_dma_data_set(dai, SNDRV_PCM_STREAM_PLAYBACK, data) -#define snd_soc_dai_dma_data_set_capture(dai, data) snd_soc_dai_dma_data_set(dai, SNDRV_PCM_STREAM_CAPTURE, data) -#define snd_soc_dai_set_dma_data(dai, ss, data) snd_soc_dai_dma_data_set(dai, ss->stream, data) -static inline void snd_soc_dai_dma_data_set(struct snd_soc_dai *dai, int stream, void *data) +#define snd_soc_dai_stream_dma_data_set_playback(dai, data) snd_soc_dai_stream_dma_data_set(dai, SNDRV_PCM_STREAM_PLAYBACK, data) +#define snd_soc_dai_stream_dma_data_set_capture(dai, data) snd_soc_dai_stream_dma_data_set(dai, SNDRV_PCM_STREAM_CAPTURE, data) +void snd_soc_dai_stream_dma_data_set_i(struct snd_soc_dai *dai, int stream, void *data); +static inline void snd_soc_dai_stream_dma_data_set_s(struct snd_soc_dai *dai, struct snd_pcm_substream *substream, void *data) { - dai->stream[stream].dma_data = data; -} - -static inline void snd_soc_dai_init_dma_data(struct snd_soc_dai *dai, void *playback, void *capture) -{ - snd_soc_dai_dma_data_set_playback(dai, playback); - snd_soc_dai_dma_data_set_capture(dai, capture); + snd_soc_dai_stream_dma_data_set_i(dai, substream->stream, data); } +#define snd_soc_dai_stream_dma_data_set(dai, x, data) _Generic((x), \ + int : snd_soc_dai_stream_dma_data_set_i, \ + struct snd_pcm_substream * : snd_soc_dai_stream_dma_data_set_s)(dai, x, data) static inline unsigned int snd_soc_dai_tdm_mask_get(const struct snd_soc_dai *dai, int stream) @@ -619,5 +619,18 @@ void snd_soc_dai_unregister(struct snd_soc_dai *dai); #define snd_soc_dai_set_widget_playback(dai, widget) snd_soc_dai_stream_widget_set(dai, SNDRV_PCM_STREAM_PLAYBACK, widget) #define snd_soc_dai_set_widget_capture(dai, widget) snd_soc_dai_stream_widget_set(dai, SNDRV_PCM_STREAM_CAPTURE, widget) #define snd_soc_dai_set_widget snd_soc_dai_stream_widget_set +#define snd_soc_dai_dma_data_get_playback(dai) snd_soc_dai_stream_dma_data_get(dai, SNDRV_PCM_STREAM_PLAYBACK) +#define snd_soc_dai_dma_data_get_capture(dai) snd_soc_dai_stream_dma_data_get(dai, SNDRV_PCM_STREAM_CAPTURE) +#define snd_soc_dai_get_dma_data snd_soc_dai_stream_dma_data_get +#define snd_soc_dai_dma_data_get snd_soc_dai_stream_dma_data_get +#define snd_soc_dai_dma_data_set_playback(dai, data) snd_soc_dai_stream_dma_data_set(dai, SNDRV_PCM_STREAM_PLAYBACK, data) +#define snd_soc_dai_dma_data_set_capture(dai, data) snd_soc_dai_stream_dma_data_set(dai, SNDRV_PCM_STREAM_CAPTURE, data) +#define snd_soc_dai_set_dma_data snd_soc_dai_stream_dma_data_set +#define snd_soc_dai_dma_data_set snd_soc_dai_stream_dma_data_set +static inline void snd_soc_dai_init_dma_data(struct snd_soc_dai *dai, void *playback, void *capture) +{ + snd_soc_dai_stream_dma_data_set_playback(dai, playback); + snd_soc_dai_stream_dma_data_set_capture(dai, capture); +} #endif diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 54d8e0eea7a1..8e3d8e056a77 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1093,6 +1093,18 @@ void snd_soc_dai_stream_widget_set(struct snd_soc_dai *dai, int stream, struct s } EXPORT_SYMBOL_GPL(snd_soc_dai_stream_widget_set); +void *snd_soc_dai_stream_dma_data_get_i(const struct snd_soc_dai *dai, int stream) +{ + return dai->stream[stream].dma_data; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_dma_data_get_i); + +void snd_soc_dai_stream_dma_data_set_i(struct snd_soc_dai *dai, int stream, void *data) +{ + dai->stream[stream].dma_data = data; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_dma_data_set_i); + void snd_soc_dai_unregister(struct snd_soc_dai *dai) { lockdep_assert_held(&client_mutex); From b006e4d060b2f92400fc91ecd6c2bc365ba42b3a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:47 +0000 Subject: [PATCH 095/924] ASoC: soc-dai: move snd_soc_dai_tdm_mask_{get/set}() to soc-dai.c DAI parameter will be capsuled soon, will be not enable to access from soc-dai.h. Move it to soc-dai.c It renames function name snd_soc_dai_tdm_mask_get() -> snd_soc_dai_stream_tdm_mask_get() snd_soc_dai_tdm_mask_set() -> snd_soc_dai_stream_tdm_mask_set() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ld9mssjt.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 15 ++++----------- sound/soc/soc-dai.c | 12 ++++++++++++ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index c75b41b773ba..b50368ca6cbf 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -535,17 +535,8 @@ static inline void snd_soc_dai_stream_dma_data_set_s(struct snd_soc_dai *dai, st int : snd_soc_dai_stream_dma_data_set_i, \ struct snd_pcm_substream * : snd_soc_dai_stream_dma_data_set_s)(dai, x, data) -static inline unsigned int snd_soc_dai_tdm_mask_get(const struct snd_soc_dai *dai, - int stream) -{ - return dai->stream[stream].tdm_mask; -} - -static inline void snd_soc_dai_tdm_mask_set(struct snd_soc_dai *dai, int stream, - unsigned int tdm_mask) -{ - dai->stream[stream].tdm_mask = tdm_mask; -} +unsigned int snd_soc_dai_stream_tdm_mask_get(const struct snd_soc_dai *dai, int stream); +void snd_soc_dai_stream_tdm_mask_set(struct snd_soc_dai *dai, int stream, unsigned int tdm_mask); static inline unsigned int snd_soc_dai_stream_active(const struct snd_soc_dai *dai, int stream) @@ -627,6 +618,8 @@ void snd_soc_dai_unregister(struct snd_soc_dai *dai); #define snd_soc_dai_dma_data_set_capture(dai, data) snd_soc_dai_stream_dma_data_set(dai, SNDRV_PCM_STREAM_CAPTURE, data) #define snd_soc_dai_set_dma_data snd_soc_dai_stream_dma_data_set #define snd_soc_dai_dma_data_set snd_soc_dai_stream_dma_data_set +#define snd_soc_dai_tdm_mask_get snd_soc_dai_stream_tdm_mask_get +#define snd_soc_dai_tdm_mask_set snd_soc_dai_stream_tdm_mask_set static inline void snd_soc_dai_init_dma_data(struct snd_soc_dai *dai, void *playback, void *capture) { snd_soc_dai_stream_dma_data_set_playback(dai, playback); diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 8e3d8e056a77..125f2103a907 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1105,6 +1105,18 @@ void snd_soc_dai_stream_dma_data_set_i(struct snd_soc_dai *dai, int stream, void } EXPORT_SYMBOL_GPL(snd_soc_dai_stream_dma_data_set_i); +unsigned int snd_soc_dai_stream_tdm_mask_get(const struct snd_soc_dai *dai, int stream) +{ + return dai->stream[stream].tdm_mask; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_tdm_mask_get); + +void snd_soc_dai_stream_tdm_mask_set(struct snd_soc_dai *dai, int stream, unsigned int tdm_mask) +{ + dai->stream[stream].tdm_mask = tdm_mask; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_tdm_mask_set); + void snd_soc_dai_unregister(struct snd_soc_dai *dai) { lockdep_assert_held(&client_mutex); From 5ffa1b3d7f0450ce830d9a438efeea55b551db25 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:51 +0000 Subject: [PATCH 096/924] ASoC: soc-dai: move snd_soc_dai_stream_active() to soc-dai.c DAI parameter will be capsuled soon, will be not enable to access from soc-dai.h. Move it to soc-dai.c Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87jyp6ssjo.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 7 +------ sound/soc/soc-dai.c | 7 +++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index b50368ca6cbf..e7d86c97da75 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -538,12 +538,7 @@ static inline void snd_soc_dai_stream_dma_data_set_s(struct snd_soc_dai *dai, st unsigned int snd_soc_dai_stream_tdm_mask_get(const struct snd_soc_dai *dai, int stream); void snd_soc_dai_stream_tdm_mask_set(struct snd_soc_dai *dai, int stream, unsigned int tdm_mask); -static inline unsigned int snd_soc_dai_stream_active(const struct snd_soc_dai *dai, - int stream) -{ - /* see snd_soc_dai_action() for setup */ - return dai->stream[stream].active; -} +unsigned int snd_soc_dai_stream_active(const struct snd_soc_dai *dai, int stream); static inline void snd_soc_dai_set_drvdata(struct snd_soc_dai *dai, void *data) diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 125f2103a907..77fd457a94f6 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1117,6 +1117,13 @@ void snd_soc_dai_stream_tdm_mask_set(struct snd_soc_dai *dai, int stream, unsign } EXPORT_SYMBOL_GPL(snd_soc_dai_stream_tdm_mask_set); +unsigned int snd_soc_dai_stream_active(const struct snd_soc_dai *dai, int stream) +{ + /* see snd_soc_dai_action() for setup */ + return dai->stream[stream].active; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_active); + void snd_soc_dai_unregister(struct snd_soc_dai *dai) { lockdep_assert_held(&client_mutex); From d9ed2b5a7b2a6195557158db953e4bce39d029b5 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:55 +0000 Subject: [PATCH 097/924] ASoC: soc-dai: move snd_soc_dai_{set/get}_stream() to soc-dai.c DAI parameter will be capsuled soon, will be not enable to access from soc-dai.h. Move it to soc-dai.c Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ik4qssjl.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 42 ++--------------------------------------- sound/soc/soc-dai.c | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index e7d86c97da75..7e64e049d71d 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -551,46 +551,8 @@ static inline void *snd_soc_dai_get_drvdata(struct snd_soc_dai *dai) return dev_get_drvdata(dai->dev); } -/** - * snd_soc_dai_set_stream() - Configures a DAI for stream operation - * @dai: DAI - * @stream: STREAM (opaque structure depending on DAI type) - * @direction: Stream direction(Playback/Capture) - * Some subsystems, such as SoundWire, don't have a notion of direction and we reuse - * the ASoC stream direction to configure sink/source ports. - * Playback maps to source ports and Capture for sink ports. - * - * This should be invoked with NULL to clear the stream set previously. - * Returns 0 on success, a negative error code otherwise. - */ -static inline int snd_soc_dai_set_stream(struct snd_soc_dai *dai, - void *stream, int direction) -{ - if (dai->driver->ops->set_stream) - return dai->driver->ops->set_stream(dai, stream, direction); - else - return -ENOTSUPP; -} - -/** - * snd_soc_dai_get_stream() - Retrieves stream from DAI - * @dai: DAI - * @direction: Stream direction(Playback/Capture) - * - * This routine only retrieves that was previously configured - * with snd_soc_dai_get_stream() - * - * Returns pointer to stream or an ERR_PTR value, e.g. - * ERR_PTR(-ENOTSUPP) if callback is not supported; - */ -static inline void *snd_soc_dai_get_stream(struct snd_soc_dai *dai, - int direction) -{ - if (dai->driver->ops->get_stream) - return dai->driver->ops->get_stream(dai, direction); - else - return ERR_PTR(-ENOTSUPP); -} +int snd_soc_dai_set_stream(struct snd_soc_dai *dai, void *stream, int direction); +void *snd_soc_dai_get_stream(struct snd_soc_dai *dai, int direction); struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 77fd457a94f6..83d030e14bb0 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1124,6 +1124,47 @@ unsigned int snd_soc_dai_stream_active(const struct snd_soc_dai *dai, int stream } EXPORT_SYMBOL_GPL(snd_soc_dai_stream_active); +/** + * snd_soc_dai_set_stream() - Configures a DAI for stream operation + * @dai: DAI + * @stream: STREAM (opaque structure depending on DAI type) + * @direction: Stream direction(Playback/Capture) + * Some subsystems, such as SoundWire, don't have a notion of direction and we reuse + * the ASoC stream direction to configure sink/source ports. + * Playback maps to source ports and Capture for sink ports. + * + * This should be invoked with NULL to clear the stream set previously. + * Returns 0 on success, a negative error code otherwise. + */ +int snd_soc_dai_set_stream(struct snd_soc_dai *dai, void *stream, int direction) +{ + if (dai->driver->ops->set_stream) + return dai->driver->ops->set_stream(dai, stream, direction); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_stream); + +/** + * snd_soc_dai_get_stream() - Retrieves stream from DAI + * @dai: DAI + * @direction: Stream direction(Playback/Capture) + * + * This routine only retrieves that was previously configured + * with snd_soc_dai_get_stream() + * + * Returns pointer to stream or an ERR_PTR value, e.g. + * ERR_PTR(-ENOTSUPP) if callback is not supported; + */ +void *snd_soc_dai_get_stream(struct snd_soc_dai *dai, int direction) +{ + if (dai->driver->ops->get_stream) + return dai->driver->ops->get_stream(dai, direction); + else + return ERR_PTR(-ENOTSUPP); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_stream); + void snd_soc_dai_unregister(struct snd_soc_dai *dai) { lockdep_assert_held(&client_mutex); From 04c3ee0f5d158f6e2027c7ef617044cb649ab4ab Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Wed, 9 Sep 2026 17:18:06 +0800 Subject: [PATCH 098/924] ASoC: rt1320: avoid re-initialize in advanced mode On some machines, speaker protection is enabled. We call this advanced mode, which loads the DSP firmware and changes settings based on the basic mode, including blind writes. The amplifier can retain the advanced mode settings until a POR or power loss occurs. Therefore, the driver does not need to switch from basic mode to advanced mode when the system performs a warm reboot. We only need to check the function_status to determine whether the system has undergone a cold or warm reboot. Fixes: 9b30521074f0 ("ASoC: rt1320: run the initialisation preset on the first hardware init") Signed-off-by: Shuming Fan Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260909091806.872977-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1320-sdw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index 90333779971d..8d2d6697a502 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -2470,7 +2470,7 @@ static int rt1320_io_init(struct device *dev, struct sdw_slave *slave) dev_dbg(dev, "%s amp func_status=0x%x\n", __func__, amp_func_status); /* initialization write */ - if ((amp_func_status & FUNCTION_NEEDS_INITIALIZATION) || !rt1320->first_hw_init) { + if ((amp_func_status & FUNCTION_NEEDS_INITIALIZATION)) { switch (rt1320->dev_id) { case RT1320_DEV_ID: if (rt1320->version_id < RT1320_VC) From e6e5b688f2aa5c899fe6d9d89eaffb4bd8d82e51 Mon Sep 17 00:00:00 2001 From: Andrew Udvare Date: Wed, 9 Sep 2026 15:31:04 -0400 Subject: [PATCH 099/924] ALSA: hda/realtek: Fix speaker dropouts on MSI GE66 Raider 11UE The speakers on this laptop are driven by a Realtek ALC1316 smart amplifier sitting behind the ALC298 processing-coefficient tunnel. Nothing configures it, so it runs on its power-up defaults with silence detection enabled: the speaker path is muted after roughly 0.2 s of quiet and takes roughly 0.4 s to reopen. Anything arriving in that window is lost, so the beginning of every sound that follows a pause is cut off. It is most obvious with speech synthesis, where the first syllable of each utterance disappears, but it affects music equally. Headphones do not pass through the amplifier and are never affected. Measured on the affected machine with generated tones: isolated 150 ms bursts spaced one second apart are inaudible, the same bursts spaced 100 ms apart are heard, and a continuous tone loses only its first 0.4 s. Add a fixup that programs the amplifier's initialisation sequence at codec init, which turns silence detection off. Writes go through the tunnel with coefficient 0x22 selecting the amplifier, 0x23 carrying the 16-bit register, 0x24 and 0x25 the value's high and low halves, and 0x26 committing. Link: https://github.com/thesofproject/sof/issues/5842 Signed-off-by: Andrew Udvare Link: https://patch.msgid.link/20260909193104.26508-1-audvare@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index ecc9c6e6ab98..8f55c3c00ccd 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -1840,6 +1840,48 @@ struct alc298_samsung_amp_desc { unsigned short init_seq[2][2]; }; +/* + * The MSI GE66 Raider 11UE drives its speakers through a Realtek ALC1316 smart + * amplifier, reached over the ALC298 coefficient tunnel. Left unconfigured the + * amplifier's silence detection mutes the speaker path after about 0.2 s of + * quiet and takes about 0.4 s to reopen, so the start of every sound that + * follows a pause is lost. Headphones do not pass through the amplifier and are + * unaffected. + * + * Tunnel layout for ALC286/ALC298: coefficient 0x22 selects the amplifier, 0x23 + * carries the 16-bit register, 0x24 and 0x25 the value's high and low halves, + * and 0x26 commits. Bit 4 of the commit word is a self-clearing go bit. + */ +static void alc298_fixup_alc1316_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const unsigned short init_seq[][2] = { + { 0x0000, 0x01 }, { 0xc001, 0x14 }, { 0xc003, 0x00 }, { 0xc004, 0x11 }, + { 0xc005, 0x00 }, { 0xc006, 0x00 }, { 0xc007, 0x11 }, { 0xc008, 0x11 }, + { 0xc009, 0x00 }, { 0xc730, 0x06 }, { 0xc612, 0x16 }, { 0xc00f, 0xf7 }, + { 0xc00a, 0xd8 }, { 0xc00b, 0xb7 }, { 0xc60b, 0xff }, { 0xc60c, 0x37 }, + { 0xc605, 0xf0 }, { 0xc020, 0x00 }, { 0xc022, 0xd6 }, { 0xc023, 0x00 }, + { 0xc025, 0xd6 }, { 0xc602, 0x07 }, { 0xc603, 0x07 }, { 0xd101, 0x00 }, + { 0xc093, 0x80 }, { 0xc090, 0x87 }, { 0xc091, 0x11 }, { 0xc614, 0x20 }, + { 0xc615, 0x0a }, { 0xc616, 0x02 }, { 0xc617, 0x00 }, { 0xc050, 0x80 }, + { 0xc051, 0x7f }, { 0xc052, 0x00 }, { 0xc053, 0x70 }, { 0xc054, 0x00 }, + { 0xc0a2, 0x01 }, { 0xc09d, 0x80 }, { 0xc09c, 0x71 } + }; + int i; + + if (action != HDA_FIXUP_ACT_INIT) + return; + + alc_write_coef_idx(codec, 0x22, 0x0010); + + for (i = 0; i < ARRAY_SIZE(init_seq); i++) { + alc_write_coef_idx(codec, 0x23, init_seq[i][0]); + alc_write_coef_idx(codec, 0x24, 0x0000); + alc_write_coef_idx(codec, 0x25, init_seq[i][1]); + alc_write_coef_idx(codec, 0x26, 0xb031); + } +} + static void alc298_fixup_samsung_amp(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -4211,6 +4253,7 @@ enum { ALC236_FIXUP_HP_15_FD0XXX, ALC236_FIXUP_LENOVO_INV_DMIC, ALC298_FIXUP_SAMSUNG_AMP, + ALC298_FIXUP_MSI_GE66_ALC1316_AMP, ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, ALC298_FIXUP_LG_GRAM_STYLE_14, @@ -6006,6 +6049,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc295_fixup_hp_pavilion_mute_led_1b, }, + [ALC298_FIXUP_MSI_GE66_ALC1316_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc298_fixup_alc1316_amp, + }, [ALC298_FIXUP_SAMSUNG_AMP] = { .type = HDA_FIXUP_FUNC, .v.func = alc298_fixup_samsung_amp, @@ -7933,6 +7980,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x144d, 0xc1cc, "Samsung Galaxy Book3 Ultra (NT960XFH)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), SND_PCI_QUIRK(0x1458, 0x900e, "Gigabyte G5 KF5 (2023)", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1462, 0x12fb, "MSI GE66 Raider 11UE", + ALC298_FIXUP_MSI_GE66_ALC1316_AMP), SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1462, 0xb171, "Cubi N 8GL (MS-B171)", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x152d, 0x1082, "Quanta NL3", ALC269_FIXUP_LIFEBOOK), @@ -8515,6 +8564,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {.id = ALC298_FIXUP_HUAWEI_MBX_STEREO, .name = "huawei-mbx-stereo"}, {.id = ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE, .name = "alc256-medion-headset"}, {.id = ALC298_FIXUP_SAMSUNG_AMP, .name = "alc298-samsung-amp"}, + {.id = ALC298_FIXUP_MSI_GE66_ALC1316_AMP, .name = "alc298-alc1316-amp"}, {.id = ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, .name = "alc298-samsung-amp-v2-2-amps"}, {.id = ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, .name = "alc298-samsung-amp-v2-4-amps"}, {.id = ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, .name = "alc256-samsung-headphone"}, From 527d09d74d903eb995c25f4377d371130b4c1b58 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:06:49 +0000 Subject: [PATCH 100/924] ASoC: codecs: 88pm860x: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87mru2vpxy.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/88pm860x-codec.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/soc/codecs/88pm860x-codec.c b/sound/soc/codecs/88pm860x-codec.c index b4f5b24cde45..7596227fbccf 100644 --- a/sound/soc/codecs/88pm860x-codec.c +++ b/sound/soc/codecs/88pm860x-codec.c @@ -1136,11 +1136,15 @@ static int pm860x_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 pm860x_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_I2S; + static const struct snd_soc_dai_ops pm860x_pcm_dai_ops = { .mute_stream = pm860x_mute_stream, .hw_params = pm860x_pcm_hw_params, .set_fmt = pm860x_pcm_set_dai_fmt, .set_sysclk = pm860x_set_dai_sysclk, + .auto_selectable_formats = &pm860x_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1149,6 +1153,8 @@ static const struct snd_soc_dai_ops pm860x_i2s_dai_ops = { .hw_params = pm860x_i2s_hw_params, .set_fmt = pm860x_i2s_set_dai_fmt, .set_sysclk = pm860x_set_dai_sysclk, + .auto_selectable_formats = &pm860x_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; From d39367addf93f334000d4eabe88684a8fbf909a9 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:06:54 +0000 Subject: [PATCH 101/924] ASoC: codecs: ab8500: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ld9mvpxt.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 2cf96cbdd294..e315638499b6 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -2079,9 +2079,22 @@ static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai, return 0; } +static const u64 ab8500_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_GATED | + SND_SOC_POSSIBLE_DAIFMT_CONT | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops ab8500_codec_ops = { .set_fmt = ab8500_codec_set_dai_fmt, .set_tdm_slot = ab8500_codec_set_dai_tdm_slot, + .auto_selectable_formats = &ab8500_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ab8500_codec_dai[] = { From efc0eb0908df6334bdf9861ce9412d453526ba8f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:06:59 +0000 Subject: [PATCH 102/924] ASoC: codecs: adau*: use .auto_selectable_formats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Acked-by: Nuno Sá Link: https://patch.msgid.link/87jyp6vpxo.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/adau1372.c | 12 ++++++++++++ sound/soc/codecs/adau1373.c | 12 ++++++++++++ sound/soc/codecs/adau1701.c | 11 +++++++++++ sound/soc/codecs/adau17x1.c | 13 +++++++++++++ sound/soc/codecs/adau1977.c | 13 +++++++++++++ sound/soc/codecs/adau7118.c | 12 ++++++++++++ 6 files changed, 73 insertions(+) diff --git a/sound/soc/codecs/adau1372.c b/sound/soc/codecs/adau1372.c index cc174ec3a1f8..0830d7d2dd7c 100644 --- a/sound/soc/codecs/adau1372.c +++ b/sound/soc/codecs/adau1372.c @@ -881,12 +881,24 @@ static const struct snd_soc_component_driver adau1372_driver = { .endianness = 1, }; +static const u64 adau1372_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops adau1372_dai_ops = { .set_fmt = adau1372_set_dai_fmt, .set_tdm_slot = adau1372_set_tdm_slot, .set_tristate = adau1372_set_tristate, .hw_params = adau1372_hw_params, .startup = adau1372_startup, + .auto_selectable_formats = &adau1372_selectable_formats, + .num_auto_selectable_formats = 1, }; #define ADAU1372_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ diff --git a/sound/soc/codecs/adau1373.c b/sound/soc/codecs/adau1373.c index 5dd961b233ce..aa7d3a4b9f58 100644 --- a/sound/soc/codecs/adau1373.c +++ b/sound/soc/codecs/adau1373.c @@ -1196,10 +1196,22 @@ static int adau1373_set_dai_sysclk(struct snd_soc_dai *dai, return 0; } +static const u64 adau1373_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops adau1373_dai_ops = { .hw_params = adau1373_hw_params, .set_sysclk = adau1373_set_dai_sysclk, .set_fmt = adau1373_set_dai_fmt, + .auto_selectable_formats = &adau1373_selectable_formats, + .num_auto_selectable_formats = 1, }; #define ADAU1373_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ diff --git a/sound/soc/codecs/adau1701.c b/sound/soc/codecs/adau1701.c index 329ab01b62c0..505d52c63627 100644 --- a/sound/soc/codecs/adau1701.c +++ b/sound/soc/codecs/adau1701.c @@ -627,11 +627,22 @@ static int adau1701_startup(struct snd_pcm_substream *substream, #define ADAU1701_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 adau1701_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops adau1701_dai_ops = { .set_fmt = adau1701_set_dai_fmt, .hw_params = adau1701_hw_params, .mute_stream = adau1701_mute_stream, .startup = adau1701_startup, + .auto_selectable_formats = &adau1701_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c index 26d7eb437ad1..45ac226b2f1b 100644 --- a/sound/soc/codecs/adau17x1.c +++ b/sound/soc/codecs/adau17x1.c @@ -776,6 +776,17 @@ static int adau17x1_startup(struct snd_pcm_substream *substream, return 0; } +static const u64 adau17x1_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + const struct snd_soc_dai_ops adau17x1_dai_ops = { .hw_params = adau17x1_hw_params, .set_sysclk = adau17x1_set_dai_sysclk, @@ -783,6 +794,8 @@ const struct snd_soc_dai_ops adau17x1_dai_ops = { .set_pll = adau17x1_set_dai_pll, .set_tdm_slot = adau17x1_set_dai_tdm_slot, .startup = adau17x1_startup, + .auto_selectable_formats = &adau17x1_selectable_formats, + .num_auto_selectable_formats = 1, }; EXPORT_SYMBOL_GPL(adau17x1_dai_ops); diff --git a/sound/soc/codecs/adau1977.c b/sound/soc/codecs/adau1977.c index b42f35b1a603..b9d985393421 100644 --- a/sound/soc/codecs/adau1977.c +++ b/sound/soc/codecs/adau1977.c @@ -741,6 +741,17 @@ static int adau1977_set_tristate(struct snd_soc_dai *dai, int tristate) ADAU1977_SAI_OVERTEMP_DRV_HIZ, val); } +static const u64 adau1977_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops adau1977_dai_ops = { .startup = adau1977_startup, .hw_params = adau1977_hw_params, @@ -748,6 +759,8 @@ static const struct snd_soc_dai_ops adau1977_dai_ops = { .set_fmt = adau1977_set_dai_fmt, .set_tdm_slot = adau1977_set_tdm_slot, .set_tristate = adau1977_set_tristate, + .auto_selectable_formats = &adau1977_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver adau1977_dai = { diff --git a/sound/soc/codecs/adau7118.c b/sound/soc/codecs/adau7118.c index 5ddea63a6612..9749299bc7f2 100644 --- a/sound/soc/codecs/adau7118.c +++ b/sound/soc/codecs/adau7118.c @@ -418,12 +418,24 @@ static int adau7118_component_probe(struct snd_soc_component *component) return ret; } +static const u64 adau7118_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops adau7118_ops = { .hw_params = adau7118_hw_params, .set_channel_map = adau7118_set_channel_map, .set_fmt = adau7118_set_fmt, .set_tdm_slot = adau7118_set_tdm_slot, .set_tristate = adau7118_set_tristate, + .auto_selectable_formats = &adau7118_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver adau7118_dai = { From 9e99d539694b9b0974ce4769a985577d6e8305d2 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:03 +0000 Subject: [PATCH 103/924] ASoC: codecs: ak4*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ik4qvpxk.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/ak4104.c | 7 +++++++ sound/soc/codecs/ak4118.c | 7 +++++++ sound/soc/codecs/ak4458.c | 9 +++++++++ sound/soc/codecs/ak4535.c | 6 ++++++ sound/soc/codecs/ak4642.c | 6 ++++++ sound/soc/codecs/ak4671.c | 7 +++++++ 6 files changed, 42 insertions(+) diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c index 6ea7cb78cd44..5ebc3afccccf 100644 --- a/sound/soc/codecs/ak4104.c +++ b/sound/soc/codecs/ak4104.c @@ -147,9 +147,16 @@ static int ak4104_hw_params(struct snd_pcm_substream *substream, return 0; } +static const u64 ak4101_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ak4101_dai_ops = { .hw_params = ak4104_hw_params, .set_fmt = ak4104_set_dai_fmt, + .auto_selectable_formats = &ak4101_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ak4104_dai = { diff --git a/sound/soc/codecs/ak4118.c b/sound/soc/codecs/ak4118.c index 03dadba0b038..2b96b82d4e40 100644 --- a/sound/soc/codecs/ak4118.c +++ b/sound/soc/codecs/ak4118.c @@ -236,9 +236,16 @@ static int ak4118_hw_params(struct snd_pcm_substream *substream, return 0; } +/* NOTE: Common formats only are auto detected */ +static const u64 ak4118_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ak4118_dai_ops = { .hw_params = ak4118_hw_params, .set_fmt = ak4118_set_dai_fmt, + .auto_selectable_formats = &ak4118_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ak4118_dai = { diff --git a/sound/soc/codecs/ak4458.c b/sound/soc/codecs/ak4458.c index f81cd8cebdd8..a74be99e877d 100644 --- a/sound/soc/codecs/ak4458.c +++ b/sound/soc/codecs/ak4458.c @@ -591,12 +591,21 @@ static int ak4458_startup(struct snd_pcm_substream *substream, &ak4458_rate_constraints); } +static const u64 ak4458_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_PDM; + static const struct snd_soc_dai_ops ak4458_dai_ops = { .startup = ak4458_startup, .hw_params = ak4458_hw_params, .set_fmt = ak4458_set_dai_fmt, .mute_stream = ak4458_set_dai_mute, .set_tdm_slot = ak4458_set_tdm_slot, + .auto_selectable_formats = &ak4458_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c index 37555674cd15..6cabad4c9f74 100644 --- a/sound/soc/codecs/ak4535.c +++ b/sound/soc/codecs/ak4535.c @@ -346,11 +346,17 @@ static int ak4535_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +static const u64 ak4535_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ak4535_dai_ops = { .hw_params = ak4535_hw_params, .set_fmt = ak4535_set_dai_fmt, .mute_stream = ak4535_mute, .set_sysclk = ak4535_set_dai_sysclk, + .auto_selectable_formats = &ak4535_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 08ec2035b270..e5266f94fb00 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -493,12 +493,18 @@ static int ak4642_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 ak4642_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ak4642_dai_ops = { .startup = ak4642_dai_startup, .shutdown = ak4642_dai_shutdown, .set_sysclk = ak4642_dai_set_sysclk, .set_fmt = ak4642_dai_set_fmt, .hw_params = ak4642_dai_hw_params, + .auto_selectable_formats = &ak4642_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ak4642_dai = { diff --git a/sound/soc/codecs/ak4671.c b/sound/soc/codecs/ak4671.c index aac54e9d54ee..1d804f03a21d 100644 --- a/sound/soc/codecs/ak4671.c +++ b/sound/soc/codecs/ak4671.c @@ -582,10 +582,17 @@ static int ak4671_set_bias_level(struct snd_soc_component *component, #define AK4671_FORMATS SNDRV_PCM_FMTBIT_S16_LE +static const u64 ak4671_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + static const struct snd_soc_dai_ops ak4671_dai_ops = { .hw_params = ak4671_hw_params, .set_sysclk = ak4671_set_dai_sysclk, .set_fmt = ak4671_set_dai_fmt, + .auto_selectable_formats = &ak4671_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ak4671_dai = { From dab547442de4a0a680245d00643f051690966154 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:07 +0000 Subject: [PATCH 104/924] ASoC: codecs: ak5*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87h5kavpxg.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/ak5386.c | 6 ++++++ sound/soc/codecs/ak5558.c | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/sound/soc/codecs/ak5386.c b/sound/soc/codecs/ak5386.c index 6525d50b7ab2..933ca90b7e28 100644 --- a/sound/soc/codecs/ak5386.c +++ b/sound/soc/codecs/ak5386.c @@ -128,10 +128,16 @@ static int ak5386_hw_free(struct snd_pcm_substream *substream, return 0; } +static const u64 ak5386_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ak5386_dai_ops = { .set_fmt = ak5386_set_dai_fmt, .hw_params = ak5386_hw_params, .hw_free = ak5386_hw_free, + .auto_selectable_formats = &ak5386_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ak5386_dai = { diff --git a/sound/soc/codecs/ak5558.c b/sound/soc/codecs/ak5558.c index 691db6565c20..0c9cdf9a53bb 100644 --- a/sound/soc/codecs/ak5558.c +++ b/sound/soc/codecs/ak5558.c @@ -285,12 +285,19 @@ static int ak5558_startup(struct snd_pcm_substream *substream, &ak5558_rate_constraints); } +static const u64 ak5558_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops ak5558_dai_ops = { .startup = ak5558_startup, .hw_params = ak5558_hw_params, .set_fmt = ak5558_set_dai_fmt, .set_tdm_slot = ak5558_set_tdm_slot, + .auto_selectable_formats = &ak5558_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ak5558_dai = { From ccb78092f0c9bedb18fce35e411decf86e36fe60 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:11 +0000 Subject: [PATCH 105/924] ASoC: codecs: alc56*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87fqzuvpxc.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/alc5623.c | 11 +++++++++++ sound/soc/codecs/alc5632.c | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/sound/soc/codecs/alc5623.c b/sound/soc/codecs/alc5623.c index f5a666c49361..96bb1d592772 100644 --- a/sound/soc/codecs/alc5623.c +++ b/sound/soc/codecs/alc5623.c @@ -822,12 +822,23 @@ static int alc5623_set_bias_level(struct snd_soc_component *component, | SNDRV_PCM_FMTBIT_S24_LE \ | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 alc5623_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops alc5623_dai_ops = { .hw_params = alc5623_pcm_hw_params, .mute_stream = alc5623_mute, .set_fmt = alc5623_set_dai_fmt, .set_sysclk = alc5623_set_dai_sysclk, .set_pll = alc5623_set_dai_pll, + .auto_selectable_formats = &alc5623_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/alc5632.c b/sound/soc/codecs/alc5632.c index 2fd3dd7ef8b3..a5e95f3fc247 100644 --- a/sound/soc/codecs/alc5632.c +++ b/sound/soc/codecs/alc5632.c @@ -1003,12 +1003,25 @@ static int alc5632_set_bias_level(struct snd_soc_component *component, | SNDRV_PCM_FMTBIT_S24_LE \ | SNDRV_PCM_FMTBIT_S32_LE) + +static const u64 alc5632_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops alc5632_dai_ops = { .hw_params = alc5632_pcm_hw_params, .mute_stream = alc5632_mute, .set_fmt = alc5632_set_dai_fmt, .set_sysclk = alc5632_set_dai_sysclk, .set_pll = alc5632_set_dai_pll, + .auto_selectable_formats = &alc5632_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; From 1a33b369a9ba6332bb5a86aa905c332e5f0fbe42 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:16 +0000 Subject: [PATCH 106/924] ASoC: codecs: arizona: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ecfevpx7.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/arizona.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index 4411165c3734..7c296c5b24f7 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -2016,6 +2016,27 @@ static int arizona_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, return 0; } +static const u64 arizona_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* + * 2nd priority + * + * _DSP_B / _LEFT_J can't be clock consumer (= arizona_set_fmt()). + */ + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, +}; + const struct snd_soc_dai_ops arizona_dai_ops = { .startup = arizona_startup, .set_fmt = arizona_set_fmt, @@ -2023,6 +2044,8 @@ const struct snd_soc_dai_ops arizona_dai_ops = { .hw_params = arizona_hw_params, .set_sysclk = arizona_dai_set_sysclk, .set_tristate = arizona_set_tristate, + .auto_selectable_formats = arizona_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(arizona_selectable_formats), }; EXPORT_SYMBOL_GPL(arizona_dai_ops); From feb853ce519086207298fa0bef456f2257736583 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:20 +0000 Subject: [PATCH 107/924] ASoC: codecs: cpcap: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87cxuyvpx3.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/cpcap.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index fc41e1547bda..0262900fbb7d 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -1244,11 +1244,24 @@ static int cpcap_hifi_set_mute(struct snd_soc_dai *dai, int mute, int direction) return regmap_update_bits(cpcap->regmap, reg, mask, val); } +static const u64 cpcap_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cpcap_dai_hifi_ops = { .hw_params = cpcap_hifi_hw_params, .set_sysclk = cpcap_hifi_set_dai_sysclk, .set_fmt = cpcap_hifi_set_dai_fmt, .mute_stream = cpcap_hifi_set_mute, + .auto_selectable_formats = &cpcap_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1406,6 +1419,8 @@ static const struct snd_soc_dai_ops cpcap_dai_voice_ops = { .set_sysclk = cpcap_voice_set_dai_sysclk, .set_fmt = cpcap_voice_set_dai_fmt, .mute_stream = cpcap_voice_set_mute, + .auto_selectable_formats = &cpcap_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; From d4d1619ebd551535eb5c9a76c141d0c0716afbfe Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:24 +0000 Subject: [PATCH 108/924] ASoC: codecs: cs35l*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87bjaivpwz.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l33.c | 6 ++++++ sound/soc/codecs/cs35l35.c | 7 +++++++ sound/soc/codecs/cs35l36.c | 12 ++++++++++++ sound/soc/codecs/cs35l41.c | 10 ++++++++++ sound/soc/codecs/cs35l45.c | 10 ++++++++++ sound/soc/codecs/cs35l56.c | 10 ++++++++++ 6 files changed, 55 insertions(+) diff --git a/sound/soc/codecs/cs35l33.c b/sound/soc/codecs/cs35l33.c index e217da124549..d0f9231f5e44 100644 --- a/sound/soc/codecs/cs35l33.c +++ b/sound/soc/codecs/cs35l33.c @@ -663,12 +663,18 @@ static int cs35l33_component_set_sysclk(struct snd_soc_component *component, return 0; } +static const u64 cs35l33_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + static const struct snd_soc_dai_ops cs35l33_ops = { .startup = cs35l33_pcm_startup, .set_tristate = cs35l33_set_tristate, .set_fmt = cs35l33_set_dai_fmt, .hw_params = cs35l33_pcm_hw_params, .set_tdm_slot = cs35l33_set_tdm_slot, + .auto_selectable_formats = &cs35l33_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs35l33_dai = { diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index b2439ec3c19f..302d838f4c44 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -657,17 +657,24 @@ static int cs35l35_dai_set_sysclk(struct snd_soc_dai *dai, return 0; } +static const u64 cs35l35_selectable_formats_i2s = SND_SOC_POSSIBLE_DAIFMT_I2S; +static const u64 cs35l35_selectable_formats_pdm = SND_SOC_POSSIBLE_DAIFMT_PDM; + static const struct snd_soc_dai_ops cs35l35_ops = { .startup = cs35l35_pcm_startup, .set_fmt = cs35l35_set_dai_fmt, .hw_params = cs35l35_hw_params, .set_sysclk = cs35l35_dai_set_sysclk, + .auto_selectable_formats = &cs35l35_selectable_formats_i2s, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops cs35l35_pdm_ops = { .startup = cs35l35_pdm_startup, .set_fmt = cs35l35_set_dai_fmt, .hw_params = cs35l35_hw_params, + .auto_selectable_formats = &cs35l35_selectable_formats_pdm, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs35l35_dai[] = { diff --git a/sound/soc/codecs/cs35l36.c b/sound/soc/codecs/cs35l36.c index 89645327945f..170588be4dbe 100644 --- a/sound/soc/codecs/cs35l36.c +++ b/sound/soc/codecs/cs35l36.c @@ -947,10 +947,22 @@ static const struct cs35l36_pll_config *cs35l36_get_clk_config( return NULL; } +static const u64 cs35l36_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_GATED | + SND_SOC_POSSIBLE_DAIFMT_CONT | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs35l36_ops = { .set_fmt = cs35l36_set_dai_fmt, .hw_params = cs35l36_pcm_hw_params, .set_sysclk = cs35l36_dai_set_sysclk, + .auto_selectable_formats = &cs35l36_selectable_formats, + .num_auto_selectable_formats = 1, }; #define CS35L36_RATES ( \ diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c index b2a076706c79..d286025183f4 100644 --- a/sound/soc/codecs/cs35l41.c +++ b/sound/soc/codecs/cs35l41.c @@ -953,11 +953,21 @@ static void cs35l41_component_remove(struct snd_soc_component *component) wm_adsp2_component_remove(&cs35l41->dsp, component); } +static const u64 cs35l41_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs35l41_ops = { .set_fmt = cs35l41_set_dai_fmt, .hw_params = cs35l41_pcm_hw_params, .set_sysclk = cs35l41_dai_set_sysclk, .set_channel_map = cs35l41_set_channel_map, + .auto_selectable_formats = &cs35l41_selectable_formats, + .num_auto_selectable_formats = 1, }; #define CS35L41_RATES ( \ diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index a032bb23b4ac..25a9eb916fa3 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -846,12 +846,22 @@ static int cs35l45_mute_stream(struct snd_soc_dai *dai, int mute, int stream) return 0; } +static const u64 cs35l45_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs35l45_asp_dai_ops = { .set_fmt = cs35l45_asp_set_fmt, .hw_params = cs35l45_asp_hw_params, .set_tdm_slot = cs35l45_asp_set_tdm_slot, .set_sysclk = cs35l45_asp_set_sysclk, .mute_stream = cs35l45_mute_stream, + .auto_selectable_formats = &cs35l45_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs35l45_dai[] = { diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index b9118ad8fab5..62ffb6fb493d 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -604,12 +604,22 @@ static int cs35l56_asp_dai_set_sysclk(struct snd_soc_dai *dai, return 0; } +static const u64 cs35l56_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs35l56_ops = { .probe = cs35l56_asp_dai_probe, .set_fmt = cs35l56_asp_dai_set_fmt, .set_tdm_slot = cs35l56_asp_dai_set_tdm_slot, .hw_params = cs35l56_asp_dai_hw_params, .set_sysclk = cs35l56_asp_dai_set_sysclk, + .auto_selectable_formats = &cs35l56_selectable_formats, + .num_auto_selectable_formats = 1, }; static void cs35l56_sdw_dai_shutdown(struct snd_pcm_substream *substream, From 27192e1876bafa6f166f92d05879bf809c13ef37 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:30 +0000 Subject: [PATCH 109/924] ASoC: codecs: cs42*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87a4q2vpwt.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs40l50-codec.c | 9 +++++++++ sound/soc/codecs/cs4234.c | 9 +++++++++ sound/soc/codecs/cs4265.c | 12 ++++++++++++ sound/soc/codecs/cs4270.c | 6 ++++++ sound/soc/codecs/cs4271.c | 6 ++++++ sound/soc/codecs/cs42l42.c | 9 +++++++++ sound/soc/codecs/cs42l43.c | 12 ++++++++++++ sound/soc/codecs/cs42l51.c | 7 +++++++ sound/soc/codecs/cs42l52.c | 13 +++++++++++++ sound/soc/codecs/cs42l56.c | 8 ++++++++ sound/soc/codecs/cs42l73.c | 7 +++++++ sound/soc/codecs/cs42l84.c | 6 ++++++ sound/soc/codecs/cs42xx8.c | 8 ++++++++ sound/soc/codecs/cs43130.c | 14 ++++++++++++++ sound/soc/codecs/cs4341.c | 8 ++++++++ sound/soc/codecs/cs4349.c | 12 ++++++++++++ sound/soc/codecs/cs48l32.c | 23 +++++++++++++++++++++++ 17 files changed, 169 insertions(+) diff --git a/sound/soc/codecs/cs40l50-codec.c b/sound/soc/codecs/cs40l50-codec.c index 676367163067..698aa59194f4 100644 --- a/sound/soc/codecs/cs40l50-codec.c +++ b/sound/soc/codecs/cs40l50-codec.c @@ -233,10 +233,19 @@ static int cs40l50_set_dai_bclk_ratio(struct snd_soc_dai *dai, unsigned int rati return 0; } +static const u64 cs40l50_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs40l50_dai_ops = { .set_fmt = cs40l50_set_dai_fmt, .set_bclk_ratio = cs40l50_set_dai_bclk_ratio, .hw_params = cs40l50_hw_params, + .auto_selectable_formats = &cs40l50_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs40l50_dai[] = { diff --git a/sound/soc/codecs/cs4234.c b/sound/soc/codecs/cs4234.c index a889fbd519a8..a6ead1ee3708 100644 --- a/sound/soc/codecs/cs4234.c +++ b/sound/soc/codecs/cs4234.c @@ -560,11 +560,20 @@ static int cs4234_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask return 0; } +static const u64 cs4234_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops cs4234_dai_ops = { .set_fmt = cs4234_dai_set_fmt, .hw_params = cs4234_dai_hw_params, .startup = cs4234_dai_startup, .set_tdm_slot = cs4234_dai_set_tdm_slot, + .auto_selectable_formats = &cs4234_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs4234_dai[] = { diff --git a/sound/soc/codecs/cs4265.c b/sound/soc/codecs/cs4265.c index 0b7d6826f4e9..97e32e6ccb84 100644 --- a/sound/soc/codecs/cs4265.c +++ b/sound/soc/codecs/cs4265.c @@ -495,11 +495,23 @@ static int cs4265_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U24_LE | \ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) +/* + * NOTE + * + * auto_select doesn't include _RIGHT_J, because capture can't use it + * (= cs4265_pcm_hw_params()). It can be selected via Card directly if needed. + */ +static const u64 cs4265_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops cs4265_ops = { .hw_params = cs4265_pcm_hw_params, .mute_stream = cs4265_mute, .set_fmt = cs4265_set_fmt, .set_sysclk = cs4265_set_sysclk, + .auto_selectable_formats = &cs4265_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 47b2f903a32c..aa3bbb951945 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -456,11 +456,17 @@ static const struct snd_kcontrol_new cs4270_snd_controls[] = { snd_soc_get_volsw, cs4270_soc_put_mute), }; +static const u64 cs4270_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops cs4270_dai_ops = { .hw_params = cs4270_hw_params, .set_sysclk = cs4270_set_dai_sysclk, .set_fmt = cs4270_set_dai_fmt, .mute_stream = cs4270_dai_mute, + .auto_selectable_formats = &cs4270_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c index d8cdd37e9112..398d8c24a9ce 100644 --- a/sound/soc/codecs/cs4271.c +++ b/sound/soc/codecs/cs4271.c @@ -455,11 +455,17 @@ static const struct snd_kcontrol_new cs4271_snd_controls[] = { 7, 1, 1), }; +static const u64 cs4271_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops cs4271_dai_ops = { .hw_params = cs4271_hw_params, .set_sysclk = cs4271_set_dai_sysclk, .set_fmt = cs4271_set_dai_fmt, .mute_stream = cs4271_mute_stream, + .auto_selectable_formats = &cs4271_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs4271_dai = { diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index fadf68860601..9e40c03ada4e 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -1122,6 +1122,13 @@ EXPORT_SYMBOL_NS_GPL(cs42l42_mute_stream, "SND_SOC_CS42L42_CORE"); SNDRV_PCM_FMTBIT_S24_LE |\ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 cs42l42_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs42l42_ops = { .startup = cs42l42_dai_startup, .hw_params = cs42l42_pcm_hw_params, @@ -1129,6 +1136,8 @@ static const struct snd_soc_dai_ops cs42l42_ops = { .set_sysclk = cs42l42_set_sysclk, .set_bclk_ratio = cs42l42_set_bclk_ratio, .mute_stream = cs42l42_mute_stream, + .auto_selectable_formats = &cs42l42_selectable_formats, + .num_auto_selectable_formats = 1, }; struct snd_soc_dai_driver cs42l42_dai = { diff --git a/sound/soc/codecs/cs42l43.c b/sound/soc/codecs/cs42l43.c index 1b817e1a6ea5..b6238f89c789 100644 --- a/sound/soc/codecs/cs42l43.c +++ b/sound/soc/codecs/cs42l43.c @@ -671,6 +671,16 @@ static int cs42l43_dai_remove(struct snd_soc_dai *dai) return 0; } +static const u64 cs42l43_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs42l43_asp_ops = { .probe = cs42l43_dai_probe, .remove = cs42l43_dai_remove, @@ -678,6 +688,8 @@ static const struct snd_soc_dai_ops cs42l43_asp_ops = { .hw_params = cs42l43_asp_hw_params, .set_fmt = cs42l43_asp_set_fmt, .set_tdm_slot = cs42l43_asp_set_tdm_slot, + .auto_selectable_formats = &cs42l43_selectable_formats, + .num_auto_selectable_formats = 1, }; static int cs42l43_sdw_hw_params(struct snd_pcm_substream *substream, diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c index 0519affe0ed8..4b964c713398 100644 --- a/sound/soc/codecs/cs42l51.c +++ b/sound/soc/codecs/cs42l51.c @@ -533,11 +533,18 @@ static int cs42l51_of_xlate_dai_id(struct snd_soc_component *component, return 0; } +static const u64 cs42l51_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops cs42l51_dai_ops = { .hw_params = cs42l51_hw_params, .set_sysclk = cs42l51_set_dai_sysclk, .set_fmt = cs42l51_set_dai_fmt, .mute_stream = cs42l51_dai_mute, + .auto_selectable_formats = &cs42l51_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs42l52.c b/sound/soc/codecs/cs42l52.c index 9d6bcfbbf0b2..10760816a5f9 100644 --- a/sound/soc/codecs/cs42l52.c +++ b/sound/soc/codecs/cs42l52.c @@ -883,11 +883,24 @@ static int cs42l52_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U24_LE) +static const u64 cs42l52_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs42l52_ops = { .hw_params = cs42l52_pcm_hw_params, .mute_stream = cs42l52_mute, .set_fmt = cs42l52_set_fmt, .set_sysclk = cs42l52_set_sysclk, + .auto_selectable_formats = &cs42l52_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs42l56.c b/sound/soc/codecs/cs42l56.c index 4d9a22a1029c..9c6448ebb2cf 100644 --- a/sound/soc/codecs/cs42l56.c +++ b/sound/soc/codecs/cs42l56.c @@ -960,11 +960,19 @@ static int cs42l56_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_FMTBIT_S32_LE) +static const u64 cs42l56_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops cs42l56_ops = { .hw_params = cs42l56_pcm_hw_params, .mute_stream = cs42l56_mute, .set_fmt = cs42l56_set_dai_fmt, .set_sysclk = cs42l56_set_sysclk, + .auto_selectable_formats = &cs42l56_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs42l73.c b/sound/soc/codecs/cs42l73.c index 6ddc6549492a..72076ccf4570 100644 --- a/sound/soc/codecs/cs42l73.c +++ b/sound/soc/codecs/cs42l73.c @@ -1163,12 +1163,19 @@ static int cs42l73_pcm_startup(struct snd_pcm_substream *substream, #define CS42L73_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 cs42l73_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops cs42l73_ops = { .startup = cs42l73_pcm_startup, .hw_params = cs42l73_pcm_hw_params, .set_fmt = cs42l73_set_dai_fmt, .set_sysclk = cs42l73_set_sysclk, .set_tristate = cs42l73_set_tristate, + .auto_selectable_formats = &cs42l73_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs42l73_dai[] = { diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c index a545730b7ed9..77015590368b 100644 --- a/sound/soc/codecs/cs42l84.c +++ b/sound/soc/codecs/cs42l84.c @@ -662,11 +662,17 @@ static int cs42l84_mute_stream(struct snd_soc_dai *dai, int mute, int stream) return 0; } +static const u64 cs42l84_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs42l84_ops = { .hw_params = cs42l84_pcm_hw_params, .set_fmt = cs42l84_set_dai_fmt, .set_sysclk = cs42l84_set_sysclk, .mute_stream = cs42l84_mute_stream, + .auto_selectable_formats = &cs42l84_selectable_formats, + .num_auto_selectable_formats = 1, }; #define CS42L84_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c index 5b689549c74e..fb9f4aff6bd8 100644 --- a/sound/soc/codecs/cs42xx8.c +++ b/sound/soc/codecs/cs42xx8.c @@ -393,12 +393,20 @@ static int cs42xx8_mute(struct snd_soc_dai *dai, int mute, int direction) return 0; } +static const u64 cs42xx8_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + static const struct snd_soc_dai_ops cs42xx8_dai_ops = { .set_fmt = cs42xx8_set_dai_fmt, .set_sysclk = cs42xx8_set_dai_sysclk, .hw_params = cs42xx8_hw_params, .hw_free = cs42xx8_hw_free, .mute_stream = cs42xx8_mute, + .auto_selectable_formats = &cs42xx8_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs43130.c b/sound/soc/codecs/cs43130.c index 48d88dad4ceb..7bbb2e83a1ff 100644 --- a/sound/soc/codecs/cs43130.c +++ b/sound/soc/codecs/cs43130.c @@ -1609,12 +1609,24 @@ static int cs43130_set_sysclk(struct snd_soc_dai *codec_dai, return 0; } +static const u64 cs43130_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs43130_pcm_ops = { .startup = cs43130_pcm_startup, .hw_params = cs43130_hw_params, .hw_free = cs43130_hw_free, .set_sysclk = cs43130_set_sysclk, .set_fmt = cs43130_pcm_set_fmt, + .auto_selectable_formats = &cs43130_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops cs43130_dop_ops = { @@ -1623,6 +1635,8 @@ static const struct snd_soc_dai_ops cs43130_dop_ops = { .hw_free = cs43130_hw_free, .set_sysclk = cs43130_set_sysclk, .set_fmt = cs43130_pcm_set_fmt, + .auto_selectable_formats = &cs43130_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops cs43130_dsd_ops = { diff --git a/sound/soc/codecs/cs4341.c b/sound/soc/codecs/cs4341.c index a44e6b3c298e..74544c58a415 100644 --- a/sound/soc/codecs/cs4341.c +++ b/sound/soc/codecs/cs4341.c @@ -171,10 +171,18 @@ static const struct snd_kcontrol_new cs4341_controls[] = { SOC_SINGLE("Popguard Transient Switch", CS4341_REG_MODE2, 1, 1, 0), }; +static const u64 cs4341_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops cs4341_dai_ops = { .set_fmt = cs4341_set_fmt, .hw_params = cs4341_hw_params, .mute_stream = cs4341_mute, + .auto_selectable_formats = &cs4341_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs4349.c b/sound/soc/codecs/cs4349.c index 9a7f70175717..28320951e309 100644 --- a/sound/soc/codecs/cs4349.c +++ b/sound/soc/codecs/cs4349.c @@ -228,10 +228,22 @@ static const struct snd_soc_dapm_route cs4349_routes[] = { #define CS4349_PCM_RATES SNDRV_PCM_RATE_8000_192000 +/* + * NOTE + * + * auto_select doesn't include _RIGHT_J, because 32bit width can't use it + * (= cs4349_pcm_hw_params()). It can be selected via Card directly if needed. + */ +static const u64 cs4349_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops cs4349_dai_ops = { .hw_params = cs4349_pcm_hw_params, .set_fmt = cs4349_set_dai_fmt, .mute_stream = cs4349_mute, + .auto_selectable_formats = &cs4349_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs48l32.c b/sound/soc/codecs/cs48l32.c index a9967771124e..c11fc1a588c4 100644 --- a/sound/soc/codecs/cs48l32.c +++ b/sound/soc/codecs/cs48l32.c @@ -2361,6 +2361,27 @@ static int cs48l32_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, return 0; } +static const u64 cs48l32_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* + * 2nd priority + * + * _DSP_B / _LEFT_J can't be clock consumer (= cs48l32_set_fmt()). + */ + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, +}; + static const struct snd_soc_dai_ops cs48l32_dai_ops = { .probe = &cs48l32_asp_dai_probe, .startup = &cs48l32_startup, @@ -2368,6 +2389,8 @@ static const struct snd_soc_dai_ops cs48l32_dai_ops = { .set_tdm_slot = &cs48l32_set_tdm_slot, .hw_params = &cs48l32_hw_params, .set_sysclk = &cs48l32_dai_set_sysclk, + .auto_selectable_formats = cs48l32_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(cs48l32_selectable_formats), }; static int cs48l32_sysclk_ev(struct snd_soc_dapm_widget *w, From b1d8fda689d34219da04af04a2b5f9fe0457ef44 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:34 +0000 Subject: [PATCH 110/924] ASoC: codecs: cs5*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/878q5mvpwp.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs530x.c | 9 +++++++++ sound/soc/codecs/cs53l30.c | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/sound/soc/codecs/cs530x.c b/sound/soc/codecs/cs530x.c index 2c7e33135911..e0ea246edbd8 100644 --- a/sound/soc/codecs/cs530x.c +++ b/sound/soc/codecs/cs530x.c @@ -945,10 +945,19 @@ static int cs530x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, val << CS530X_ASP_TDM_SLOT_SHIFT); } +static const u64 cs530x_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs530x_dai_ops = { .set_fmt = cs530x_set_fmt, .hw_params = cs530x_hw_params, .set_tdm_slot = cs530x_set_tdm_slot, + .auto_selectable_formats = &cs530x_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_driver cs530x_dai = { diff --git a/sound/soc/codecs/cs53l30.c b/sound/soc/codecs/cs53l30.c index 511453b4c375..c9c682a312c3 100644 --- a/sound/soc/codecs/cs53l30.c +++ b/sound/soc/codecs/cs53l30.c @@ -833,6 +833,12 @@ static int cs53l30_mute_stream(struct snd_soc_dai *dai, int mute, int stream) #define CS53L30_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 cs53l30_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs53l30_ops = { .hw_params = cs53l30_pcm_hw_params, .set_fmt = cs53l30_set_dai_fmt, @@ -840,6 +846,8 @@ static const struct snd_soc_dai_ops cs53l30_ops = { .set_tristate = cs53l30_set_tristate, .set_tdm_slot = cs53l30_set_dai_tdm_slot, .mute_stream = cs53l30_mute_stream, + .auto_selectable_formats = &cs53l30_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs53l30_dai = { From 956fe07e17822de78b2aae167fc38e63820a72c8 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:43 +0000 Subject: [PATCH 111/924] ASoC: codecs: cx2072x: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/877bl6vpwg.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/cx2072x.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/codecs/cx2072x.c b/sound/soc/codecs/cx2072x.c index d02ab74e549b..5b9e9520ae01 100644 --- a/sound/soc/codecs/cx2072x.c +++ b/sound/soc/codecs/cx2072x.c @@ -1521,6 +1521,16 @@ static const struct snd_soc_component_driver soc_codec_driver_cx2072x = { .endianness = 1, }; + +static const u64 cx2072x_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + /* * DAI ops */ @@ -1529,6 +1539,8 @@ static const struct snd_soc_dai_ops cx2072x_dai_ops = { .set_fmt = cx2072x_set_dai_fmt, .hw_params = cx2072x_hw_params, .set_bclk_ratio = cx2072x_set_dai_bclk_ratio, + .auto_selectable_formats = &cx2072x_selectable_formats, + .num_auto_selectable_formats = 1, }; static int cx2072x_dsp_dai_probe(struct snd_soc_dai *dai) From 1e3cc1d36a1c703c6886f6652f95c88e0e84ff96 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:47 +0000 Subject: [PATCH 112/924] ASoC: codecs: da*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/875x0qvpwc.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/da7210.c | 7 +++++++ sound/soc/codecs/da7218.c | 12 ++++++++++++ sound/soc/codecs/da7219.c | 12 ++++++++++++ sound/soc/codecs/da732x.c | 17 +++++++++++++++++ sound/soc/codecs/da9055.c | 8 ++++++++ 5 files changed, 56 insertions(+) diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c index 4df1a25e318b..597510de561a 100644 --- a/sound/soc/codecs/da7210.c +++ b/sound/soc/codecs/da7210.c @@ -1030,6 +1030,11 @@ static int da7210_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, return -EINVAL; } +static const u64 da7210_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + /* DAI operations */ static const struct snd_soc_dai_ops da7210_dai_ops = { .hw_params = da7210_hw_params, @@ -1037,6 +1042,8 @@ static const struct snd_soc_dai_ops da7210_dai_ops = { .set_sysclk = da7210_set_dai_sysclk, .set_pll = da7210_set_dai_pll, .mute_stream = da7210_mute, + .auto_selectable_formats = &da7210_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/da7218.c b/sound/soc/codecs/da7218.c index 93bd045ed870..361daf14152e 100644 --- a/sound/soc/codecs/da7218.c +++ b/sound/soc/codecs/da7218.c @@ -2166,12 +2166,24 @@ static int da7218_hw_params(struct snd_pcm_substream *substream, return 0; } +static const u64 da7218_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops da7218_dai_ops = { .hw_params = da7218_hw_params, .set_sysclk = da7218_set_dai_sysclk, .set_pll = da7218_set_dai_pll, .set_fmt = da7218_set_dai_fmt, .set_tdm_slot = da7218_set_dai_tdm_slot, + .auto_selectable_formats = &da7218_selectable_formats, + .num_auto_selectable_formats = 1, }; #define DA7218_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c index f6d371e93549..673285c3213b 100644 --- a/sound/soc/codecs/da7219.c +++ b/sound/soc/codecs/da7219.c @@ -1643,12 +1643,24 @@ static int da7219_hw_params(struct snd_pcm_substream *substream, return 0; } +static const u64 da7219_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops da7219_dai_ops = { .hw_params = da7219_hw_params, .set_sysclk = da7219_set_dai_sysclk, .set_pll = da7219_set_dai_pll, .set_fmt = da7219_set_dai_fmt, .set_tdm_slot = da7219_set_dai_tdm_slot, + .auto_selectable_formats = &da7219_selectable_formats, + .num_auto_selectable_formats = 1, }; #define DA7219_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ diff --git a/sound/soc/codecs/da732x.c b/sound/soc/codecs/da732x.c index 12d1ac98461e..c9b6172ac0a1 100644 --- a/sound/soc/codecs/da732x.c +++ b/sound/soc/codecs/da732x.c @@ -1188,10 +1188,27 @@ static int da732x_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, #define DA732X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 da732x_selectable_formats[] = { + /* Hi Priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* Low Priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops da732x_dai_ops = { .hw_params = da732x_hw_params, .set_fmt = da732x_set_dai_fmt, .set_sysclk = da732x_set_dai_sysclk, + .auto_selectable_formats = da732x_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(da732x_selectable_formats), }; static struct snd_soc_dai_driver da732x_dai[] = { diff --git a/sound/soc/codecs/da9055.c b/sound/soc/codecs/da9055.c index a88c13533145..faff1535ab12 100644 --- a/sound/soc/codecs/da9055.c +++ b/sound/soc/codecs/da9055.c @@ -1317,6 +1317,12 @@ static int da9055_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, return -EINVAL; } +static const u64 da9055_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + /* DAI operations */ static const struct snd_soc_dai_ops da9055_dai_ops = { .hw_params = da9055_hw_params, @@ -1324,6 +1330,8 @@ static const struct snd_soc_dai_ops da9055_dai_ops = { .set_sysclk = da9055_set_dai_sysclk, .set_pll = da9055_set_dai_pll, .mute_stream = da9055_mute, + .auto_selectable_formats = &da9055_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; From 1427806905e4e3adbd7b3a21b386176787c196a1 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:51 +0000 Subject: [PATCH 113/924] ASoC: codecs: es*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/874igavpw8.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/es7134.c | 6 ++++++ sound/soc/codecs/es7241.c | 7 +++++++ sound/soc/codecs/es8311.c | 17 +++++++++++++++++ sound/soc/codecs/es8316.c | 9 +++++++++ sound/soc/codecs/es8323.c | 13 +++++++++++++ sound/soc/codecs/es8326.c | 9 +++++++++ sound/soc/codecs/es8328.c | 7 +++++++ sound/soc/codecs/es8375.c | 12 ++++++++++++ sound/soc/codecs/es8389.c | 9 +++++++++ 9 files changed, 89 insertions(+) diff --git a/sound/soc/codecs/es7134.c b/sound/soc/codecs/es7134.c index 441df1523f1c..e3e6142ca028 100644 --- a/sound/soc/codecs/es7134.c +++ b/sound/soc/codecs/es7134.c @@ -130,10 +130,16 @@ static int es7134_component_probe(struct snd_soc_component *c) return 0; } +static const u64 es7134_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops es7134_dai_ops = { .set_fmt = es7134_set_fmt, .hw_params = es7134_hw_params, .set_sysclk = es7134_set_sysclk, + .auto_selectable_formats = &es7134_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver es7134_dai = { diff --git a/sound/soc/codecs/es7241.c b/sound/soc/codecs/es7241.c index 339553cfbb48..a0bd5386387d 100644 --- a/sound/soc/codecs/es7241.c +++ b/sound/soc/codecs/es7241.c @@ -152,10 +152,17 @@ static int es7241_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; } +static const u64 es7241_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops es7241_dai_ops = { .set_fmt = es7241_set_fmt, .hw_params = es7241_hw_params, .set_sysclk = es7241_set_sysclk, + .auto_selectable_formats = &es7241_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver es7241_dai = { diff --git a/sound/soc/codecs/es8311.c b/sound/soc/codecs/es8311.c index 6fe3ae41afbd..e46b85f11d57 100644 --- a/sound/soc/codecs/es8311.c +++ b/sound/soc/codecs/es8311.c @@ -800,12 +800,29 @@ static int es8311_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 es8311_selectable_formats[] = { + /* Hi Priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* Low Priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops es8311_dai_ops = { .startup = es8311_startup, .hw_params = es8311_hw_params, .mute_stream = es8311_mute, .set_sysclk = es8311_set_sysclk, .set_fmt = es8311_set_dai_fmt, + .auto_selectable_formats = es8311_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(es8311_selectable_formats), .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index 24ec8b211cdb..59399476b9d7 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -566,12 +566,21 @@ static int es8316_mute(struct snd_soc_dai *dai, int mute, int direction) #define ES8316_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 es8316_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops es8316_ops = { .startup = es8316_pcm_startup, .hw_params = es8316_pcm_hw_params, .set_fmt = es8316_set_dai_fmt, .set_sysclk = es8316_set_dai_sysclk, .mute_stream = es8316_mute, + .auto_selectable_formats = &es8316_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/es8323.c b/sound/soc/codecs/es8323.c index b926340256be..12fcfe017ab2 100644 --- a/sound/soc/codecs/es8323.c +++ b/sound/soc/codecs/es8323.c @@ -622,12 +622,25 @@ static int es8323_mute_stream(struct snd_soc_dai *dai, int mute, int stream) mute ? ES8323_DACCONTROL3_DACMUTE : 0); } +static const u64 es8323_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops es8323_ops = { .startup = es8323_pcm_startup, .hw_params = es8323_pcm_hw_params, .set_fmt = es8323_set_dai_fmt, .set_sysclk = es8323_set_dai_sysclk, .mute_stream = es8323_mute_stream, + .auto_selectable_formats = &es8323_selectable_formats, + .num_auto_selectable_formats = 1, }; #define ES8323_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c index c5460589a88b..74c0bceb0f9b 100644 --- a/sound/soc/codecs/es8326.c +++ b/sound/soc/codecs/es8326.c @@ -723,11 +723,20 @@ static int es8326_set_bias_level(struct snd_soc_component *codec, #define es8326_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 es8326_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops es8326_ops = { .hw_params = es8326_pcm_hw_params, .set_fmt = es8326_set_dai_fmt, .set_sysclk = es8326_set_dai_sysclk, .mute_stream = es8326_mute, + .auto_selectable_formats = &es8326_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 0, }; diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c index aaa6646ad4c5..7d52b5d9d061 100644 --- a/sound/soc/codecs/es8328.c +++ b/sound/soc/codecs/es8328.c @@ -727,12 +727,19 @@ static int es8328_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 es8328_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops es8328_dai_ops = { .startup = es8328_startup, .hw_params = es8328_hw_params, .mute_stream = es8328_mute, .set_sysclk = es8328_set_sysclk, .set_fmt = es8328_set_dai_fmt, + .auto_selectable_formats = &es8328_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/es8375.c b/sound/soc/codecs/es8375.c index e8747bc24433..bc21cf7e602d 100644 --- a/sound/soc/codecs/es8375.c +++ b/sound/soc/codecs/es8375.c @@ -494,11 +494,23 @@ static int es8375_mute(struct snd_soc_dai *dai, int mute, int stream) #define es8375_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 es8375_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops es8375_ops = { .hw_params = es8375_hw_params, .mute_stream = es8375_mute, .set_sysclk = es8375_set_sysclk, .set_fmt = es8375_set_dai_fmt, + .auto_selectable_formats = &es8375_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver es8375_dai = { diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c index 80efce3e0a22..452e597d81d5 100644 --- a/sound/soc/codecs/es8389.c +++ b/sound/soc/codecs/es8389.c @@ -904,6 +904,13 @@ static int es8389_mute(struct snd_soc_dai *dai, int mute, int direction) #define es8389_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 es8389_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops es8389_ops = { .hw_params = es8389_pcm_hw_params, .hw_free = es8389_pcm_hw_free, @@ -911,6 +918,8 @@ static const struct snd_soc_dai_ops es8389_ops = { .set_sysclk = es8389_set_dai_sysclk, .set_tdm_slot = es8389_set_tdm_slot, .mute_stream = es8389_mute, + .auto_selectable_formats = &es8389_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver es8389_dai = { From f7ee5cdfbd08708985325ec37e215b3b8941ed22 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:55 +0000 Subject: [PATCH 114/924] ASoC: codecs: inno_rk3036: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/8733vuvpw5.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/inno_rk3036.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/codecs/inno_rk3036.c b/sound/soc/codecs/inno_rk3036.c index fdd19f8e8864..aaf03f228150 100644 --- a/sound/soc/codecs/inno_rk3036.c +++ b/sound/soc/codecs/inno_rk3036.c @@ -309,9 +309,21 @@ static int rk3036_codec_dai_hw_params(struct snd_pcm_substream *substream, SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 rk3036_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rk3036_codec_dai_ops = { .set_fmt = rk3036_codec_dai_set_fmt, .hw_params = rk3036_codec_dai_hw_params, + .auto_selectable_formats = &rk3036_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rk3036_codec_dai_driver[] = { From 43a8fe22f7dec5dcdf6d3554a70ca4419fd60a1f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:07:59 +0000 Subject: [PATCH 115/924] ASoC: codecs: isabelle: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/871pbevpw0.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/isabelle.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/soc/codecs/isabelle.c b/sound/soc/codecs/isabelle.c index fa1c23dda4eb..7e8dde79b11a 100644 --- a/sound/soc/codecs/isabelle.c +++ b/sound/soc/codecs/isabelle.c @@ -1011,10 +1011,17 @@ static int isabelle_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) #define ISABELLE_FORMATS (SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 isabelle_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_PDM; + static const struct snd_soc_dai_ops isabelle_hs_dai_ops = { .hw_params = isabelle_hw_params, .set_fmt = isabelle_set_dai_fmt, .mute_stream = isabelle_hs_mute, + .auto_selectable_formats = &isabelle_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1022,6 +1029,8 @@ static const struct snd_soc_dai_ops isabelle_hf_dai_ops = { .hw_params = isabelle_hw_params, .set_fmt = isabelle_set_dai_fmt, .mute_stream = isabelle_hf_mute, + .auto_selectable_formats = &isabelle_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1029,12 +1038,16 @@ static const struct snd_soc_dai_ops isabelle_line_dai_ops = { .hw_params = isabelle_hw_params, .set_fmt = isabelle_set_dai_fmt, .mute_stream = isabelle_line_mute, + .auto_selectable_formats = &isabelle_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; static const struct snd_soc_dai_ops isabelle_ul_dai_ops = { .hw_params = isabelle_hw_params, .set_fmt = isabelle_set_dai_fmt, + .auto_selectable_formats = &isabelle_selectable_formats, + .num_auto_selectable_formats = 1, }; /* ISABELLE dai structure */ From 4f3d20ac26eb04323ddc28c23b980c5e5bb4cdbe Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:04 +0000 Subject: [PATCH 116/924] ASoC: codecs: lm49453: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87zey2ubbf.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/lm49453.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sound/soc/codecs/lm49453.c b/sound/soc/codecs/lm49453.c index 6e2e292d143b..991555bec712 100644 --- a/sound/soc/codecs/lm49453.c +++ b/sound/soc/codecs/lm49453.c @@ -1283,11 +1283,18 @@ static int lm49453_set_bias_level(struct snd_soc_component *component, #define LM49453_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 lm49453_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops lm49453_headset_dai_ops = { .hw_params = lm49453_hw_params, .set_sysclk = lm49453_set_dai_sysclk, .set_fmt = lm49453_set_dai_fmt, .mute_stream = lm49453_hp_mute, + .auto_selectable_formats = &lm49453_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1296,6 +1303,8 @@ static const struct snd_soc_dai_ops lm49453_speaker_dai_ops = { .set_sysclk = lm49453_set_dai_sysclk, .set_fmt = lm49453_set_dai_fmt, .mute_stream = lm49453_ls_mute, + .auto_selectable_formats = &lm49453_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1304,6 +1313,8 @@ static const struct snd_soc_dai_ops lm49453_haptic_dai_ops = { .set_sysclk = lm49453_set_dai_sysclk, .set_fmt = lm49453_set_dai_fmt, .mute_stream = lm49453_ha_mute, + .auto_selectable_formats = &lm49453_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1312,6 +1323,8 @@ static const struct snd_soc_dai_ops lm49453_ep_dai_ops = { .set_sysclk = lm49453_set_dai_sysclk, .set_fmt = lm49453_set_dai_fmt, .mute_stream = lm49453_ep_mute, + .auto_selectable_formats = &lm49453_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1320,6 +1333,8 @@ static const struct snd_soc_dai_ops lm49453_lineout_dai_ops = { .set_sysclk = lm49453_set_dai_sysclk, .set_fmt = lm49453_set_dai_fmt, .mute_stream = lm49453_lo_mute, + .auto_selectable_formats = &lm49453_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; From 2e4e627eda15d34c0263a503992b52918a17cf8a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:08 +0000 Subject: [PATCH 117/924] ASoC: codecs: max*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87y0dmubbb.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/max98088.c | 12 ++++++++++++ sound/soc/codecs/max98090.c | 25 +++++++++++++++++++++++++ sound/soc/codecs/max98095.c | 14 ++++++++++++++ sound/soc/codecs/max98371.c | 7 +++++++ sound/soc/codecs/max98373-i2c.c | 10 ++++++++++ sound/soc/codecs/max98388.c | 10 ++++++++++ sound/soc/codecs/max98390.c | 10 ++++++++++ sound/soc/codecs/max98396.c | 12 ++++++++++++ sound/soc/codecs/max9850.c | 11 +++++++++++ sound/soc/codecs/max98520.c | 10 ++++++++++ sound/soc/codecs/max9860.c | 23 +++++++++++++++++++++++ sound/soc/codecs/max9867.c | 10 ++++++++++ sound/soc/codecs/max98925.c | 8 ++++++++ sound/soc/codecs/max98926.c | 8 ++++++++ sound/soc/codecs/max98927.c | 10 ++++++++++ 15 files changed, 180 insertions(+) diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c index df438baf05dc..1dd7881c84aa 100644 --- a/sound/soc/codecs/max98088.c +++ b/sound/soc/codecs/max98088.c @@ -1342,11 +1342,21 @@ static int max98088_set_bias_level(struct snd_soc_component *component, #define MAX98088_RATES SNDRV_PCM_RATE_8000_96000 #define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +static const u64 max98088_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max98088_dai1_ops = { .set_sysclk = max98088_dai_set_sysclk, .set_fmt = max98088_dai1_set_fmt, .hw_params = max98088_dai1_hw_params, .mute_stream = max98088_dai1_mute, + .auto_selectable_formats = &max98088_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1355,6 +1365,8 @@ static const struct snd_soc_dai_ops max98088_dai2_ops = { .set_fmt = max98088_dai2_set_fmt, .hw_params = max98088_dai2_hw_params, .mute_stream = max98088_dai2_mute, + .auto_selectable_formats = &max98088_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c index 64dc442e9066..c31f3d32fa43 100644 --- a/sound/soc/codecs/max98090.c +++ b/sound/soc/codecs/max98090.c @@ -2382,6 +2382,29 @@ static int max98090_set_jack(struct snd_soc_component *component, #define MAX98090_RATES SNDRV_PCM_RATE_8000_96000 #define MAX98090_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +static const u64 max98090_selectable_formats[] = { + /* + * Non RIGHT_J formats doesn't have 24bit support. + * priority is RIGHT_J > I2S/LEFT_J/DSP_A + * see + * max98090_dai_startup() + */ + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, +}; + static const struct snd_soc_dai_ops max98090_dai_ops = { .startup = max98090_dai_startup, .set_sysclk = max98090_dai_set_sysclk, @@ -2390,6 +2413,8 @@ static const struct snd_soc_dai_ops max98090_dai_ops = { .hw_params = max98090_dai_hw_params, .mute_stream = max98090_dai_mute, .trigger = max98090_dai_trigger, + .auto_selectable_formats = max98090_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(max98090_selectable_formats), .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c index d93436046849..a64a5ba00379 100644 --- a/sound/soc/codecs/max98095.c +++ b/sound/soc/codecs/max98095.c @@ -1413,22 +1413,36 @@ static int max98095_set_bias_level(struct snd_soc_component *component, #define MAX98095_RATES SNDRV_PCM_RATE_8000_96000 #define MAX98095_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +static const u64 max98095_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max98095_dai1_ops = { .set_sysclk = max98095_dai_set_sysclk, .set_fmt = max98095_dai1_set_fmt, .hw_params = max98095_dai1_hw_params, + .auto_selectable_formats = &max98095_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops max98095_dai2_ops = { .set_sysclk = max98095_dai_set_sysclk, .set_fmt = max98095_dai2_set_fmt, .hw_params = max98095_dai2_hw_params, + .auto_selectable_formats = &max98095_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops max98095_dai3_ops = { .set_sysclk = max98095_dai_set_sysclk, .set_fmt = max98095_dai3_set_fmt, .hw_params = max98095_dai3_hw_params, + .auto_selectable_formats = &max98095_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver max98095_dai[] = { diff --git a/sound/soc/codecs/max98371.c b/sound/soc/codecs/max98371.c index c8d4c68af562..b62bd98f2252 100644 --- a/sound/soc/codecs/max98371.c +++ b/sound/soc/codecs/max98371.c @@ -322,9 +322,16 @@ static const struct snd_soc_dapm_route max98371_audio_map[] = { #define MAX98371_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) +static const u64 max98371_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops max98371_dai_ops = { .set_fmt = max98371_dai_set_fmt, .hw_params = max98371_dai_hw_params, + .auto_selectable_formats = &max98371_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver max98371_dai[] = { diff --git a/sound/soc/codecs/max98373-i2c.c b/sound/soc/codecs/max98373-i2c.c index cbd7ebffa6d6..4e52282ace7f 100644 --- a/sound/soc/codecs/max98373-i2c.c +++ b/sound/soc/codecs/max98373-i2c.c @@ -398,10 +398,20 @@ static int max98373_dai_tdm_slot(struct snd_soc_dai *dai, #define MAX98373_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 max98373_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops max98373_dai_ops = { .set_fmt = max98373_dai_set_fmt, .hw_params = max98373_dai_hw_params, .set_tdm_slot = max98373_dai_tdm_slot, + .auto_selectable_formats = &max98373_selectable_formats, + .num_auto_selectable_formats = 1, }; static bool max98373_readable_register(struct device *dev, unsigned int reg) diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c index d97dab41bf0f..a653e0a21899 100644 --- a/sound/soc/codecs/max98388.c +++ b/sound/soc/codecs/max98388.c @@ -776,10 +776,20 @@ static int max98388_dai_tdm_slot(struct snd_soc_dai *dai, #define MAX98388_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 max98388_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops max98388_dai_ops = { .set_fmt = max98388_dai_set_fmt, .hw_params = max98388_dai_hw_params, .set_tdm_slot = max98388_dai_tdm_slot, + .auto_selectable_formats = &max98388_selectable_formats, + .num_auto_selectable_formats = 1, }; static bool max98388_readable_register(struct device *dev, diff --git a/sound/soc/codecs/max98390.c b/sound/soc/codecs/max98390.c index 2295fc057c71..b87cca2a5b43 100644 --- a/sound/soc/codecs/max98390.c +++ b/sound/soc/codecs/max98390.c @@ -462,11 +462,21 @@ static int max98390_dai_set_sysclk(struct snd_soc_dai *dai, return 0; } +static const u64 max98390_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops max98390_dai_ops = { .set_sysclk = max98390_dai_set_sysclk, .set_fmt = max98390_dai_set_fmt, .hw_params = max98390_dai_hw_params, .set_tdm_slot = max98390_dai_tdm_slot, + .auto_selectable_formats = &max98390_selectable_formats, + .num_auto_selectable_formats = 1, }; static int max98390_dac_event(struct snd_soc_dapm_widget *w, diff --git a/sound/soc/codecs/max98396.c b/sound/soc/codecs/max98396.c index e5a2fe824c03..935319fee41b 100644 --- a/sound/soc/codecs/max98396.c +++ b/sound/soc/codecs/max98396.c @@ -790,10 +790,22 @@ static int max98396_dai_tdm_slot(struct snd_soc_dai *dai, #define MAX98396_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 max98396_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max98396_dai_ops = { .set_fmt = max98396_dai_set_fmt, .hw_params = max98396_dai_hw_params, .set_tdm_slot = max98396_dai_tdm_slot, + .auto_selectable_formats = &max98396_selectable_formats, + .num_auto_selectable_formats = 1, }; static int max98396_dac_event(struct snd_soc_dapm_widget *w, diff --git a/sound/soc/codecs/max9850.c b/sound/soc/codecs/max9850.c index 9bd6a61212c1..c229b732121f 100644 --- a/sound/soc/codecs/max9850.c +++ b/sound/soc/codecs/max9850.c @@ -254,10 +254,21 @@ static int max9850_set_bias_level(struct snd_soc_component *component, #define MAX9850_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 max9850_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max9850_dai_ops = { .hw_params = max9850_hw_params, .set_sysclk = max9850_set_dai_sysclk, .set_fmt = max9850_set_dai_fmt, + .auto_selectable_formats = &max9850_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver max9850_dai = { diff --git a/sound/soc/codecs/max98520.c b/sound/soc/codecs/max98520.c index 4fb98505db1a..57759d85907b 100644 --- a/sound/soc/codecs/max98520.c +++ b/sound/soc/codecs/max98520.c @@ -326,10 +326,20 @@ static int max98520_dai_tdm_slot(struct snd_soc_dai *dai, #define MAX98520_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 max98520_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops max98520_dai_ops = { .set_fmt = max98520_dai_set_fmt, .hw_params = max98520_dai_hw_params, .set_tdm_slot = max98520_dai_tdm_slot, + .auto_selectable_formats = &max98520_selectable_formats, + .num_auto_selectable_formats = 1, }; static int max98520_dac_event(struct snd_soc_dapm_widget *w, diff --git a/sound/soc/codecs/max9860.c b/sound/soc/codecs/max9860.c index 20e288e38669..35265c2502fe 100644 --- a/sound/soc/codecs/max9860.c +++ b/sound/soc/codecs/max9860.c @@ -459,9 +459,32 @@ static int max9860_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) } } +static const u64 max9860_selectable_formats[] = { + /* + * DSP_A/B has some limitations. + * priority is I2S/LEFT_J > DSP_A/B + * see + * max9860_hw_params() + */ + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops max9860_dai_ops = { .hw_params = max9860_hw_params, .set_fmt = max9860_set_fmt, + .auto_selectable_formats = max9860_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(max9860_selectable_formats), }; static struct snd_soc_dai_driver max9860_dai = { diff --git a/sound/soc/codecs/max9867.c b/sound/soc/codecs/max9867.c index 07a53ec2a18a..f2214e3a19ce 100644 --- a/sound/soc/codecs/max9867.c +++ b/sound/soc/codecs/max9867.c @@ -522,12 +522,22 @@ static int max9867_dai_set_fmt(struct snd_soc_dai *codec_dai, return 0; } +static const u64 max9867_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max9867_dai_ops = { .set_sysclk = max9867_set_dai_sysclk, .set_fmt = max9867_dai_set_fmt, .mute_stream = max9867_mute, .startup = max9867_startup, .hw_params = max9867_dai_hw_params, + .auto_selectable_formats = &max9867_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/max98925.c b/sound/soc/codecs/max98925.c index 4302ab16a642..fe43ef99a0cb 100644 --- a/sound/soc/codecs/max98925.c +++ b/sound/soc/codecs/max98925.c @@ -484,10 +484,18 @@ static int max98925_dai_set_sysclk(struct snd_soc_dai *dai, #define MAX98925_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 max98925_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max98925_dai_ops = { .set_sysclk = max98925_dai_set_sysclk, .set_fmt = max98925_dai_set_fmt, .hw_params = max98925_dai_hw_params, + .auto_selectable_formats = &max98925_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver max98925_dai[] = { diff --git a/sound/soc/codecs/max98926.c b/sound/soc/codecs/max98926.c index 51a30c182167..5b590a1f00de 100644 --- a/sound/soc/codecs/max98926.c +++ b/sound/soc/codecs/max98926.c @@ -448,9 +448,17 @@ static int max98926_dai_hw_params(struct snd_pcm_substream *substream, #define MAX98926_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 max98926_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max98926_dai_ops = { .set_fmt = max98926_dai_set_fmt, .hw_params = max98926_dai_hw_params, + .auto_selectable_formats = &max98926_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver max98926_dai[] = { diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c index 524300d8fb0b..d8dfaefdca7e 100644 --- a/sound/soc/codecs/max98927.c +++ b/sound/soc/codecs/max98927.c @@ -463,11 +463,21 @@ static int max98927_dai_set_sysclk(struct snd_soc_dai *dai, return 0; } +static const u64 max98927_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops max98927_dai_ops = { .set_sysclk = max98927_dai_set_sysclk, .set_fmt = max98927_dai_set_fmt, .hw_params = max98927_dai_hw_params, .set_tdm_slot = max98927_dai_tdm_slot, + .auto_selectable_formats = &max98927_selectable_formats, + .num_auto_selectable_formats = 1, }; static int max98927_dac_event(struct snd_soc_dapm_widget *w, From 0363504f2a1b26cbba625cb382699ce7687c2ee3 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:13 +0000 Subject: [PATCH 118/924] ASoC: codecs: mc13783: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87wlt6ubb6.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/mc13783.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/codecs/mc13783.c b/sound/soc/codecs/mc13783.c index 086ac97e8386..a6231a12a9b0 100644 --- a/sound/soc/codecs/mc13783.c +++ b/sound/soc/codecs/mc13783.c @@ -637,11 +637,21 @@ static void mc13783_remove(struct snd_soc_component *component) #define MC13783_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 mc13783_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops mc13783_ops_dac = { .hw_params = mc13783_pcm_hw_params_dac, .set_fmt = mc13783_set_fmt_async, .set_sysclk = mc13783_set_sysclk_dac, .set_tdm_slot = mc13783_set_tdm_slot_dac, + .auto_selectable_formats = &mc13783_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops mc13783_ops_codec = { @@ -649,6 +659,8 @@ static const struct snd_soc_dai_ops mc13783_ops_codec = { .set_fmt = mc13783_set_fmt_async, .set_sysclk = mc13783_set_sysclk_codec, .set_tdm_slot = mc13783_set_tdm_slot_codec, + .auto_selectable_formats = &mc13783_selectable_formats, + .num_auto_selectable_formats = 1, }; /* From c9cea75cf3b3ba45d8083af57e38b680a695a2ad Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:19 +0000 Subject: [PATCH 119/924] ASoC: codecs: ml26124: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87v78qubb0.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/ml26124.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/soc/codecs/ml26124.c b/sound/soc/codecs/ml26124.c index 7209b47c2029..a94a3f9b8f90 100644 --- a/sound/soc/codecs/ml26124.c +++ b/sound/soc/codecs/ml26124.c @@ -492,11 +492,17 @@ static int ml26124_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 ml26124_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops ml26124_dai_ops = { .hw_params = ml26124_hw_params, .mute_stream = ml26124_mute, .set_fmt = ml26124_set_dai_fmt, .set_sysclk = ml26124_set_dai_sysclk, + .auto_selectable_formats = &ml26124_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; From 9ed1ee45e1005bfd52cae09e0ff07ef38752e763 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:24 +0000 Subject: [PATCH 120/924] ASoC: codecs: nau*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87tsoaubaw.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8325.c | 11 +++++++++++ sound/soc/codecs/nau8540.c | 11 +++++++++++ sound/soc/codecs/nau8810.c | 12 ++++++++++++ sound/soc/codecs/nau8821.c | 11 +++++++++++ sound/soc/codecs/nau8822.c | 12 ++++++++++++ sound/soc/codecs/nau8824.c | 11 +++++++++++ sound/soc/codecs/nau8825.c | 11 +++++++++++ 7 files changed, 79 insertions(+) diff --git a/sound/soc/codecs/nau8325.c b/sound/soc/codecs/nau8325.c index 236d94c29c24..2c95ed00a681 100644 --- a/sound/soc/codecs/nau8325.c +++ b/sound/soc/codecs/nau8325.c @@ -628,10 +628,21 @@ static const struct snd_soc_component_driver nau8325_component_driver = { .num_dapm_routes = ARRAY_SIZE(nau8325_dapm_routes), }; +static const u64 nau8325_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops nau8325_dai_ops = { .startup = nau8325_dai_startup, .hw_params = nau8325_hw_params, .set_fmt = nau8325_set_fmt, + .auto_selectable_formats = &nau8325_selectable_formats, + .num_auto_selectable_formats = 1, }; #define NAU8325_RATES SNDRV_PCM_RATE_8000_96000 diff --git a/sound/soc/codecs/nau8540.c b/sound/soc/codecs/nau8540.c index fefbd5722c00..a939f4b7bb25 100644 --- a/sound/soc/codecs/nau8540.c +++ b/sound/soc/codecs/nau8540.c @@ -623,12 +623,23 @@ static int nau8540_dai_trigger(struct snd_pcm_substream *substream, return ret; } +static const u64 nau8540_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops nau8540_dai_ops = { .startup = nau8540_dai_startup, .hw_params = nau8540_hw_params, .set_fmt = nau8540_set_fmt, .set_tdm_slot = nau8540_set_tdm_slot, .trigger = nau8540_dai_trigger, + .auto_selectable_formats = &nau8540_selectable_formats, + .num_auto_selectable_formats = 1, }; #define NAU8540_RATES SNDRV_PCM_RATE_8000_48000 diff --git a/sound/soc/codecs/nau8810.c b/sound/soc/codecs/nau8810.c index a050387dd485..eb3e93380678 100644 --- a/sound/soc/codecs/nau8810.c +++ b/sound/soc/codecs/nau8810.c @@ -817,11 +817,23 @@ static int nau8810_set_bias_level(struct snd_soc_component *component, #define NAU8810_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 nau8810_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops nau8810_ops = { .hw_params = nau8810_pcm_hw_params, .set_fmt = nau8810_set_dai_fmt, .set_sysclk = nau8810_set_sysclk, .set_pll = nau8810_set_pll, + .auto_selectable_formats = &nau8810_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver nau8810_dai = { diff --git a/sound/soc/codecs/nau8821.c b/sound/soc/codecs/nau8821.c index c45c5b864ea4..ecadbaee1037 100644 --- a/sound/soc/codecs/nau8821.c +++ b/sound/soc/codecs/nau8821.c @@ -981,11 +981,22 @@ static int nau8821_digital_mute(struct snd_soc_dai *dai, int mute, NAU8821_R31_MUTE_CTRL, NAU8821_DAC_SOFT_MUTE, val); } +static const u64 nau8821_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops nau8821_dai_ops = { .startup = nau8821_dai_startup, .hw_params = nau8821_hw_params, .set_fmt = nau8821_set_dai_fmt, .mute_stream = nau8821_digital_mute, + .auto_selectable_formats = &nau8821_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/nau8822.c b/sound/soc/codecs/nau8822.c index 830164e991a7..20913d9f7be9 100644 --- a/sound/soc/codecs/nau8822.c +++ b/sound/soc/codecs/nau8822.c @@ -1026,12 +1026,24 @@ static int nau8822_set_bias_level(struct snd_soc_component *component, #define NAU8822_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 nau8822_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops nau8822_dai_ops = { .hw_params = nau8822_hw_params, .mute_stream = nau8822_mute, .set_fmt = nau8822_set_dai_fmt, .set_sysclk = nau8822_set_dai_sysclk, .set_pll = nau8822_set_pll, + .auto_selectable_formats = &nau8822_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/nau8824.c b/sound/soc/codecs/nau8824.c index 426a488ff2a3..853eb9472af9 100644 --- a/sound/soc/codecs/nau8824.c +++ b/sound/soc/codecs/nau8824.c @@ -1580,11 +1580,22 @@ static const struct snd_soc_component_driver nau8824_component_driver = { .endianness = 1, }; +static const u64 nau8824_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops nau8824_dai_ops = { .startup = nau8824_dai_startup, .hw_params = nau8824_hw_params, .set_fmt = nau8824_set_fmt, .set_tdm_slot = nau8824_set_tdm_slot, + .auto_selectable_formats = &nau8824_selectable_formats, + .num_auto_selectable_formats = 1, }; #define NAU8824_RATES SNDRV_PCM_RATE_8000_192000 diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index ae83b778bf93..5165aeba4b67 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -1507,11 +1507,22 @@ static int nau8825_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, return 0; } +static const u64 nau8825_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops nau8825_dai_ops = { .startup = nau8825_dai_startup, .hw_params = nau8825_hw_params, .set_fmt = nau8825_set_dai_fmt, .set_tdm_slot = nau8825_set_tdm_slot, + .auto_selectable_formats = &nau8825_selectable_formats, + .num_auto_selectable_formats = 1, }; #define NAU8825_RATES SNDRV_PCM_RATE_8000_192000 From b66dcb7112d05d5a20e6ce9c2144eef92bafbf40 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:29 +0000 Subject: [PATCH 121/924] ASoC: codecs: ntp8*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87se3uubaq.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/ntp8835.c | 7 +++++++ sound/soc/codecs/ntp8918.c | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/sound/soc/codecs/ntp8835.c b/sound/soc/codecs/ntp8835.c index 5837b9379fee..39620b84e214 100644 --- a/sound/soc/codecs/ntp8835.c +++ b/sound/soc/codecs/ntp8835.c @@ -385,9 +385,16 @@ static int ntp8835_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; }; +static const u64 ntp8835_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ntp8835_dai_ops = { .hw_params = ntp8835_hw_params, .set_fmt = ntp8835_set_fmt, + .auto_selectable_formats = &ntp8835_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ntp8835_dai = { diff --git a/sound/soc/codecs/ntp8918.c b/sound/soc/codecs/ntp8918.c index a18d79aa80aa..eca34fafc4e6 100644 --- a/sound/soc/codecs/ntp8918.c +++ b/sound/soc/codecs/ntp8918.c @@ -306,10 +306,17 @@ static int ntp8918_digital_mute(struct snd_soc_dai *dai, int mute, int stream) mute_mask, mute ? mute_mask : 0); } +static const u64 ntp8918_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ntp8918_dai_ops = { .hw_params = ntp8918_hw_params, .set_fmt = ntp8918_set_fmt, .mute_stream = ntp8918_digital_mute, + .auto_selectable_formats = &ntp8918_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ntp8918_dai = { From 0c55552b5f5efc48679247a826a4f73eb3d6f5b0 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:33 +0000 Subject: [PATCH 122/924] ASoC: codecs: rk33*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87qzjeubam.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/rk3308_codec.c | 11 +++++++++++ sound/soc/codecs/rk3328_codec.c | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c index 60befe9d37f0..abed31ba660c 100644 --- a/sound/soc/codecs/rk3308_codec.c +++ b/sound/soc/codecs/rk3308_codec.c @@ -679,9 +679,20 @@ static int rk3308_codec_hw_params(struct snd_pcm_substream *substream, rk3308_codec_adc_dig_config(rk3308, params); } +static const u64 rk3308_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rk3308_codec_dai_ops = { .hw_params = rk3308_codec_hw_params, .set_fmt = rk3308_codec_set_dai_fmt, + .auto_selectable_formats = &rk3308_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rk3308_codec_dai_driver = { diff --git a/sound/soc/codecs/rk3328_codec.c b/sound/soc/codecs/rk3328_codec.c index 5871b5a81975..c2798ee94059 100644 --- a/sound/soc/codecs/rk3328_codec.c +++ b/sound/soc/codecs/rk3328_codec.c @@ -312,12 +312,21 @@ static void rk3328_pcm_shutdown(struct snd_pcm_substream *substream, rk3328_codec_close_playback(rk3328); } +static const u64 rk3328_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rk3328_dai_ops = { .hw_params = rk3328_hw_params, .set_fmt = rk3328_set_dai_fmt, .mute_stream = rk3328_mute_stream, .startup = rk3328_pcm_startup, .shutdown = rk3328_pcm_shutdown, + .auto_selectable_formats = &rk3328_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; From b1b8e119c486abeada7feb3ae304913d772b1506 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:38 +0000 Subject: [PATCH 123/924] ASoC: codecs: rtq912*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87pkyyubai.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/rtq9124.c | 9 +++++++++ sound/soc/codecs/rtq9128.c | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/sound/soc/codecs/rtq9124.c b/sound/soc/codecs/rtq9124.c index 2a041894bc0c..cc1c68752fde 100644 --- a/sound/soc/codecs/rtq9124.c +++ b/sound/soc/codecs/rtq9124.c @@ -286,10 +286,19 @@ static int rtq9124_dai_hw_params(struct snd_pcm_substream *substream, return 0; } +static const u64 rtq9124_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rtq9124_dai_ops = { .set_fmt = rtq9124_dai_set_format, .set_tdm_slot = rtq9124_dai_set_tdm_slot, .hw_params = rtq9124_dai_hw_params, + .auto_selectable_formats = &rtq9124_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rtq9124_dai_driver = { diff --git a/sound/soc/codecs/rtq9128.c b/sound/soc/codecs/rtq9128.c index 573200e5062f..9567e90c0b62 100644 --- a/sound/soc/codecs/rtq9128.c +++ b/sound/soc/codecs/rtq9128.c @@ -727,11 +727,20 @@ static int rtq9128_dai_mute_stream(struct snd_soc_dai *dai, int mute, int stream return ret < 0 ? ret : 0; } +static const u64 rtq9128_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rtq9128_dai_ops = { .set_fmt = rtq9128_dai_set_fmt, .set_tdm_slot = rtq9128_dai_set_tdm_slot, .hw_params = rtq9128_dai_hw_params, .mute_stream = rtq9128_dai_mute_stream, + .auto_selectable_formats = &rtq9128_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; From 84bf0c2e5e7a6098e52299c71f7ba070afa17b34 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:42 +0000 Subject: [PATCH 124/924] ASoC: codecs: rt*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87o6eiubad.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1011.c | 10 ++++++++++ sound/soc/codecs/rt1015.c | 10 ++++++++++ sound/soc/codecs/rt1016.c | 10 ++++++++++ sound/soc/codecs/rt1019.c | 10 ++++++++++ sound/soc/codecs/rt1305.c | 10 ++++++++++ sound/soc/codecs/rt1308.c | 10 ++++++++++ sound/soc/codecs/rt1318.c | 10 ++++++++++ sound/soc/codecs/rt274.c | 8 ++++++++ sound/soc/codecs/rt286.c | 8 ++++++++ sound/soc/codecs/rt298.c | 8 ++++++++ sound/soc/codecs/rt5514.c | 12 ++++++++++++ sound/soc/codecs/rt5616.c | 10 ++++++++++ sound/soc/codecs/rt5631.c | 10 ++++++++++ sound/soc/codecs/rt5640.c | 10 ++++++++++ sound/soc/codecs/rt5645.c | 10 ++++++++++ sound/soc/codecs/rt5651.c | 10 ++++++++++ sound/soc/codecs/rt5659.c | 10 ++++++++++ sound/soc/codecs/rt5660.c | 10 ++++++++++ sound/soc/codecs/rt5663.c | 10 ++++++++++ sound/soc/codecs/rt5665.c | 10 ++++++++++ sound/soc/codecs/rt5668.c | 22 ++++++++++++++++++++++ sound/soc/codecs/rt5670.c | 10 ++++++++++ sound/soc/codecs/rt5677.c | 10 ++++++++++ sound/soc/codecs/rt5682.c | 22 ++++++++++++++++++++++ sound/soc/codecs/rt5682s.c | 22 ++++++++++++++++++++++ sound/soc/codecs/rt9120.c | 9 +++++++++ sound/soc/codecs/rt9123.c | 13 +++++++++++++ 27 files changed, 304 insertions(+) diff --git a/sound/soc/codecs/rt1011.c b/sound/soc/codecs/rt1011.c index 15488a059283..d47b0370dd6b 100644 --- a/sound/soc/codecs/rt1011.c +++ b/sound/soc/codecs/rt1011.c @@ -2133,10 +2133,20 @@ static int rt1011_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 rt1011_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1011_aif_dai_ops = { .hw_params = rt1011_hw_params, .set_fmt = rt1011_set_dai_fmt, .set_tdm_slot = rt1011_set_tdm_slot, + .auto_selectable_formats = &rt1011_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1011_dai[] = { diff --git a/sound/soc/codecs/rt1015.c b/sound/soc/codecs/rt1015.c index ff0c08ff610e..c6d59c286064 100644 --- a/sound/soc/codecs/rt1015.c +++ b/sound/soc/codecs/rt1015.c @@ -1016,10 +1016,20 @@ static void rt1015_remove(struct snd_soc_component *component) #define RT1015_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt1015_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1015_aif_dai_ops = { .hw_params = rt1015_hw_params, .set_fmt = rt1015_set_dai_fmt, .set_tdm_slot = rt1015_set_tdm_slot, + .auto_selectable_formats = &rt1015_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1015_dai[] = { diff --git a/sound/soc/codecs/rt1016.c b/sound/soc/codecs/rt1016.c index 11c8c45574a1..56a2f3d3354d 100644 --- a/sound/soc/codecs/rt1016.c +++ b/sound/soc/codecs/rt1016.c @@ -534,9 +534,19 @@ static void rt1016_remove(struct snd_soc_component *component) #define RT1016_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt1016_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1016_aif_dai_ops = { .hw_params = rt1016_hw_params, .set_fmt = rt1016_set_dai_fmt, + .auto_selectable_formats = &rt1016_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1016_dai[] = { diff --git a/sound/soc/codecs/rt1019.c b/sound/soc/codecs/rt1019.c index 370521149ac8..3998abccd93a 100644 --- a/sound/soc/codecs/rt1019.c +++ b/sound/soc/codecs/rt1019.c @@ -492,12 +492,22 @@ static int rt1019_probe(struct snd_soc_component *component) #define RT1019_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt1019_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1019_aif_dai_ops = { .hw_params = rt1019_hw_params, .set_fmt = rt1019_set_dai_fmt, .set_sysclk = rt1019_set_dai_sysclk, .set_pll = rt1019_set_dai_pll, .set_tdm_slot = rt1019_set_tdm_slot, + .auto_selectable_formats = &rt1019_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1019_dai[] = { diff --git a/sound/soc/codecs/rt1305.c b/sound/soc/codecs/rt1305.c index 2d5fad76cf52..92998ec0a392 100644 --- a/sound/soc/codecs/rt1305.c +++ b/sound/soc/codecs/rt1305.c @@ -910,9 +910,19 @@ static int rt1305_resume(struct snd_soc_component *component) SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 rt1305_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1305_aif_dai_ops = { .hw_params = rt1305_hw_params, .set_fmt = rt1305_set_dai_fmt, + .auto_selectable_formats = &rt1305_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1305_dai[] = { diff --git a/sound/soc/codecs/rt1308.c b/sound/soc/codecs/rt1308.c index 630946cd7cdf..28c25871ec0c 100644 --- a/sound/soc/codecs/rt1308.c +++ b/sound/soc/codecs/rt1308.c @@ -729,9 +729,19 @@ static int rt1308_resume(struct snd_soc_component *component) SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 rt1308_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1308_aif_dai_ops = { .hw_params = rt1308_hw_params, .set_fmt = rt1308_set_dai_fmt, + .auto_selectable_formats = &rt1308_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1308_dai[] = { diff --git a/sound/soc/codecs/rt1318.c b/sound/soc/codecs/rt1318.c index 55607f1b9a1e..5f61de9e2ff9 100644 --- a/sound/soc/codecs/rt1318.c +++ b/sound/soc/codecs/rt1318.c @@ -1087,12 +1087,22 @@ static int rt1318_resume(struct snd_soc_component *component) #define RT1318_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt1318_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1318_aif_dai_ops = { .hw_params = rt1318_hw_params, .set_fmt = rt1318_set_dai_fmt, .set_sysclk = rt1318_set_dai_sysclk, .set_pll = rt1318_set_dai_pll, .set_tdm_slot = rt1318_set_tdm_slot, + .auto_selectable_formats = &rt1318_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1318_dai[] = { diff --git a/sound/soc/codecs/rt274.c b/sound/soc/codecs/rt274.c index f8c370106504..62fc071d646a 100644 --- a/sound/soc/codecs/rt274.c +++ b/sound/soc/codecs/rt274.c @@ -1027,6 +1027,12 @@ static int rt274_resume(struct snd_soc_component *component) #define RT274_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt274_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rt274_aif_dai_ops = { .hw_params = rt274_hw_params, .set_fmt = rt274_set_dai_fmt, @@ -1034,6 +1040,8 @@ static const struct snd_soc_dai_ops rt274_aif_dai_ops = { .set_pll = rt274_set_dai_pll, .set_bclk_ratio = rt274_set_bclk_ratio, .set_tdm_slot = rt274_set_tdm_slot, + .auto_selectable_formats = &rt274_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt274_dai[] = { diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index 4217467904d6..51b407b78e6d 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -995,11 +995,19 @@ static int rt286_resume(struct snd_soc_component *component) #define RT286_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt286_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rt286_aif_dai_ops = { .hw_params = rt286_hw_params, .set_fmt = rt286_set_dai_fmt, .set_sysclk = rt286_set_dai_sysclk, .set_bclk_ratio = rt286_set_bclk_ratio, + .auto_selectable_formats = &rt286_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt286_dai[] = { diff --git a/sound/soc/codecs/rt298.c b/sound/soc/codecs/rt298.c index 09aed08b5b79..72407fec0f34 100644 --- a/sound/soc/codecs/rt298.c +++ b/sound/soc/codecs/rt298.c @@ -1056,11 +1056,19 @@ static int rt298_resume(struct snd_soc_component *component) #define RT298_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt298_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rt298_aif_dai_ops = { .hw_params = rt298_hw_params, .set_fmt = rt298_set_dai_fmt, .set_sysclk = rt298_set_dai_sysclk, .set_bclk_ratio = rt298_set_bclk_ratio, + .auto_selectable_formats = &rt298_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt298_dai[] = { diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c index 58aecd4c83a6..8dad8ba0fe0f 100644 --- a/sound/soc/codecs/rt5514.c +++ b/sound/soc/codecs/rt5514.c @@ -1142,12 +1142,24 @@ static int rt5514_i2c_write(void *context, unsigned int reg, unsigned int val) #define RT5514_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5514_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rt5514_aif_dai_ops = { .hw_params = rt5514_hw_params, .set_fmt = rt5514_set_dai_fmt, .set_sysclk = rt5514_set_dai_sysclk, .set_pll = rt5514_set_dai_pll, .set_tdm_slot = rt5514_set_tdm_slot, + .auto_selectable_formats = &rt5514_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5514_dai[] = { diff --git a/sound/soc/codecs/rt5616.c b/sound/soc/codecs/rt5616.c index 46ee412dc81d..3f9d97d04d48 100644 --- a/sound/soc/codecs/rt5616.c +++ b/sound/soc/codecs/rt5616.c @@ -1260,11 +1260,21 @@ static int rt5616_resume(struct snd_soc_component *component) #define RT5616_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5616_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5616_aif_dai_ops = { .hw_params = rt5616_hw_params, .set_fmt = rt5616_set_dai_fmt, .set_sysclk = rt5616_set_dai_sysclk, .set_pll = rt5616_set_dai_pll, + .auto_selectable_formats = &rt5616_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5616_dai[] = { diff --git a/sound/soc/codecs/rt5631.c b/sound/soc/codecs/rt5631.c index ed1233973eb8..f97fd24e4229 100644 --- a/sound/soc/codecs/rt5631.c +++ b/sound/soc/codecs/rt5631.c @@ -1626,11 +1626,21 @@ static int rt5631_probe(struct snd_soc_component *component) SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S8) +static const u64 rt5631_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5631_ops = { .hw_params = rt5631_hifi_pcm_params, .set_fmt = rt5631_hifi_codec_set_dai_fmt, .set_sysclk = rt5631_hifi_codec_set_dai_sysclk, .set_pll = rt5631_codec_set_dai_pll, + .auto_selectable_formats = &rt5631_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5631_dai[] = { diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c index 74fd05176eff..4fa1eee8e87f 100644 --- a/sound/soc/codecs/rt5640.c +++ b/sound/soc/codecs/rt5640.c @@ -2877,11 +2877,21 @@ static int rt5640_resume(struct snd_soc_component *component) #define RT5640_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5640_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5640_aif_dai_ops = { .hw_params = rt5640_hw_params, .set_fmt = rt5640_set_dai_fmt, .set_sysclk = rt5640_set_dai_sysclk, .set_pll = rt5640_set_dai_pll, + .auto_selectable_formats = &rt5640_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5640_dai[] = { diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 7313ea71c958..bf9f278c3de9 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3541,12 +3541,22 @@ static int rt5645_resume(struct snd_soc_component *component) #define RT5645_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5645_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5645_aif_dai_ops = { .hw_params = rt5645_hw_params, .set_fmt = rt5645_set_dai_fmt, .set_sysclk = rt5645_set_dai_sysclk, .set_tdm_slot = rt5645_set_tdm_slot, .set_pll = rt5645_set_dai_pll, + .auto_selectable_formats = &rt5645_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5645_dai[] = { diff --git a/sound/soc/codecs/rt5651.c b/sound/soc/codecs/rt5651.c index 5c729135f71b..9723b4c7540c 100644 --- a/sound/soc/codecs/rt5651.c +++ b/sound/soc/codecs/rt5651.c @@ -2102,11 +2102,21 @@ static int rt5651_resume(struct snd_soc_component *component) #define RT5651_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5651_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5651_aif_dai_ops = { .hw_params = rt5651_hw_params, .set_fmt = rt5651_set_dai_fmt, .set_sysclk = rt5651_set_dai_sysclk, .set_pll = rt5651_set_dai_pll, + .auto_selectable_formats = &rt5651_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5651_dai[] = { diff --git a/sound/soc/codecs/rt5659.c b/sound/soc/codecs/rt5659.c index 3097ee6d4e89..3d782f41ccdd 100644 --- a/sound/soc/codecs/rt5659.c +++ b/sound/soc/codecs/rt5659.c @@ -3716,11 +3716,21 @@ static int rt5659_resume(struct snd_soc_component *component) #define RT5659_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5659_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5659_aif_dai_ops = { .hw_params = rt5659_hw_params, .set_fmt = rt5659_set_dai_fmt, .set_tdm_slot = rt5659_set_tdm_slot, .set_bclk_ratio = rt5659_set_bclk_ratio, + .auto_selectable_formats = &rt5659_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5659_dai[] = { diff --git a/sound/soc/codecs/rt5660.c b/sound/soc/codecs/rt5660.c index edf2e3e346e4..807604120d28 100644 --- a/sound/soc/codecs/rt5660.c +++ b/sound/soc/codecs/rt5660.c @@ -1161,11 +1161,21 @@ static int rt5660_resume(struct snd_soc_component *component) #define RT5660_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5660_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5660_aif_dai_ops = { .hw_params = rt5660_hw_params, .set_fmt = rt5660_set_dai_fmt, .set_sysclk = rt5660_set_dai_sysclk, .set_pll = rt5660_set_dai_pll, + .auto_selectable_formats = &rt5660_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5660_dai[] = { diff --git a/sound/soc/codecs/rt5663.c b/sound/soc/codecs/rt5663.c index 262d3bba1f3d..ec46e6180cc0 100644 --- a/sound/soc/codecs/rt5663.c +++ b/sound/soc/codecs/rt5663.c @@ -3222,6 +3222,14 @@ static int rt5663_resume(struct snd_soc_component *component) #define RT5663_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5663_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5663_aif_dai_ops = { .hw_params = rt5663_hw_params, .set_fmt = rt5663_set_dai_fmt, @@ -3229,6 +3237,8 @@ static const struct snd_soc_dai_ops rt5663_aif_dai_ops = { .set_pll = rt5663_set_dai_pll, .set_tdm_slot = rt5663_set_tdm_slot, .set_bclk_ratio = rt5663_set_bclk_ratio, + .auto_selectable_formats = &rt5663_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5663_dai[] = { diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c index 1d987472ba42..9545758f58d6 100644 --- a/sound/soc/codecs/rt5665.c +++ b/sound/soc/codecs/rt5665.c @@ -4401,11 +4401,21 @@ static int rt5665_resume(struct snd_soc_component *component) #define RT5665_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5665_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5665_aif_dai_ops = { .hw_params = rt5665_hw_params, .set_fmt = rt5665_set_dai_fmt, .set_tdm_slot = rt5665_set_tdm_slot, .set_bclk_ratio = rt5665_set_bclk_ratio, + .auto_selectable_formats = &rt5665_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5665_dai[] = { diff --git a/sound/soc/codecs/rt5668.c b/sound/soc/codecs/rt5668.c index 4154bad27610..cd6245d6b91a 100644 --- a/sound/soc/codecs/rt5668.c +++ b/sound/soc/codecs/rt5668.c @@ -2254,16 +2254,38 @@ static int rt5668_resume(struct snd_soc_component *component) #define RT5668_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5668_selectable_formats_aif1 = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + +static const u64 rt5668_selectable_formats_aif2 = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5668_aif1_dai_ops = { .hw_params = rt5668_hw_params, .set_fmt = rt5668_set_dai_fmt, .set_tdm_slot = rt5668_set_tdm_slot, + .auto_selectable_formats = &rt5668_selectable_formats_aif1, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops rt5668_aif2_dai_ops = { .hw_params = rt5668_hw_params, .set_fmt = rt5668_set_dai_fmt, .set_bclk_ratio = rt5668_set_bclk_ratio, + .auto_selectable_formats = &rt5668_selectable_formats_aif2, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5668_dai[] = { diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index 1d120b5dc2be..f772f093479a 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -2791,12 +2791,22 @@ static int rt5670_resume(struct snd_soc_component *component) #define RT5670_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5670_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5670_aif_dai_ops = { .hw_params = rt5670_hw_params, .set_fmt = rt5670_set_dai_fmt, .set_tdm_slot = rt5670_set_tdm_slot, .set_pll = rt5670_set_dai_pll, .set_bclk_ratio = rt5670_set_bclk_ratio, + .auto_selectable_formats = &rt5670_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5670_dai[] = { diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 4757017cc83d..7b80922236e5 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5031,12 +5031,22 @@ static int rt5677_write(void *context, unsigned int reg, unsigned int val) #define RT5677_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5677_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5677_aif_dai_ops = { .hw_params = rt5677_hw_params, .set_fmt = rt5677_set_dai_fmt, .set_sysclk = rt5677_set_dai_sysclk, .set_pll = rt5677_set_dai_pll, .set_tdm_slot = rt5677_set_tdm_slot, + .auto_selectable_formats = &rt5677_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops rt5677_dsp_dai_ops = { diff --git a/sound/soc/codecs/rt5682.c b/sound/soc/codecs/rt5682.c index 6f7e68c20d13..1aceb08b280d 100644 --- a/sound/soc/codecs/rt5682.c +++ b/sound/soc/codecs/rt5682.c @@ -3043,11 +3043,31 @@ static int rt5682_resume(struct snd_soc_component *component) #define rt5682_resume NULL #endif +static const u64 rt5682_selectable_formats_aif1 = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + +static const u64 rt5682_selectable_formats_aif2 = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + const struct snd_soc_dai_ops rt5682_aif1_dai_ops = { .hw_params = rt5682_hw_params, .set_fmt = rt5682_set_dai_fmt, .set_tdm_slot = rt5682_set_tdm_slot, .set_bclk_ratio = rt5682_set_bclk1_ratio, + .auto_selectable_formats = &rt5682_selectable_formats_aif1, + .num_auto_selectable_formats = 1, }; EXPORT_SYMBOL_GPL(rt5682_aif1_dai_ops); @@ -3055,6 +3075,8 @@ const struct snd_soc_dai_ops rt5682_aif2_dai_ops = { .hw_params = rt5682_hw_params, .set_fmt = rt5682_set_dai_fmt, .set_bclk_ratio = rt5682_set_bclk2_ratio, + .auto_selectable_formats = &rt5682_selectable_formats_aif2, + .num_auto_selectable_formats = 1, }; EXPORT_SYMBOL_GPL(rt5682_aif2_dai_ops); diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c index 34997cfb465b..6dd0d36a7186 100644 --- a/sound/soc/codecs/rt5682s.c +++ b/sound/soc/codecs/rt5682s.c @@ -2926,17 +2926,39 @@ static int rt5682s_resume(struct snd_soc_component *component) #define rt5682s_resume NULL #endif +static const u64 rt5682s_selectable_formats_aif1 = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + +static const u64 rt5682s_selectable_formats_aif2 = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5682s_aif1_dai_ops = { .hw_params = rt5682s_hw_params, .set_fmt = rt5682s_set_dai_fmt, .set_tdm_slot = rt5682s_set_tdm_slot, .set_bclk_ratio = rt5682s_set_bclk1_ratio, + .auto_selectable_formats = &rt5682s_selectable_formats_aif1, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops rt5682s_aif2_dai_ops = { .hw_params = rt5682s_hw_params, .set_fmt = rt5682s_set_dai_fmt, .set_bclk_ratio = rt5682s_set_bclk2_ratio, + .auto_selectable_formats = &rt5682s_selectable_formats_aif2, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_component_driver rt5682s_soc_component_dev = { diff --git a/sound/soc/codecs/rt9120.c b/sound/soc/codecs/rt9120.c index e51cd4a31894..1445e823c4ff 100644 --- a/sound/soc/codecs/rt9120.c +++ b/sound/soc/codecs/rt9120.c @@ -289,9 +289,18 @@ static int rt9120_hw_params(struct snd_pcm_substream *substream, return 0; } +static const u64 rt9120_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rt9120_dai_ops = { .set_fmt = rt9120_set_fmt, .hw_params = rt9120_hw_params, + .auto_selectable_formats = &rt9120_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt9120_dai = { diff --git a/sound/soc/codecs/rt9123.c b/sound/soc/codecs/rt9123.c index 07eaf275c9e9..251bad450ec8 100644 --- a/sound/soc/codecs/rt9123.c +++ b/sound/soc/codecs/rt9123.c @@ -299,10 +299,23 @@ static int rt9123_dai_hw_params(struct snd_pcm_substream *substream, return 0; } +/* + * NOTE + * + * auto_select doesn't include _DSP_A / _DSP_B, because it has limitaion for TDM + * (= rt9123_dai_hw_params()). It can be selected via Card directly if needed. + */ +static const u64 rt9123_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops rt9123_dai_ops = { .set_fmt = rt9123_dai_set_format, .set_tdm_slot = rt9123_dai_set_tdm_slot, .hw_params = rt9123_dai_hw_params, + .auto_selectable_formats = &rt9123_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt9123_dai_driver = { From f710416f4d8b9502dc28befadacc148d541fe718 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:46 +0000 Subject: [PATCH 125/924] ASoC: codecs: sgtl5000: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87mru2uba9.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/sgtl5000.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index 35df1a8c4c8c..97bea75ed24f 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -1158,12 +1158,23 @@ static int sgtl5000_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_FMTBIT_S24_LE |\ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 sgtl5000_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops sgtl5000_ops = { .hw_params = sgtl5000_pcm_hw_params, .mute_stream = sgtl5000_mute_stream, .set_fmt = sgtl5000_set_dai_fmt, .set_sysclk = sgtl5000_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &sgtl5000_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver sgtl5000_dai = { From 9d6aa5e50a8b477a1f387d47ce9e1cb2f42d78f7 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:50 +0000 Subject: [PATCH 126/924] ASoC: codecs: si476x: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ld9muba5.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/si476x.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/sound/soc/codecs/si476x.c b/sound/soc/codecs/si476x.c index d87141ba8438..9b904450ab8a 100644 --- a/sound/soc/codecs/si476x.c +++ b/sound/soc/codecs/si476x.c @@ -199,9 +199,27 @@ static int si476x_codec_hw_params(struct snd_pcm_substream *substream, return err; } +static const u64 si476x_selectable_formats[] = { + /* Hi Priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* Low Priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops si476x_dai_ops = { .hw_params = si476x_codec_hw_params, .set_fmt = si476x_codec_set_dai_fmt, + .auto_selectable_formats = si476x_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(si476x_selectable_formats), }; static struct snd_soc_dai_driver si476x_dai = { From 793b720dbf4fe70b7078ea6a93dc6792c510612f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:54 +0000 Subject: [PATCH 127/924] ASoC: codecs: sma130*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87jyp6uba2.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/sma1303.c | 13 +++++++++++++ sound/soc/codecs/sma1307.c | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/sound/soc/codecs/sma1303.c b/sound/soc/codecs/sma1303.c index 15758eb13f8c..0bff32559714 100644 --- a/sound/soc/codecs/sma1303.c +++ b/sound/soc/codecs/sma1303.c @@ -1412,12 +1412,25 @@ static int sma1303_dai_set_tdm_slot(struct snd_soc_dai *dai, return 0; } +static const u64 sma1303_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops sma1303_dai_ops_amp = { .set_sysclk = sma1303_dai_set_sysclk_amp, .set_fmt = sma1303_dai_set_fmt_amp, .hw_params = sma1303_dai_hw_params_amp, .mute_stream = sma1303_dai_mute, .set_tdm_slot = sma1303_dai_set_tdm_slot, + .auto_selectable_formats = &sma1303_selectable_formats, + .num_auto_selectable_formats = 1, }; #define SMA1303_RATES SNDRV_PCM_RATE_8000_192000 diff --git a/sound/soc/codecs/sma1307.c b/sound/soc/codecs/sma1307.c index adb369a29b9d..ac55bf6f927b 100644 --- a/sound/soc/codecs/sma1307.c +++ b/sound/soc/codecs/sma1307.c @@ -1552,12 +1552,25 @@ static int sma1307_dai_mute_stream(struct snd_soc_dai *dai, int mute, return 0; } +static const u64 sma1307_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops sma1307_dai_ops_amp = { .hw_params = sma1307_dai_hw_params_amp, .set_fmt = sma1307_dai_set_fmt_amp, .set_sysclk = sma1307_dai_set_sysclk_amp, .set_tdm_slot = sma1307_dai_set_tdm_slot, .mute_stream = sma1307_dai_mute_stream, + .auto_selectable_formats = &sma1307_selectable_formats, + .num_auto_selectable_formats = 1, }; #define SMA1307_RATES_PLAYBACK SNDRV_PCM_RATE_8000_96000 From dd711abc375025ed9f77d780f65845a25cb3bbff Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:08:57 +0000 Subject: [PATCH 128/924] ASoC: codecs: src4xxx: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ik4qub9y.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/src4xxx.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/codecs/src4xxx.c b/sound/soc/codecs/src4xxx.c index 5a3489475225..6e187c930372 100644 --- a/sound/soc/codecs/src4xxx.c +++ b/sound/soc/codecs/src4xxx.c @@ -326,10 +326,18 @@ static int src4xxx_hw_params(struct snd_pcm_substream *substream, return 0; }; +static const u64 src4xxx_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops src4xxx_dai_ops = { .hw_params = src4xxx_hw_params, .set_sysclk = src4xxx_set_mclk_hz, .set_fmt = src4xxx_set_dai_fmt, + .auto_selectable_formats = &src4xxx_selectable_formats, + .num_auto_selectable_formats = 1, }; #define SRC4XXX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) From dec1b4abfbd3a1f8d505dacc257589530e7be9fa Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:09:01 +0000 Subject: [PATCH 129/924] ASoC: codecs: ssm*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87h5kaub9u.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2518.c | 13 +++++++++++++ sound/soc/codecs/ssm2602.c | 13 +++++++++++++ sound/soc/codecs/ssm3515.c | 10 ++++++++++ sound/soc/codecs/ssm4567.c | 13 +++++++++++++ 4 files changed, 49 insertions(+) diff --git a/sound/soc/codecs/ssm2518.c b/sound/soc/codecs/ssm2518.c index 5192569ba6a8..32e81b31479d 100644 --- a/sound/soc/codecs/ssm2518.c +++ b/sound/soc/codecs/ssm2518.c @@ -620,12 +620,25 @@ static int ssm2518_startup(struct snd_pcm_substream *substream, #define SSM2518_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32) +static const u64 ssm2518_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops ssm2518_dai_ops = { .startup = ssm2518_startup, .hw_params = ssm2518_hw_params, .mute_stream = ssm2518_mute, .set_fmt = ssm2518_set_dai_fmt, .set_tdm_slot = ssm2518_set_tdm_slot, + .auto_selectable_formats = &ssm2518_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index fccd2eacd7a6..92df44f86915 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -523,12 +523,25 @@ static int ssm2602_set_bias_level(struct snd_soc_component *component, #define SSM2602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 ssm2602_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops ssm2602_dai_ops = { .startup = ssm2602_startup, .hw_params = ssm2602_hw_params, .mute_stream = ssm2602_mute, .set_sysclk = ssm2602_set_dai_sysclk, .set_fmt = ssm2602_set_dai_fmt, + .auto_selectable_formats = &ssm2602_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/ssm3515.c b/sound/soc/codecs/ssm3515.c index 8c6665677a17..f71509237fca 100644 --- a/sound/soc/codecs/ssm3515.c +++ b/sound/soc/codecs/ssm3515.c @@ -356,12 +356,22 @@ static int ssm3515_hw_free(struct snd_pcm_substream *substream, return 0; } +static const u64 ssm3515_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops ssm3515_dai_ops = { .mute_stream = ssm3515_mute, .hw_params = ssm3515_hw_params, .set_fmt = ssm3515_set_fmt, .set_tdm_slot = ssm3515_set_tdm_slot, .hw_free = ssm3515_hw_free, + .auto_selectable_formats = &ssm3515_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ssm3515_dai_driver = { diff --git a/sound/soc/codecs/ssm4567.c b/sound/soc/codecs/ssm4567.c index 8415dd163edd..181047b38d3d 100644 --- a/sound/soc/codecs/ssm4567.c +++ b/sound/soc/codecs/ssm4567.c @@ -389,11 +389,24 @@ static int ssm4567_set_bias_level(struct snd_soc_component *component, return ret; } +static const u64 ssm4567_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_PDM | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops ssm4567_dai_ops = { .hw_params = ssm4567_hw_params, .mute_stream = ssm4567_mute, .set_fmt = ssm4567_set_dai_fmt, .set_tdm_slot = ssm4567_set_tdm_slot, + .auto_selectable_formats = &ssm4567_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; From 9f349fafa7693ae0d2b13a39886f894b3cec7cb5 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:09:05 +0000 Subject: [PATCH 130/924] ASoC: codecs: sta*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87fqzuub9q.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/sta32x.c | 9 +++++++++ sound/soc/codecs/sta350.c | 9 +++++++++ sound/soc/codecs/sta529.c | 7 +++++++ 3 files changed, 25 insertions(+) diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c index d6de739212f9..9a9a68c722cd 100644 --- a/sound/soc/codecs/sta32x.c +++ b/sound/soc/codecs/sta32x.c @@ -848,10 +848,19 @@ static int sta32x_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 sta32x_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF; + static const struct snd_soc_dai_ops sta32x_dai_ops = { .hw_params = sta32x_hw_params, .set_sysclk = sta32x_set_dai_sysclk, .set_fmt = sta32x_set_dai_fmt, + .auto_selectable_formats = &sta32x_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver sta32x_dai = { diff --git a/sound/soc/codecs/sta350.c b/sound/soc/codecs/sta350.c index 2ba35076732b..d6d29d291081 100644 --- a/sound/soc/codecs/sta350.c +++ b/sound/soc/codecs/sta350.c @@ -882,10 +882,19 @@ static int sta350_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 sta350_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF; + static const struct snd_soc_dai_ops sta350_dai_ops = { .hw_params = sta350_hw_params, .set_sysclk = sta350_set_dai_sysclk, .set_fmt = sta350_set_dai_fmt, + .auto_selectable_formats = &sta350_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver sta350_dai = { diff --git a/sound/soc/codecs/sta529.c b/sound/soc/codecs/sta529.c index 7d57999f9cb1..0faf439ffee9 100644 --- a/sound/soc/codecs/sta529.c +++ b/sound/soc/codecs/sta529.c @@ -286,10 +286,17 @@ static int sta529_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt) return 0; } +static const u64 sta529_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops sta529_dai_ops = { .hw_params = sta529_hw_params, .set_fmt = sta529_set_dai_fmt, .mute_stream = sta529_mute, + .auto_selectable_formats = &sta529_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; From 122b339a5a0b017996dce1dd907f7541cdbae27e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:09:09 +0000 Subject: [PATCH 131/924] ASoC: codecs: tas*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ecfeub9m.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2552.c | 14 ++++++++++++++ sound/soc/codecs/tas2562.c | 10 ++++++++++ sound/soc/codecs/tas2764.c | 12 ++++++++++++ sound/soc/codecs/tas2770.c | 12 ++++++++++++ sound/soc/codecs/tas2780.c | 10 ++++++++++ sound/soc/codecs/tas5086.c | 7 +++++++ sound/soc/codecs/tas571x.c | 7 +++++++ sound/soc/codecs/tas5720.c | 9 +++++++++ sound/soc/codecs/tas6424.c | 9 +++++++++ 9 files changed, 90 insertions(+) diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c index be2c9a0fa529..beb5654ac4b4 100644 --- a/sound/soc/codecs/tas2552.c +++ b/sound/soc/codecs/tas2552.c @@ -518,6 +518,18 @@ static const struct dev_pm_ops tas2552_pm = { RUNTIME_PM_OPS(tas2552_runtime_suspend, tas2552_runtime_resume, NULL) }; +static const u64 tas2552_selectable_formats[] = { + /* Hi Priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF, + /* Low Priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops tas2552_speaker_dai_ops = { .hw_params = tas2552_hw_params, .prepare = tas2552_prepare, @@ -525,6 +537,8 @@ static const struct snd_soc_dai_ops tas2552_speaker_dai_ops = { .set_fmt = tas2552_set_dai_fmt, .set_tdm_slot = tas2552_set_dai_tdm_slot, .mute_stream = tas2552_mute, + .auto_selectable_formats = tas2552_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(tas2552_selectable_formats), .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas2562.c b/sound/soc/codecs/tas2562.c index 0b8644ad74f1..1fe17b0895fc 100644 --- a/sound/soc/codecs/tas2562.c +++ b/sound/soc/codecs/tas2562.c @@ -604,11 +604,21 @@ static const struct snd_soc_component_driver soc_component_dev_tas2562 = { .endianness = 1, }; +static const u64 tas2562_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops tas2562_speaker_dai_ops = { .hw_params = tas2562_hw_params, .set_fmt = tas2562_set_dai_fmt, .set_tdm_slot = tas2562_set_dai_tdm_slot, .mute_stream = tas2562_mute, + .auto_selectable_formats = &tas2562_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c index f9c65c9f6d0a..44ac55f84beb 100644 --- a/sound/soc/codecs/tas2764.c +++ b/sound/soc/codecs/tas2764.c @@ -602,6 +602,16 @@ static int tas2764_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) return tas2764_write_sdout_idle_mask(tas2764, tas2764->idle_slot_config.tx_mask); } +static const u64 tas2764_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops tas2764_dai_ops = { .mute_stream = tas2764_mute, .hw_params = tas2764_hw_params, @@ -609,6 +619,8 @@ static const struct snd_soc_dai_ops tas2764_dai_ops = { .set_bclk_ratio = tas2764_set_bclk_ratio, .set_tdm_slot = tas2764_set_dai_tdm_slot, .set_tdm_idle = tas2764_set_dai_tdm_idle, + .auto_selectable_formats = &tas2764_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c index e79754edac5c..59912ed9f4a5 100644 --- a/sound/soc/codecs/tas2770.c +++ b/sound/soc/codecs/tas2770.c @@ -566,12 +566,24 @@ static int tas2770_set_dai_tdm_idle(struct snd_soc_dai *dai, return 0; } +static const u64 tas2770_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops tas2770_dai_ops = { .mute_stream = tas2770_mute, .hw_params = tas2770_hw_params, .set_fmt = tas2770_set_fmt, .set_tdm_slot = tas2770_set_dai_tdm_slot, .set_tdm_idle = tas2770_set_dai_tdm_idle, + .auto_selectable_formats = &tas2770_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas2780.c b/sound/soc/codecs/tas2780.c index f1fce4305fa1..991d0c0efee2 100644 --- a/sound/soc/codecs/tas2780.c +++ b/sound/soc/codecs/tas2780.c @@ -441,11 +441,21 @@ static int tas2780_set_dai_tdm_slot(struct snd_soc_dai *dai, return ret; } +static const u64 tas2780_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops tas2780_dai_ops = { .mute_stream = tas2780_mute, .hw_params = tas2780_hw_params, .set_fmt = tas2780_set_fmt, .set_tdm_slot = tas2780_set_dai_tdm_slot, + .auto_selectable_formats = &tas2780_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas5086.c b/sound/soc/codecs/tas5086.c index 4d1c122c8f04..1ab82ef1c41d 100644 --- a/sound/soc/codecs/tas5086.c +++ b/sound/soc/codecs/tas5086.c @@ -743,11 +743,18 @@ static const struct snd_soc_dapm_route tas5086_dapm_routes[] = { }; +static const u64 tas5086_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops tas5086_dai_ops = { .hw_params = tas5086_hw_params, .set_sysclk = tas5086_set_dai_sysclk, .set_fmt = tas5086_set_dai_fmt, .mute_stream = tas5086_mute_stream, + .auto_selectable_formats = &tas5086_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver tas5086_dai = { diff --git a/sound/soc/codecs/tas571x.c b/sound/soc/codecs/tas571x.c index 8b5f9accf120..5e2560347461 100644 --- a/sound/soc/codecs/tas571x.c +++ b/sound/soc/codecs/tas571x.c @@ -352,10 +352,17 @@ static int tas571x_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 tas571x_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops tas571x_dai_ops = { .set_fmt = tas571x_set_dai_fmt, .hw_params = tas571x_hw_params, .mute_stream = tas571x_mute, + .auto_selectable_formats = &tas571x_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas5720.c b/sound/soc/codecs/tas5720.c index 0bbcfaadf3f2..2539a097e743 100644 --- a/sound/soc/codecs/tas5720.c +++ b/sound/soc/codecs/tas5720.c @@ -682,11 +682,20 @@ static const struct snd_soc_component_driver soc_component_dev_tas5722 = { #define TAS5720_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE |\ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) +static const u64 tas5720_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops tas5720_speaker_dai_ops = { .hw_params = tas5720_hw_params, .set_fmt = tas5720_set_dai_fmt, .set_tdm_slot = tas5720_set_dai_tdm_slot, .mute_stream = tas5720_mute, + .auto_selectable_formats = &tas5720_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas6424.c b/sound/soc/codecs/tas6424.c index f5d50f8a1cfb..2718183e864c 100644 --- a/sound/soc/codecs/tas6424.c +++ b/sound/soc/codecs/tas6424.c @@ -378,11 +378,20 @@ static const struct snd_soc_component_driver soc_codec_dev_tas6424 = { .endianness = 1, }; +static const u64 tas6424_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops tas6424_speaker_dai_ops = { .hw_params = tas6424_hw_params, .set_fmt = tas6424_set_dai_fmt, .set_tdm_slot = tas6424_set_dai_tdm_slot, .mute_stream = tas6424_mute, + .auto_selectable_formats = &tas6424_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; From 5759273b853c3d248a03d952eba5d30e08da114b Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:09:13 +0000 Subject: [PATCH 132/924] ASoC: codecs: tfa9879: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87cxuyub9i.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/tfa9879.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/codecs/tfa9879.c b/sound/soc/codecs/tfa9879.c index f30479f1f53b..519103731768 100644 --- a/sound/soc/codecs/tfa9879.c +++ b/sound/soc/codecs/tfa9879.c @@ -248,10 +248,19 @@ static const struct regmap_config tfa9879_regmap = { .cache_type = REGCACHE_RBTREE, }; +static const u64 tfa9879_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops tfa9879_dai_ops = { .hw_params = tfa9879_hw_params, .mute_stream = tfa9879_mute_stream, .set_fmt = tfa9879_set_fmt, + .auto_selectable_formats = &tfa9879_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; From cb125dde4b5d4bd2395565c09611b39b2de89f5b Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:09:17 +0000 Subject: [PATCH 133/924] ASoC: codecs: tlv320*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87bjaiub9e.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320adc3xxx.c | 14 ++++++++++++++ sound/soc/codecs/tlv320adcx140.c | 12 ++++++++++++ sound/soc/codecs/tlv320aic23.c | 9 +++++++++ sound/soc/codecs/tlv320aic26.c | 8 ++++++++ sound/soc/codecs/tlv320aic31xx.c | 11 +++++++++++ sound/soc/codecs/tlv320aic32x4.c | 8 ++++++++ sound/soc/codecs/tlv320aic3x.c | 14 ++++++++++++++ sound/soc/codecs/tlv320dac33.c | 8 ++++++++ 8 files changed, 84 insertions(+) diff --git a/sound/soc/codecs/tlv320adc3xxx.c b/sound/soc/codecs/tlv320adc3xxx.c index d7d958ecd8db..528c44446482 100644 --- a/sound/soc/codecs/tlv320adc3xxx.c +++ b/sound/soc/codecs/tlv320adc3xxx.c @@ -1362,10 +1362,24 @@ static int adc3xxx_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) return 0; } +static const u64 adc3xxx_selectable_formats[] = { + /* Hi Priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF, + /* Low Priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops adc3xxx_dai_ops = { .hw_params = adc3xxx_hw_params, .set_sysclk = adc3xxx_set_dai_sysclk, .set_fmt = adc3xxx_set_dai_fmt, + .auto_selectable_formats = adc3xxx_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(adc3xxx_selectable_formats), }; static struct snd_soc_dai_driver adc3xxx_dai = { diff --git a/sound/soc/codecs/tlv320adcx140.c b/sound/soc/codecs/tlv320adcx140.c index 4eb9cea27276..c29584d77649 100644 --- a/sound/soc/codecs/tlv320adcx140.c +++ b/sound/soc/codecs/tlv320adcx140.c @@ -916,10 +916,22 @@ static int adcx140_set_dai_tdm_slot(struct snd_soc_dai *codec_dai, return 0; } +static const u64 adcx140_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops adcx140_dai_ops = { .hw_params = adcx140_hw_params, .set_fmt = adcx140_set_dai_fmt, .set_tdm_slot = adcx140_set_dai_tdm_slot, + .auto_selectable_formats = &adcx140_selectable_formats, + .num_auto_selectable_formats = 1, }; static int adcx140_configure_gpo(struct adcx140_priv *adcx140) diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index 04ec8fb5c6e5..f31ffa189580 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -507,6 +507,13 @@ static int tlv320aic23_set_bias_level(struct snd_soc_component *component, #define AIC23_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 tlv320aic23_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops tlv320aic23_dai_ops = { .prepare = tlv320aic23_pcm_prepare, .hw_params = tlv320aic23_hw_params, @@ -514,6 +521,8 @@ static const struct snd_soc_dai_ops tlv320aic23_dai_ops = { .mute_stream = tlv320aic23_mute, .set_fmt = tlv320aic23_set_dai_fmt, .set_sysclk = tlv320aic23_set_dai_sysclk, + .auto_selectable_formats = &tlv320aic23_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c index 84e954311c1b..7dcc960a0b9e 100644 --- a/sound/soc/codecs/tlv320aic26.c +++ b/sound/soc/codecs/tlv320aic26.c @@ -208,11 +208,19 @@ static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) #define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) +static const u64 aic26_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + static const struct snd_soc_dai_ops aic26_dai_ops = { .hw_params = aic26_hw_params, .mute_stream = aic26_mute, .set_sysclk = aic26_set_sysclk, .set_fmt = aic26_set_fmt, + .auto_selectable_formats = &aic26_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index 43bcbc5449e1..9770e041b9fd 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -1422,11 +1422,22 @@ static const struct snd_soc_component_driver soc_codec_driver_aic31xx = { .endianness = 1, }; +static const u64 aic31xx_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops aic31xx_dai_ops = { .hw_params = aic31xx_hw_params, .set_sysclk = aic31xx_set_dai_sysclk, .set_fmt = aic31xx_set_dai_fmt, .mute_stream = aic31xx_dac_mute, + .auto_selectable_formats = &aic31xx_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index 9e0e4aa2b09c..af4be5bee723 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -916,11 +916,19 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_3LE \ | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 aic32x4_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; static const struct snd_soc_dai_ops aic32x4_ops = { .hw_params = aic32x4_hw_params, .mute_stream = aic32x4_mute, .set_fmt = aic32x4_set_dai_fmt, .set_sysclk = aic32x4_set_dai_sysclk, + .auto_selectable_formats = &aic32x4_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index b38393a8130f..c0f58cba8a99 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1500,6 +1500,18 @@ static int aic3x_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 aic3x_selectable_formats[] = { + /* Hi Priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF, + /* Low Priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops aic3x_dai_ops = { .hw_params = aic3x_hw_params, .prepare = aic3x_prepare, @@ -1507,6 +1519,8 @@ static const struct snd_soc_dai_ops aic3x_dai_ops = { .set_sysclk = aic3x_set_dai_sysclk, .set_fmt = aic3x_set_dai_fmt, .set_tdm_slot = aic3x_set_dai_tdm_slot, + .auto_selectable_formats = aic3x_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(aic3x_selectable_formats), .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 9bd2ddd8dacd..55a95b62a650 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -1435,6 +1435,12 @@ static const struct snd_soc_component_driver soc_component_dev_tlv320dac33 = { SNDRV_PCM_RATE_48000) #define DAC33_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 dac33_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + static const struct snd_soc_dai_ops dac33_dai_ops = { .startup = dac33_startup, .shutdown = dac33_shutdown, @@ -1443,6 +1449,8 @@ static const struct snd_soc_dai_ops dac33_dai_ops = { .delay = dac33_dai_delay, .set_sysclk = dac33_set_dai_sysclk, .set_fmt = dac33_set_dai_fmt, + .auto_selectable_formats = &dac33_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver dac33_dai = { From 41058e1bddb3b33e5c222c2dba784130ee7640c1 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:09:20 +0000 Subject: [PATCH 134/924] ASoC: codecs: tscs454: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87a4q2ub9b.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/tscs454.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sound/soc/codecs/tscs454.c b/sound/soc/codecs/tscs454.c index b70c9d931e1e..e772dd39a849 100644 --- a/sound/soc/codecs/tscs454.c +++ b/sound/soc/codecs/tscs454.c @@ -3233,6 +3233,17 @@ static int tscs454_prepare(struct snd_pcm_substream *substream, return 0; } +static const u64 tscs454_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static struct snd_soc_dai_ops const tscs454_dai1_ops = { .set_sysclk = tscs454_set_sysclk, .set_bclk_ratio = tscs454_set_bclk_ratio, @@ -3241,6 +3252,8 @@ static struct snd_soc_dai_ops const tscs454_dai1_ops = { .hw_params = tscs454_hw_params, .hw_free = tscs454_hw_free, .prepare = tscs454_prepare, + .auto_selectable_formats = &tscs454_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_ops const tscs454_dai23_ops = { @@ -3251,6 +3264,8 @@ static struct snd_soc_dai_ops const tscs454_dai23_ops = { .hw_params = tscs454_hw_params, .hw_free = tscs454_hw_free, .prepare = tscs454_prepare, + .auto_selectable_formats = &tscs454_selectable_formats, + .num_auto_selectable_formats = 1, }; static int tscs454_probe(struct snd_soc_component *component) From 688b914d278903222f0794924991236e3df805e9 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:09:24 +0000 Subject: [PATCH 135/924] ASoC: codecs: twl4030: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/878q5mub97.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 9476cdfd4dde..345bb6243fb8 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -2097,6 +2097,10 @@ static int twl4030_voice_set_tristate(struct snd_soc_dai *dai, int tristate) #define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000) #define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 twl4030_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + static const struct snd_soc_dai_ops twl4030_dai_hifi_ops = { .startup = twl4030_startup, .shutdown = twl4030_shutdown, @@ -2104,8 +2108,14 @@ static const struct snd_soc_dai_ops twl4030_dai_hifi_ops = { .set_sysclk = twl4030_set_dai_sysclk, .set_fmt = twl4030_set_dai_fmt, .set_tristate = twl4030_set_tristate, + .auto_selectable_formats = &twl4030_selectable_formats, + .num_auto_selectable_formats = 1, }; +static const u64 twl4030_voice_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops twl4030_dai_voice_ops = { .startup = twl4030_voice_startup, .shutdown = twl4030_voice_shutdown, @@ -2113,6 +2123,8 @@ static const struct snd_soc_dai_ops twl4030_dai_voice_ops = { .set_sysclk = twl4030_voice_set_dai_sysclk, .set_fmt = twl4030_voice_set_dai_fmt, .set_tristate = twl4030_voice_set_tristate, + .auto_selectable_formats = &twl4030_voice_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver twl4030_dai[] = { From 7673fc2091760cf462a422db165bb63edf34ae0e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:09:28 +0000 Subject: [PATCH 136/924] ASoC: codecs: uda13*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/877bl6ub93.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/uda1334.c | 6 ++++++ sound/soc/codecs/uda1342.c | 7 +++++++ sound/soc/codecs/uda1380.c | 11 +++++++++++ 3 files changed, 24 insertions(+) diff --git a/sound/soc/codecs/uda1334.c b/sound/soc/codecs/uda1334.c index 54c5cb5b3d4b..7ae6afdba38b 100644 --- a/sound/soc/codecs/uda1334.c +++ b/sound/soc/codecs/uda1334.c @@ -193,12 +193,18 @@ static int uda1334_mute_stream(struct snd_soc_dai *dai, int mute, int stream) #define UDA1334_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +static const u64 uda1334_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops uda1334_dai_ops = { .startup = uda1334_startup, .shutdown = uda1334_shutdown, .set_sysclk = uda1334_set_dai_sysclk, .set_fmt = uda1334_set_fmt, .mute_stream = uda1334_mute_stream, + .auto_selectable_formats = &uda1334_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver uda1334_dai = { diff --git a/sound/soc/codecs/uda1342.c b/sound/soc/codecs/uda1342.c index 4e2d6240a869..63d087590ac9 100644 --- a/sound/soc/codecs/uda1342.c +++ b/sound/soc/codecs/uda1342.c @@ -223,6 +223,11 @@ static const struct snd_soc_dapm_route uda1342_dapm_routes[] = { { "VOUTR", NULL, "DAC" }, }; +static const u64 uda1342_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops uda1342_dai_ops = { .startup = uda1342_startup, .shutdown = uda1342_shutdown, @@ -230,6 +235,8 @@ static const struct snd_soc_dai_ops uda1342_dai_ops = { .mute_stream = uda1342_mute, .set_sysclk = uda1342_set_dai_sysclk, .set_fmt = uda1342_set_dai_fmt, + .auto_selectable_formats = &uda1342_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver uda1342_dai = { diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 1dd09d10fc4a..02adc534dfb0 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -653,11 +653,18 @@ static int uda1380_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +static const u64 uda1380_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops uda1380_dai_ops = { .hw_params = uda1380_pcm_hw_params, .shutdown = uda1380_pcm_shutdown, .trigger = uda1380_trigger, .set_fmt = uda1380_set_dai_fmt_both, + .auto_selectable_formats = &uda1380_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops uda1380_dai_ops_playback = { @@ -665,6 +672,8 @@ static const struct snd_soc_dai_ops uda1380_dai_ops_playback = { .shutdown = uda1380_pcm_shutdown, .trigger = uda1380_trigger, .set_fmt = uda1380_set_dai_fmt_playback, + .auto_selectable_formats = &uda1380_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops uda1380_dai_ops_capture = { @@ -672,6 +681,8 @@ static const struct snd_soc_dai_ops uda1380_dai_ops_capture = { .shutdown = uda1380_pcm_shutdown, .trigger = uda1380_trigger, .set_fmt = uda1380_set_dai_fmt_capture, + .auto_selectable_formats = &uda1380_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver uda1380_dai[] = { From fe72aace0a5a13241cfc1d411019dee9b783a8e1 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:09:33 +0000 Subject: [PATCH 137/924] ASoC: codecs: wm*: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/875x0qub8y.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm2200.c | 10 ++++++++++ sound/soc/codecs/wm5100.c | 10 ++++++++++ sound/soc/codecs/wm8350.c | 24 ++++++++++++++++++++++++ sound/soc/codecs/wm8400.c | 9 +++++++++ sound/soc/codecs/wm8510.c | 12 ++++++++++++ sound/soc/codecs/wm8523.c | 13 +++++++++++++ sound/soc/codecs/wm8524.c | 6 ++++++ sound/soc/codecs/wm8580.c | 20 ++++++++++++++++++++ sound/soc/codecs/wm8711.c | 13 +++++++++++++ sound/soc/codecs/wm8728.c | 9 +++++++++ sound/soc/codecs/wm8731.c | 13 +++++++++++++ sound/soc/codecs/wm8737.c | 11 +++++++++++ sound/soc/codecs/wm8741.c | 13 +++++++++++++ sound/soc/codecs/wm8750.c | 13 +++++++++++++ sound/soc/codecs/wm8753.c | 20 ++++++++++++++++++++ sound/soc/codecs/wm8770.c | 11 +++++++++++ sound/soc/codecs/wm8776.c | 13 +++++++++++++ sound/soc/codecs/wm8804.c | 15 ++++++++++++++- sound/soc/codecs/wm8900.c | 18 ++++++++++++++++++ sound/soc/codecs/wm8903.c | 18 ++++++++++++++++++ sound/soc/codecs/wm8904.c | 18 ++++++++++++++++++ sound/soc/codecs/wm8940.c | 13 +++++++++++++ sound/soc/codecs/wm8955.c | 18 ++++++++++++++++++ sound/soc/codecs/wm8960.c | 13 +++++++++++++ sound/soc/codecs/wm8961.c | 18 ++++++++++++++++++ sound/soc/codecs/wm8962.c | 18 ++++++++++++++++++ sound/soc/codecs/wm8971.c | 13 +++++++++++++ sound/soc/codecs/wm8974.c | 17 +++++++++++++++++ sound/soc/codecs/wm8978.c | 12 ++++++++++++ sound/soc/codecs/wm8983.c | 12 ++++++++++++ sound/soc/codecs/wm8985.c | 18 ++++++++++++++++++ sound/soc/codecs/wm8988.c | 13 +++++++++++++ sound/soc/codecs/wm8990.c | 9 +++++++++ sound/soc/codecs/wm8991.c | 9 +++++++++ sound/soc/codecs/wm8993.c | 18 ++++++++++++++++++ sound/soc/codecs/wm8994.c | 20 ++++++++++++++++++++ sound/soc/codecs/wm8995.c | 20 ++++++++++++++++++++ sound/soc/codecs/wm8996.c | 12 ++++++++++++ sound/soc/codecs/wm9081.c | 18 ++++++++++++++++++ sound/soc/codecs/wm9713.c | 13 +++++++++++++ 40 files changed, 572 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c index bc78b553b6cb..692e933dba75 100644 --- a/sound/soc/codecs/wm2200.c +++ b/sound/soc/codecs/wm2200.c @@ -2066,10 +2066,20 @@ static int wm2200_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 wm2200_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm2200_dai_ops = { .probe = wm2200_dai_probe, .set_fmt = wm2200_set_fmt, .hw_params = wm2200_hw_params, + .auto_selectable_formats = &wm2200_selectable_formats, + .num_auto_selectable_formats = 1, }; #define WM2200_RATES SNDRV_PCM_RATE_8000_48000 diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c index 2fe937a93026..f7ecbac386e5 100644 --- a/sound/soc/codecs/wm5100.c +++ b/sound/soc/codecs/wm5100.c @@ -1501,9 +1501,19 @@ static int wm5100_hw_params(struct snd_pcm_substream *substream, return 0; } +static const u64 wm5100_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm5100_dai_ops = { .set_fmt = wm5100_set_fmt, .hw_params = wm5100_hw_params, + .auto_selectable_formats = &wm5100_selectable_formats, + .num_auto_selectable_formats = 1, }; static int wm5100_set_sysclk(struct snd_soc_component *component, int clk_id, diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index ad68ff1790e0..0e7c8a5fae72 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1426,6 +1426,28 @@ EXPORT_SYMBOL_GPL(wm8350_mic_jack_detect); SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8350_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* + * 2nd priority + * + * iface of DSP_A/B are same, the diff is only LRCLK_INV. + * It will be impossible to distinguish between DSP_A/B + * if xB_IF are used. Allow xB_NF only. + */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8350_dai_ops = { .hw_params = wm8350_pcm_hw_params, .mute_stream = wm8350_mute, @@ -1434,6 +1456,8 @@ static const struct snd_soc_dai_ops wm8350_dai_ops = { .set_pll = wm8350_set_fll, .set_clkdiv = wm8350_set_clkdiv, .no_capture_mute = 1, + .auto_selectable_formats = wm8350_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8350_selectable_formats), }; static struct snd_soc_dai_driver wm8350_dai = { diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index 0eecc8657a38..83e2d72a4a76 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -1222,6 +1222,13 @@ static int wm8400_set_bias_level(struct snd_soc_component *component, #define WM8400_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8400_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops wm8400_dai_ops = { .hw_params = wm8400_hw_params, .mute_stream = wm8400_mute, @@ -1230,6 +1237,8 @@ static const struct snd_soc_dai_ops wm8400_dai_ops = { .set_sysclk = wm8400_set_dai_sysclk, .set_pll = wm8400_set_dai_pll, .no_capture_mute = 1, + .auto_selectable_formats = &wm8400_selectable_formats, + .num_auto_selectable_formats = 1, }; /* diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 137dcd3d7487..7d40ed21f569 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -545,6 +545,16 @@ static int wm8510_set_bias_level(struct snd_soc_component *component, #define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8510_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8510_dai_ops = { .hw_params = wm8510_pcm_hw_params, .mute_stream = wm8510_mute, @@ -552,6 +562,8 @@ static const struct snd_soc_dai_ops wm8510_dai_ops = { .set_clkdiv = wm8510_set_dai_clkdiv, .set_pll = wm8510_set_dai_pll, .no_capture_mute = 1, + .auto_selectable_formats = &wm8510_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8510_dai = { diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c index b8832a1d61fe..9972d6599002 100644 --- a/sound/soc/codecs/wm8523.c +++ b/sound/soc/codecs/wm8523.c @@ -374,11 +374,24 @@ static int wm8523_set_bias_level(struct snd_soc_component *component, #define WM8523_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8523_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8523_dai_ops = { .startup = wm8523_startup, .hw_params = wm8523_hw_params, .set_sysclk = wm8523_set_dai_sysclk, .set_fmt = wm8523_set_dai_fmt, + .auto_selectable_formats = &wm8523_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8523_dai = { diff --git a/sound/soc/codecs/wm8524.c b/sound/soc/codecs/wm8524.c index 23daf158f35f..8d0565b103cd 100644 --- a/sound/soc/codecs/wm8524.c +++ b/sound/soc/codecs/wm8524.c @@ -190,6 +190,10 @@ static int wm8524_hw_params(struct snd_pcm_substream *substream, SNDRV_PCM_FMTBIT_S24_LE |\ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8525_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops wm8524_dai_ops = { .startup = wm8524_startup, .shutdown = wm8524_shutdown, @@ -197,6 +201,8 @@ static const struct snd_soc_dai_ops wm8524_dai_ops = { .set_fmt = wm8524_set_fmt, .mute_stream = wm8524_mute_stream, .hw_params = wm8524_hw_params, + .auto_selectable_formats = &wm8525_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8524_dai = { diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index eb374ba6e5b5..9bc5d1e795dc 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -860,6 +860,22 @@ static int wm8580_playback_startup(struct snd_pcm_substream *substream, #define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8580_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8580_dai_ops_playback = { .startup = wm8580_playback_startup, .set_sysclk = wm8580_set_sysclk, @@ -869,6 +885,8 @@ static const struct snd_soc_dai_ops wm8580_dai_ops_playback = { .set_pll = wm8580_set_dai_pll, .mute_stream = wm8580_mute, .no_capture_mute = 1, + .auto_selectable_formats = wm8580_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8580_selectable_formats), }; static const struct snd_soc_dai_ops wm8580_dai_ops_capture = { @@ -877,6 +895,8 @@ static const struct snd_soc_dai_ops wm8580_dai_ops_capture = { .set_fmt = wm8580_set_paif_dai_fmt, .set_clkdiv = wm8580_set_dai_clkdiv, .set_pll = wm8580_set_dai_pll, + .auto_selectable_formats = wm8580_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8580_selectable_formats), }; static struct snd_soc_dai_driver wm8580_dai[] = { diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c index 2db8829661c3..390af15a749a 100644 --- a/sound/soc/codecs/wm8711.c +++ b/sound/soc/codecs/wm8711.c @@ -325,6 +325,17 @@ static int wm8711_set_bias_level(struct snd_soc_component *component, #define WM8711_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8711_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8711_ops = { .prepare = wm8711_pcm_prepare, .hw_params = wm8711_hw_params, @@ -333,6 +344,8 @@ static const struct snd_soc_dai_ops wm8711_ops = { .set_sysclk = wm8711_set_dai_sysclk, .set_fmt = wm8711_set_dai_fmt, .no_capture_mute = 1, + .auto_selectable_formats = &wm8711_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8711_dai = { diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c index 3109a6a0df74..7af5ce0fffe0 100644 --- a/sound/soc/codecs/wm8728.c +++ b/sound/soc/codecs/wm8728.c @@ -190,11 +190,20 @@ static int wm8728_set_bias_level(struct snd_soc_component *component, #define WM8728_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8728_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8728_dai_ops = { .hw_params = wm8728_hw_params, .mute_stream = wm8728_mute, .set_fmt = wm8728_set_dai_fmt, .no_capture_mute = 1, + .auto_selectable_formats = &wm8728_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8728_dai = { diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index ce87280d590d..f25b50919a9a 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -519,6 +519,17 @@ static int wm8731_startup(struct snd_pcm_substream *substream, #define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8731_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8731_dai_ops = { .startup = wm8731_startup, .hw_params = wm8731_hw_params, @@ -526,6 +537,8 @@ static const struct snd_soc_dai_ops wm8731_dai_ops = { .set_sysclk = wm8731_set_dai_sysclk, .set_fmt = wm8731_set_dai_fmt, .no_capture_mute = 1, + .auto_selectable_formats = &wm8731_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8731_dai = { diff --git a/sound/soc/codecs/wm8737.c b/sound/soc/codecs/wm8737.c index 33a3f88fffb3..72b7d09503a9 100644 --- a/sound/soc/codecs/wm8737.c +++ b/sound/soc/codecs/wm8737.c @@ -516,10 +516,21 @@ static int wm8737_set_bias_level(struct snd_soc_component *component, #define WM8737_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8737_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF; + static const struct snd_soc_dai_ops wm8737_dai_ops = { .hw_params = wm8737_hw_params, .set_sysclk = wm8737_set_dai_sysclk, .set_fmt = wm8737_set_dai_fmt, + .auto_selectable_formats = &wm8737_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8737_dai = { diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c index ca56fdfb5088..e49adc9808b4 100644 --- a/sound/soc/codecs/wm8741.c +++ b/sound/soc/codecs/wm8741.c @@ -381,6 +381,17 @@ static int wm8741_mute(struct snd_soc_dai *codec_dai, int mute, int direction) #define WM8741_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8741_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8741_dai_ops = { .startup = wm8741_startup, .hw_params = wm8741_hw_params, @@ -388,6 +399,8 @@ static const struct snd_soc_dai_ops wm8741_dai_ops = { .set_fmt = wm8741_set_dai_fmt, .mute_stream = wm8741_mute, .no_capture_mute = 1, + .auto_selectable_formats = &wm8741_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8741_dai = { diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index d3f1178c2d34..b0b444793830 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -659,12 +659,25 @@ static int wm8750_set_bias_level(struct snd_soc_component *component, #define WM8750_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8750_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8750_dai_ops = { .hw_params = wm8750_pcm_hw_params, .mute_stream = wm8750_mute, .set_fmt = wm8750_set_dai_fmt, .set_sysclk = wm8750_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8750_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8750_dai = { diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index ac4008b4832d..cbdf1e6aea5b 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -1380,6 +1380,22 @@ static int wm8753_set_bias_level(struct snd_soc_component *component, * 3. Voice disabled - HIFI over HIFI * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture */ +static const u64 wm8753_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, +}; + static const struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode = { .hw_params = wm8753_i2s_hw_params, .mute_stream = wm8753_mute, @@ -1388,6 +1404,8 @@ static const struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode = { .set_pll = wm8753_set_dai_pll, .set_sysclk = wm8753_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = wm8753_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8753_selectable_formats), }; static const struct snd_soc_dai_ops wm8753_dai_ops_voice_mode = { @@ -1398,6 +1416,8 @@ static const struct snd_soc_dai_ops wm8753_dai_ops_voice_mode = { .set_pll = wm8753_set_dai_pll, .set_sysclk = wm8753_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = wm8753_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8753_selectable_formats), }; static struct snd_soc_dai_driver wm8753_dai[] = { diff --git a/sound/soc/codecs/wm8770.c b/sound/soc/codecs/wm8770.c index b8b4d1e823e6..1e485d2fe21a 100644 --- a/sound/soc/codecs/wm8770.c +++ b/sound/soc/codecs/wm8770.c @@ -537,12 +537,23 @@ static int wm8770_set_bias_level(struct snd_soc_component *component, #define WM8770_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8770_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8770_dai_ops = { .mute_stream = wm8770_mute, .hw_params = wm8770_hw_params, .set_fmt = wm8770_set_fmt, .set_sysclk = wm8770_set_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8770_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8770_dai = { diff --git a/sound/soc/codecs/wm8776.c b/sound/soc/codecs/wm8776.c index a8e4f71c77d1..c633fe824356 100644 --- a/sound/soc/codecs/wm8776.c +++ b/sound/soc/codecs/wm8776.c @@ -360,18 +360,31 @@ static int wm8776_set_bias_level(struct snd_soc_component *component, #define WM8776_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8776_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8776_dac_ops = { .mute_stream = wm8776_mute, .hw_params = wm8776_hw_params, .set_fmt = wm8776_set_fmt, .set_sysclk = wm8776_set_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8776_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops wm8776_adc_ops = { .hw_params = wm8776_hw_params, .set_fmt = wm8776_set_fmt, .set_sysclk = wm8776_set_sysclk, + .auto_selectable_formats = &wm8776_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8776_dai[] = { diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c index 94aa3c8de0ab..2fc9634fe51f 100644 --- a/sound/soc/codecs/wm8804.c +++ b/sound/soc/codecs/wm8804.c @@ -502,12 +502,25 @@ static int wm8804_set_clkdiv(struct snd_soc_dai *dai, return 0; } +static const u64 wm8804_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8804_dai_ops = { .hw_params = wm8804_hw_params, .set_fmt = wm8804_set_fmt, .set_sysclk = wm8804_set_sysclk, .set_clkdiv = wm8804_set_clkdiv, - .set_pll = wm8804_set_pll + .set_pll = wm8804_set_pll, + .auto_selectable_formats = &wm8804_selectable_formats, + .num_auto_selectable_formats = 1, }; #define WM8804_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index a9128cfa6ff1..dcda7a7be0b1 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -992,6 +992,22 @@ static int wm8900_mute(struct snd_soc_dai *codec_dai, int mute, int direction) (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8900_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8900_dai_ops = { .hw_params = wm8900_hw_params, .set_clkdiv = wm8900_set_dai_clkdiv, @@ -999,6 +1015,8 @@ static const struct snd_soc_dai_ops wm8900_dai_ops = { .set_fmt = wm8900_set_dai_fmt, .mute_stream = wm8900_mute, .no_capture_mute = 1, + .auto_selectable_formats = wm8900_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8900_selectable_formats), }; static struct snd_soc_dai_driver wm8900_dai = { diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 156e1e24a388..68112cb2eec8 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -1733,12 +1733,30 @@ static irqreturn_t wm8903_irq(int irq, void *data) SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8903_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8903_dai_ops = { .hw_params = wm8903_hw_params, .mute_stream = wm8903_mute, .set_fmt = wm8903_set_dai_fmt, .set_sysclk = wm8903_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = wm8903_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8903_selectable_formats), }; static struct snd_soc_dai_driver wm8903_dai = { diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index 9e5782e50f47..153ce2c1a0f6 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -2026,6 +2026,22 @@ static int wm8904_set_bias_level(struct snd_soc_component *component, #define WM8904_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8904_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8904_dai_ops = { .set_sysclk = wm8904_set_sysclk, .set_fmt = wm8904_set_fmt, @@ -2034,6 +2050,8 @@ static const struct snd_soc_dai_ops wm8904_dai_ops = { .hw_params = wm8904_hw_params, .mute_stream = wm8904_mute, .no_capture_mute = 1, + .auto_selectable_formats = wm8904_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8904_selectable_formats), }; static struct snd_soc_dai_driver wm8904_dai = { diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index 229252363188..012556215d12 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -730,6 +730,17 @@ static int wm8940_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8940_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8940_dai_ops = { .hw_params = wm8940_i2s_hw_params, .set_sysclk = wm8940_set_dai_sysclk, @@ -738,6 +749,8 @@ static const struct snd_soc_dai_ops wm8940_dai_ops = { .set_clkdiv = wm8940_set_dai_clkdiv, .set_pll = wm8940_set_dai_pll, .no_capture_mute = 1, + .auto_selectable_formats = &wm8940_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8940_dai = { diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c index c897a9ab764d..1d367a957f3e 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -845,12 +845,30 @@ static int wm8955_set_bias_level(struct snd_soc_component *component, #define WM8955_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8955_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8955_dai_ops = { .set_sysclk = wm8955_set_sysclk, .set_fmt = wm8955_set_fmt, .hw_params = wm8955_hw_params, .mute_stream = wm8955_mute, .no_capture_mute = 1, + .auto_selectable_formats = wm8955_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8955_selectable_formats), }; static struct snd_soc_dai_driver wm8955_dai = { diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index a810732c6af0..093530ae872b 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -1340,6 +1340,17 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8960_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8960_dai_ops = { .hw_params = wm8960_hw_params, .hw_free = wm8960_hw_free, @@ -1349,6 +1360,8 @@ static const struct snd_soc_dai_ops wm8960_dai_ops = { .set_pll = wm8960_set_dai_pll, .set_sysclk = wm8960_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8960_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8960_dai = { diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index 504fc59e4be2..55ccac7286c9 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c @@ -803,6 +803,22 @@ static int wm8961_set_bias_level(struct snd_soc_component *component, (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8961_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8961_dai_ops = { .hw_params = wm8961_hw_params, .set_sysclk = wm8961_set_sysclk, @@ -811,6 +827,8 @@ static const struct snd_soc_dai_ops wm8961_dai_ops = { .set_tristate = wm8961_set_tristate, .set_clkdiv = wm8961_set_clkdiv, .no_capture_mute = 1, + .auto_selectable_formats = wm8961_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8961_selectable_formats), }; static struct snd_soc_dai_driver wm8961_dai = { diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index 8a9598161b35..b481d70c5333 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -3056,6 +3056,22 @@ static int wm8962_mute(struct snd_soc_dai *dai, int mute, int direction) #define WM8962_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8962_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8962_dai_ops = { .hw_params = wm8962_hw_params, .set_sysclk = wm8962_set_dai_sysclk, @@ -3063,6 +3079,8 @@ static const struct snd_soc_dai_ops wm8962_dai_ops = { .set_tdm_slot = wm8962_set_tdm_slot, .mute_stream = wm8962_mute, .no_capture_mute = 1, + .auto_selectable_formats = wm8962_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8962_selectable_formats), }; static struct snd_soc_dai_driver wm8962_dai = { diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c index a5d2e91b66bb..388ee05d4520 100644 --- a/sound/soc/codecs/wm8971.c +++ b/sound/soc/codecs/wm8971.c @@ -601,12 +601,25 @@ static int wm8971_set_bias_level(struct snd_soc_component *component, #define WM8971_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8971_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8971_dai_ops = { .hw_params = wm8971_pcm_hw_params, .mute_stream = wm8971_mute, .set_fmt = wm8971_set_dai_fmt, .set_sysclk = wm8971_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8971_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8971_dai = { diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c index 4656652b8e97..5e1737d455b1 100644 --- a/sound/soc/codecs/wm8974.c +++ b/sound/soc/codecs/wm8974.c @@ -623,6 +623,21 @@ static int wm8974_set_bias_level(struct snd_soc_component *component, #define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8974_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8974_ops = { .hw_params = wm8974_pcm_hw_params, .mute_stream = wm8974_mute, @@ -631,6 +646,8 @@ static const struct snd_soc_dai_ops wm8974_ops = { .set_pll = wm8974_set_dai_pll, .set_sysclk = wm8974_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = wm8974_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8974_selectable_formats), }; static struct snd_soc_dai_driver wm8974_dai = { diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index ad8064bbaaac..25cc433c7653 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -892,6 +892,16 @@ static int wm8978_set_bias_level(struct snd_soc_component *component, #define WM8978_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8978_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8978_dai_ops = { .hw_params = wm8978_hw_params, .mute_stream = wm8978_mute, @@ -899,6 +909,8 @@ static const struct snd_soc_dai_ops wm8978_dai_ops = { .set_clkdiv = wm8978_set_dai_clkdiv, .set_sysclk = wm8978_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8978_selectable_formats, + .num_auto_selectable_formats = 1, }; /* Also supports 12kHz */ diff --git a/sound/soc/codecs/wm8983.c b/sound/soc/codecs/wm8983.c index fab25f35fd14..3366166a2b77 100644 --- a/sound/soc/codecs/wm8983.c +++ b/sound/soc/codecs/wm8983.c @@ -943,6 +943,16 @@ static int wm8983_probe(struct snd_soc_component *component) return 0; } +/* FIXME: it doesn't support DSP A/B for now */ +static const u64 wm8983_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8983_dai_ops = { .mute_stream = wm8983_dac_mute, .hw_params = wm8983_hw_params, @@ -950,6 +960,8 @@ static const struct snd_soc_dai_ops wm8983_dai_ops = { .set_sysclk = wm8983_set_sysclk, .set_pll = wm8983_set_pll, .no_capture_mute = 1, + .auto_selectable_formats = &wm8983_selectable_formats, + .num_auto_selectable_formats = 1, }; #define WM8983_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ diff --git a/sound/soc/codecs/wm8985.c b/sound/soc/codecs/wm8985.c index 2a64d5a851da..77ca9a0feb8a 100644 --- a/sound/soc/codecs/wm8985.c +++ b/sound/soc/codecs/wm8985.c @@ -1072,6 +1072,22 @@ static int wm8985_probe(struct snd_soc_component *component) return ret; } +static const u64 wm8985_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8985_dai_ops = { .mute_stream = wm8985_dac_mute, .hw_params = wm8985_hw_params, @@ -1079,6 +1095,8 @@ static const struct snd_soc_dai_ops wm8985_dai_ops = { .set_sysclk = wm8985_set_sysclk, .set_pll = wm8985_set_pll, .no_capture_mute = 1, + .auto_selectable_formats = wm8985_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8985_selectable_formats), }; #define WM8985_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c index 741aecc7641a..6f97c9aa453a 100644 --- a/sound/soc/codecs/wm8988.c +++ b/sound/soc/codecs/wm8988.c @@ -762,6 +762,17 @@ static int wm8988_set_bias_level(struct snd_soc_component *component, #define WM8988_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8988_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8988_ops = { .startup = wm8988_pcm_startup, .hw_params = wm8988_pcm_hw_params, @@ -769,6 +780,8 @@ static const struct snd_soc_dai_ops wm8988_ops = { .set_sysclk = wm8988_set_dai_sysclk, .mute_stream = wm8988_mute, .no_capture_mute = 1, + .auto_selectable_formats = &wm8988_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8988_dai = { diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index 11dfbbbdcd92..c8fef187f540 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -1151,6 +1151,13 @@ static int wm8990_set_bias_level(struct snd_soc_component *component, * 1. ADC/DAC on Primary Interface * 2. ADC on Primary Interface/DAC on secondary */ +static const u64 wm8990_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops wm8990_dai_ops = { .hw_params = wm8990_hw_params, .mute_stream = wm8990_mute, @@ -1159,6 +1166,8 @@ static const struct snd_soc_dai_ops wm8990_dai_ops = { .set_pll = wm8990_set_dai_pll, .set_sysclk = wm8990_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8990_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8990_dai = { diff --git a/sound/soc/codecs/wm8991.c b/sound/soc/codecs/wm8991.c index 225c235d3d89..2cb7fb9962af 100644 --- a/sound/soc/codecs/wm8991.c +++ b/sound/soc/codecs/wm8991.c @@ -1195,6 +1195,13 @@ static int wm8991_set_bias_level(struct snd_soc_component *component, #define WM8991_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8991_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops wm8991_ops = { .hw_params = wm8991_hw_params, .mute_stream = wm8991_mute, @@ -1202,6 +1209,8 @@ static const struct snd_soc_dai_ops wm8991_ops = { .set_clkdiv = wm8991_set_dai_clkdiv, .set_pll = wm8991_set_dai_pll, .no_capture_mute = 1, + .auto_selectable_formats = &wm8991_selectable_formats, + .num_auto_selectable_formats = 1, }; /* diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index 2a94b23319b5..934fa41a53cf 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -1441,6 +1441,22 @@ static irqreturn_t wm8993_irq(int irq, void *data) return IRQ_HANDLED; } +static const u64 wm8993_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8993_ops = { .set_sysclk = wm8993_set_sysclk, .set_fmt = wm8993_set_dai_fmt, @@ -1449,6 +1465,8 @@ static const struct snd_soc_dai_ops wm8993_ops = { .set_pll = wm8993_set_fll, .set_tdm_slot = wm8993_set_tdm_slot, .no_capture_mute = 1, + .auto_selectable_formats = wm8993_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8993_selectable_formats), }; #define WM8993_RATES SNDRV_PCM_RATE_8000_48000 diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 8bf58a6a8af7..97d8df3815ac 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -3202,6 +3202,22 @@ static int wm8994_aif2_probe(struct snd_soc_dai *dai) #define WM8994_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8994_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8994_aif1_dai_ops = { .set_sysclk = wm8994_set_dai_sysclk, .set_fmt = wm8994_set_dai_fmt, @@ -3210,6 +3226,8 @@ static const struct snd_soc_dai_ops wm8994_aif1_dai_ops = { .set_pll = wm8994_set_fll, .set_tristate = wm8994_set_tristate, .no_capture_mute = 1, + .auto_selectable_formats = wm8994_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8994_selectable_formats), }; static const struct snd_soc_dai_ops wm8994_aif2_dai_ops = { @@ -3221,6 +3239,8 @@ static const struct snd_soc_dai_ops wm8994_aif2_dai_ops = { .set_pll = wm8994_set_fll, .set_tristate = wm8994_set_tristate, .no_capture_mute = 1, + .auto_selectable_formats = wm8994_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8994_selectable_formats), }; static const struct snd_soc_dai_ops wm8994_aif3_dai_ops = { diff --git a/sound/soc/codecs/wm8995.c b/sound/soc/codecs/wm8995.c index 799989a5bf0f..3933a419f1e2 100644 --- a/sound/soc/codecs/wm8995.c +++ b/sound/soc/codecs/wm8995.c @@ -2091,6 +2091,22 @@ static int wm8995_probe(struct snd_soc_component *component) #define WM8995_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8995_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8995_aif1_dai_ops = { .set_sysclk = wm8995_set_dai_sysclk, .set_fmt = wm8995_set_dai_fmt, @@ -2099,6 +2115,8 @@ static const struct snd_soc_dai_ops wm8995_aif1_dai_ops = { .set_pll = wm8995_set_fll, .set_tristate = wm8995_set_tristate, .no_capture_mute = 1, + .auto_selectable_formats = wm8995_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8995_selectable_formats), }; static const struct snd_soc_dai_ops wm8995_aif2_dai_ops = { @@ -2109,6 +2127,8 @@ static const struct snd_soc_dai_ops wm8995_aif2_dai_ops = { .set_pll = wm8995_set_fll, .set_tristate = wm8995_set_tristate, .no_capture_mute = 1, + .auto_selectable_formats = wm8995_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8995_selectable_formats), }; static const struct snd_soc_dai_ops wm8995_aif3_dai_ops = { diff --git a/sound/soc/codecs/wm8996.c b/sound/soc/codecs/wm8996.c index dface555f928..25a6186e074a 100644 --- a/sound/soc/codecs/wm8996.c +++ b/sound/soc/codecs/wm8996.c @@ -2703,10 +2703,22 @@ static const struct snd_soc_component_driver soc_component_dev_wm8996 = { SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE |\ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8996_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8996_dai_ops = { .set_fmt = wm8996_set_fmt, .hw_params = wm8996_hw_params, .set_sysclk = wm8996_set_sysclk, + .auto_selectable_formats = &wm8996_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8996_dai[] = { diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 2e4e0a76499d..824e9c6b5f3e 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -1230,12 +1230,30 @@ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm9081_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm9081_dai_ops = { .hw_params = wm9081_hw_params, .set_fmt = wm9081_set_dai_fmt, .mute_stream = wm9081_mute, .set_tdm_slot = wm9081_set_tdm_slot, .no_capture_mute = 1, + .auto_selectable_formats = wm9081_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm9081_selectable_formats), }; /* We report two channels because the CODEC processes a stereo signal, even diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index 3ba7ca3c1770..3cffdfbbf9dd 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -1067,6 +1067,17 @@ static int ac97_aux_prepare(struct snd_pcm_substream *substream, (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm9713_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm9713_dai_ops_hifi = { .prepare = ac97_hifi_prepare, .set_clkdiv = wm9713_set_dai_clkdiv, @@ -1085,6 +1096,8 @@ static const struct snd_soc_dai_ops wm9713_dai_ops_voice = { .set_pll = wm9713_set_dai_pll, .set_fmt = wm9713_set_dai_fmt, .set_tristate = wm9713_set_dai_tristate, + .auto_selectable_formats = &wm9713_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm9713_dai[] = { From 93a0091f9745d0ba3043ae3d1f7365e347906c55 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:09:37 +0000 Subject: [PATCH 138/924] ASoC: codecs: zl38060: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/874igaub8v.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/zl38060.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/soc/codecs/zl38060.c b/sound/soc/codecs/zl38060.c index 894b8eb42e39..b7062860cca1 100644 --- a/sound/soc/codecs/zl38060.c +++ b/sound/soc/codecs/zl38060.c @@ -334,10 +334,16 @@ static const struct reg_sequence cp_config_stereo_bypass[] = { { 0x0202, 0x000F }, /* enable I2S1 + DAC */ }; +static const u64 zl38_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops zl38_dai_ops = { .set_fmt = zl38_set_fmt, .hw_params = zl38_hw_params, .hw_free = zl38_hw_free, + .auto_selectable_formats = &zl38_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver zl38_dai = { From 04d17816c81ef7929600ca0c2214395728bcde5c Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:13:31 +0000 Subject: [PATCH 139/924] ASoC: dwc: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87y0djyw5g.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/dwc/dwc-i2s.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index 2e9fb79b1cd8..ade89c196a79 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -464,6 +464,13 @@ static int dw_i2s_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 dw_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops dw_i2s_dai_ops = { .probe = dw_i2s_dai_probe, .startup = dw_i2s_startup, @@ -472,6 +479,8 @@ static const struct snd_soc_dai_ops dw_i2s_dai_ops = { .trigger = dw_i2s_trigger, .set_fmt = dw_i2s_set_fmt, .set_tdm_slot = dw_i2s_set_tdm_slot, + .auto_selectable_formats = &dw_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static int dw_i2s_runtime_suspend(struct device *dev) From 732bac6bd644df65dda5cd71af842ac23a50e7d8 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:13:40 +0000 Subject: [PATCH 140/924] ASoC: hisilicon: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87v78nyw57.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/hisilicon/hi6210-i2s.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/hisilicon/hi6210-i2s.c b/sound/soc/hisilicon/hi6210-i2s.c index 33b1dafb2540..554356f060b0 100644 --- a/sound/soc/hisilicon/hi6210-i2s.c +++ b/sound/soc/hisilicon/hi6210-i2s.c @@ -507,6 +507,11 @@ static int hi6210_i2s_dai_probe(struct snd_soc_dai *dai) } +static const u64 hi6210_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops hi6210_i2s_dai_ops = { .probe = hi6210_i2s_dai_probe, .trigger = hi6210_i2s_trigger, @@ -514,6 +519,8 @@ static const struct snd_soc_dai_ops hi6210_i2s_dai_ops = { .set_fmt = hi6210_i2s_set_fmt, .startup = hi6210_i2s_startup, .shutdown = hi6210_i2s_shutdown, + .auto_selectable_formats = &hi6210_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_driver hi6210_i2s_dai_init = { From b18ce5ae42ac4c224afa507ce40fd0f14ddcf92e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:13:44 +0000 Subject: [PATCH 141/924] ASoC: img: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87tso7yw53.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/img/img-i2s-in.c | 12 +++++++++++- sound/soc/img/img-i2s-out.c | 13 ++++++++++++- sound/soc/img/img-parallel-out.c | 8 +++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/sound/soc/img/img-i2s-in.c b/sound/soc/img/img-i2s-in.c index 98d2ab68883a..a0c87e2a9b30 100644 --- a/sound/soc/img/img-i2s-in.c +++ b/sound/soc/img/img-i2s-in.c @@ -379,11 +379,21 @@ static int img_i2s_in_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 img_i2s_in_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops img_i2s_in_dai_ops = { .probe = img_i2s_in_dai_probe, .trigger = img_i2s_in_trigger, .hw_params = img_i2s_in_hw_params, - .set_fmt = img_i2s_in_set_fmt + .set_fmt = img_i2s_in_set_fmt, + .auto_selectable_formats = &img_i2s_in_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_component_driver img_i2s_in_component = { diff --git a/sound/soc/img/img-i2s-out.c b/sound/soc/img/img-i2s-out.c index aedcf56a5359..caa7870dbec6 100644 --- a/sound/soc/img/img-i2s-out.c +++ b/sound/soc/img/img-i2s-out.c @@ -385,11 +385,22 @@ static int img_i2s_out_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 img_i2s_out_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_CONT | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops img_i2s_out_dai_ops = { .probe = img_i2s_out_dai_probe, .trigger = img_i2s_out_trigger, .hw_params = img_i2s_out_hw_params, - .set_fmt = img_i2s_out_set_fmt + .set_fmt = img_i2s_out_set_fmt, + .auto_selectable_formats = &img_i2s_out_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_component_driver img_i2s_out_component = { diff --git a/sound/soc/img/img-parallel-out.c b/sound/soc/img/img-parallel-out.c index 86b60e6dab38..89270913ab35 100644 --- a/sound/soc/img/img-parallel-out.c +++ b/sound/soc/img/img-parallel-out.c @@ -183,11 +183,17 @@ static int img_prl_out_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 img_prl_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF; + static const struct snd_soc_dai_ops img_prl_out_dai_ops = { .probe = img_prl_out_dai_probe, .trigger = img_prl_out_trigger, .hw_params = img_prl_out_hw_params, - .set_fmt = img_prl_out_set_fmt + .set_fmt = img_prl_out_set_fmt, + .auto_selectable_formats = &img_prl_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver img_prl_out_dai = { From 2bc2523c253d4491f1897fbcd489d893dd2bc3cd Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:13:47 +0000 Subject: [PATCH 142/924] ASoC: jz4740: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87se3ryw50.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/jz4740/jz4740-i2s.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c index d36bbc820618..7a1589cb775a 100644 --- a/sound/soc/jz4740/jz4740-i2s.c +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -326,6 +326,11 @@ static int jz4740_i2s_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 jz4740_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops jz4740_i2s_dai_ops = { .probe = jz4740_i2s_dai_probe, .startup = jz4740_i2s_startup, @@ -333,6 +338,8 @@ static const struct snd_soc_dai_ops jz4740_i2s_dai_ops = { .trigger = jz4740_i2s_trigger, .hw_params = jz4740_i2s_hw_params, .set_fmt = jz4740_i2s_set_fmt, + .auto_selectable_formats = &jz4740_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; #define JZ4740_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \ From 0dab4c4fe6c857da73d6b1d7104bcac92d04d05c Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:13:50 +0000 Subject: [PATCH 143/924] ASoC: kirkwood: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87qzjbyw4x.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/kirkwood/kirkwood-i2s.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c index 99bd066c7309..739a819d8494 100644 --- a/sound/soc/kirkwood/kirkwood-i2s.c +++ b/sound/soc/kirkwood/kirkwood-i2s.c @@ -544,11 +544,18 @@ static int kirkwood_i2s_init(struct kirkwood_dma_data *priv) } +static const u64 kirkwood_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops kirkwood_i2s_dai_ops = { .startup = kirkwood_i2s_startup, .trigger = kirkwood_i2s_trigger, .hw_params = kirkwood_i2s_hw_params, .set_fmt = kirkwood_i2s_set_fmt, + .auto_selectable_formats = &kirkwood_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver kirkwood_i2s_dai[2] = { From 47afd963102b27d28f9d1ac172437651df9259c1 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:13:54 +0000 Subject: [PATCH 144/924] ASoC: loongson: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87pkyvyw4t.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/loongson/loongson_i2s.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/soc/loongson/loongson_i2s.c b/sound/soc/loongson/loongson_i2s.c index cfe102a8b604..da0d029c028a 100644 --- a/sound/soc/loongson/loongson_i2s.c +++ b/sound/soc/loongson/loongson_i2s.c @@ -220,12 +220,18 @@ static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai) return 0; } +static const u64 loongson_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J; + static const struct snd_soc_dai_ops loongson_i2s_dai_ops = { .probe = loongson_i2s_dai_probe, .trigger = loongson_i2s_trigger, .hw_params = loongson_i2s_hw_params, .set_sysclk = loongson_i2s_set_dai_sysclk, .set_fmt = loongson_i2s_set_fmt, + .auto_selectable_formats = &loongson_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; struct snd_soc_dai_driver loongson_i2s_dai = { From d83c510123724e898282d6327924f37e80e6ae67 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:14:02 +0000 Subject: [PATCH 145/924] ASoC: mxs: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87mrtzyw4m.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/mxs/mxs-saif.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c index a77cd516a1bf..e46896ac9669 100644 --- a/sound/soc/mxs/mxs-saif.c +++ b/sound/soc/mxs/mxs-saif.c @@ -691,6 +691,14 @@ static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd, (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 mxs_saif_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops mxs_saif_dai_ops = { .startup = mxs_saif_startup, .shutdown = mxs_saif_shutdown, @@ -699,6 +707,8 @@ static const struct snd_soc_dai_ops mxs_saif_dai_ops = { .hw_params = mxs_saif_hw_params, .set_sysclk = mxs_saif_set_dai_sysclk, .set_fmt = mxs_saif_set_dai_fmt, + .auto_selectable_formats = &mxs_saif_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver mxs_saif_dai = { From 1eae5ea3637d09c073e5294ddc24712b4370054e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:14:06 +0000 Subject: [PATCH 146/924] ASoC: pxa: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ld9jyw4i.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/pxa/mmp-sspa.c | 6 ++++++ sound/soc/pxa/pxa-ssp.c | 11 +++++++++++ sound/soc/pxa/pxa2xx-i2s.c | 6 ++++++ 3 files changed, 23 insertions(+) diff --git a/sound/soc/pxa/mmp-sspa.c b/sound/soc/pxa/mmp-sspa.c index fbbce81680cf..f9066fbdd571 100644 --- a/sound/soc/pxa/mmp-sspa.c +++ b/sound/soc/pxa/mmp-sspa.c @@ -339,6 +339,10 @@ static int mmp_sspa_probe(struct snd_soc_dai *dai) SNDRV_PCM_FMTBIT_S24_3LE | \ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 mmp_sspa_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops mmp_sspa_dai_ops = { .probe = mmp_sspa_probe, .startup = mmp_sspa_startup, @@ -348,6 +352,8 @@ static const struct snd_soc_dai_ops mmp_sspa_dai_ops = { .set_sysclk = mmp_sspa_set_dai_sysclk, .set_pll = mmp_sspa_set_dai_pll, .set_fmt = mmp_sspa_set_dai_fmt, + .auto_selectable_formats = &mmp_sspa_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver mmp_sspa_dai = { diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 3a0abcb0bfbd..9a98246ad19c 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -809,6 +809,15 @@ static int pxa_ssp_remove(struct snd_soc_dai *dai) #define PXA_SSP_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 pxa_ssp_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops pxa_ssp_dai_ops = { .probe = pxa_ssp_probe, .remove = pxa_ssp_remove, @@ -820,6 +829,8 @@ static const struct snd_soc_dai_ops pxa_ssp_dai_ops = { .set_fmt = pxa_ssp_set_dai_fmt, .set_tdm_slot = pxa_ssp_set_dai_tdm_slot, .set_tristate = pxa_ssp_set_dai_tristate, + .auto_selectable_formats = &pxa_ssp_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver pxa_ssp_dai = { diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index fe1df78926f5..008cf5ae2607 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -328,6 +328,10 @@ static int pxa2xx_i2s_remove(struct snd_soc_dai *dai) SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) +static const u64 pxa_i2s_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops pxa_i2s_dai_ops = { .probe = pxa2xx_i2s_probe, .remove = pxa2xx_i2s_remove, @@ -337,6 +341,8 @@ static const struct snd_soc_dai_ops pxa_i2s_dai_ops = { .hw_params = pxa2xx_i2s_hw_params, .set_fmt = pxa2xx_i2s_set_dai_fmt, .set_sysclk = pxa2xx_i2s_set_dai_sysclk, + .auto_selectable_formats = &pxa_i2s_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver pxa_i2s_dai = { From 0d0f03f34a4df1f9e0cbedd63bf0c424bb84e55d Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:14:10 +0000 Subject: [PATCH 147/924] ASoC: renesas: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87jyp3yw4d.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/renesas/rz-ssi.c | 9 +++++++++ sound/soc/renesas/siu_dai.c | 6 ++++++ sound/soc/renesas/ssi.c | 13 +++++++++++++ 3 files changed, 28 insertions(+) diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c index 9fe8a639c47c..b1587f499cb8 100644 --- a/sound/soc/renesas/rz-ssi.c +++ b/sound/soc/renesas/rz-ssi.c @@ -900,6 +900,13 @@ static int rz_ssi_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 rz_ssi_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rz_ssi_dai_ops = { .probe = rz_ssi_dai_probe, .startup = rz_ssi_startup, @@ -907,6 +914,8 @@ static const struct snd_soc_dai_ops rz_ssi_dai_ops = { .trigger = rz_ssi_dai_trigger, .set_fmt = rz_ssi_dai_set_fmt, .hw_params = rz_ssi_dai_hw_params, + .auto_selectable_formats = &rz_ssi_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_pcm_hardware rz_ssi_pcm_hardware = { diff --git a/sound/soc/renesas/siu_dai.c b/sound/soc/renesas/siu_dai.c index 85dee9025710..fff6ef870f0d 100644 --- a/sound/soc/renesas/siu_dai.c +++ b/sound/soc/renesas/siu_dai.c @@ -688,12 +688,18 @@ static int siu_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, return ret; } +static const u64 siu_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops siu_dai_ops = { .startup = siu_dai_startup, .shutdown = siu_dai_shutdown, .prepare = siu_dai_prepare, .set_sysclk = siu_dai_set_sysclk, .set_fmt = siu_dai_set_fmt, + .auto_selectable_formats = &siu_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver siu_i2s_dai = { diff --git a/sound/soc/renesas/ssi.c b/sound/soc/renesas/ssi.c index 96cf523c2273..f00067bafe8d 100644 --- a/sound/soc/renesas/ssi.c +++ b/sound/soc/renesas/ssi.c @@ -329,6 +329,17 @@ static int ssi_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) +static const u64 ssi_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_GATED | + SND_SOC_POSSIBLE_DAIFMT_CONT | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops ssi_dai_ops = { .startup = ssi_startup, .shutdown = ssi_shutdown, @@ -337,6 +348,8 @@ static const struct snd_soc_dai_ops ssi_dai_ops = { .set_sysclk = ssi_set_sysclk, .set_clkdiv = ssi_set_clkdiv, .set_fmt = ssi_set_fmt, + .auto_selectable_formats = &ssi_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver sh4_ssi_dai[] = { From d39bf59dbb6aa074418296791ebc65fb6a5057c1 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:14:14 +0000 Subject: [PATCH 148/924] ASoC: rockchip: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ik4nyw4a.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_i2s.c | 25 +++++++++++++++------ sound/soc/rockchip/rockchip_i2s_tdm.c | 27 +++++++++++++++++------ sound/soc/rockchip/rockchip_pdm.c | 14 ++++++++---- sound/soc/rockchip/rockchip_sai.c | 31 +++++++++++++++++++-------- 4 files changed, 71 insertions(+), 26 deletions(-) diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c index 261f36d4c2fd..ef3f6586af77 100644 --- a/sound/soc/rockchip/rockchip_i2s.c +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -537,13 +537,26 @@ static int rockchip_i2s_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 rockchip_i2s_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rockchip_i2s_dai_ops = { - .probe = rockchip_i2s_dai_probe, - .hw_params = rockchip_i2s_hw_params, - .set_bclk_ratio = rockchip_i2s_set_bclk_ratio, - .set_sysclk = rockchip_i2s_set_sysclk, - .set_fmt = rockchip_i2s_set_fmt, - .trigger = rockchip_i2s_trigger, + .probe = rockchip_i2s_dai_probe, + .hw_params = rockchip_i2s_hw_params, + .set_bclk_ratio = rockchip_i2s_set_bclk_ratio, + .set_sysclk = rockchip_i2s_set_sysclk, + .set_fmt = rockchip_i2s_set_fmt, + .trigger = rockchip_i2s_trigger, + .auto_selectable_formats = &rockchip_i2s_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rockchip_i2s_dai = { diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c index 5ef7f109706d..983319a538b1 100644 --- a/sound/soc/rockchip/rockchip_i2s_tdm.c +++ b/sound/soc/rockchip/rockchip_i2s_tdm.c @@ -826,14 +826,27 @@ static int rockchip_i2s_tdm_set_bclk_ratio(struct snd_soc_dai *dai, return 0; } +static const u64 rockchip_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rockchip_i2s_tdm_dai_ops = { - .probe = rockchip_i2s_tdm_dai_probe, - .hw_params = rockchip_i2s_tdm_hw_params, - .set_bclk_ratio = rockchip_i2s_tdm_set_bclk_ratio, - .set_fmt = rockchip_i2s_tdm_set_fmt, - .set_sysclk = rockchip_i2s_tdm_set_sysclk, - .set_tdm_slot = rockchip_dai_tdm_slot, - .trigger = rockchip_i2s_tdm_trigger, + .probe = rockchip_i2s_tdm_dai_probe, + .hw_params = rockchip_i2s_tdm_hw_params, + .set_bclk_ratio = rockchip_i2s_tdm_set_bclk_ratio, + .set_fmt = rockchip_i2s_tdm_set_fmt, + .set_sysclk = rockchip_i2s_tdm_set_sysclk, + .set_tdm_slot = rockchip_dai_tdm_slot, + .trigger = rockchip_i2s_tdm_trigger, + .auto_selectable_formats = &rockchip_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_component_driver rockchip_i2s_tdm_component = { diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c index 343fda478901..0720748c7c8d 100644 --- a/sound/soc/rockchip/rockchip_pdm.c +++ b/sound/soc/rockchip/rockchip_pdm.c @@ -381,11 +381,17 @@ static int rockchip_pdm_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 rockchip_pdm_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rockchip_pdm_dai_ops = { - .probe = rockchip_pdm_dai_probe, - .set_fmt = rockchip_pdm_set_fmt, - .trigger = rockchip_pdm_trigger, - .hw_params = rockchip_pdm_hw_params, + .probe = rockchip_pdm_dai_probe, + .set_fmt = rockchip_pdm_set_fmt, + .trigger = rockchip_pdm_trigger, + .hw_params = rockchip_pdm_hw_params, + .auto_selectable_formats = &rockchip_pdm_selectable_formats, + .num_auto_selectable_formats = 1, }; #define ROCKCHIP_PDM_RATES SNDRV_PCM_RATE_8000_192000 diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c index 30b5e71d0937..9dfe46ffe0ff 100644 --- a/sound/soc/rockchip/rockchip_sai.c +++ b/sound/soc/rockchip/rockchip_sai.c @@ -957,16 +957,29 @@ static int rockchip_sai_set_sysclk(struct snd_soc_dai *dai, int stream, return 0; } +static const u64 rockchip_sai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rockchip_sai_dai_ops = { - .probe = rockchip_sai_dai_probe, - .startup = rockchip_sai_startup, - .shutdown = rockchip_sai_shutdown, - .hw_params = rockchip_sai_hw_params, - .set_fmt = rockchip_sai_set_fmt, - .set_sysclk = rockchip_sai_set_sysclk, - .prepare = rockchip_sai_prepare, - .trigger = rockchip_sai_trigger, - .set_tdm_slot = rockchip_sai_set_tdm_slot, + .probe = rockchip_sai_dai_probe, + .startup = rockchip_sai_startup, + .shutdown = rockchip_sai_shutdown, + .hw_params = rockchip_sai_hw_params, + .set_fmt = rockchip_sai_set_fmt, + .set_sysclk = rockchip_sai_set_sysclk, + .prepare = rockchip_sai_prepare, + .trigger = rockchip_sai_trigger, + .set_tdm_slot = rockchip_sai_set_tdm_slot, + .auto_selectable_formats = &rockchip_sai_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_driver rockchip_sai_dai = { From da4d9d64d6cbc1b973875cb9d431236915f01257 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:14:21 +0000 Subject: [PATCH 149/924] ASoC: sti: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87fqzryw42.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/sti/uniperif_player.c | 29 ++++++++++++++++++++--------- sound/soc/sti/uniperif_reader.c | 27 +++++++++++++++++++-------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index e4b9799ad9b2..844ddcfc421a 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -1028,16 +1028,27 @@ static int uni_player_parse_dt_audio_glue(struct platform_device *pdev, return 0; } +static const u64 uni_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops uni_player_dai_ops = { - .startup = uni_player_startup, - .shutdown = uni_player_shutdown, - .prepare = uni_player_prepare, - .probe = sti_uniperiph_dai_probe, - .trigger = uni_player_trigger, - .hw_params = sti_uniperiph_dai_hw_params, - .set_fmt = sti_uniperiph_dai_set_fmt, - .set_sysclk = uni_player_set_sysclk, - .set_tdm_slot = sti_uniperiph_set_tdm_slot + .startup = uni_player_startup, + .shutdown = uni_player_shutdown, + .prepare = uni_player_prepare, + .probe = sti_uniperiph_dai_probe, + .trigger = uni_player_trigger, + .hw_params = sti_uniperiph_dai_hw_params, + .set_fmt = sti_uniperiph_dai_set_fmt, + .set_sysclk = uni_player_set_sysclk, + .set_tdm_slot = sti_uniperiph_set_tdm_slot, + .auto_selectable_formats = &uni_selectable_formats, + .num_auto_selectable_formats = 1, }; int uni_player_init(struct platform_device *pdev, diff --git a/sound/soc/sti/uniperif_reader.c b/sound/soc/sti/uniperif_reader.c index 45d7613f595c..d92fc1181483 100644 --- a/sound/soc/sti/uniperif_reader.c +++ b/sound/soc/sti/uniperif_reader.c @@ -391,15 +391,26 @@ static void uni_reader_shutdown(struct snd_pcm_substream *substream, reader->substream = NULL; } +static const u64 uni_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops uni_reader_dai_ops = { - .startup = uni_reader_startup, - .shutdown = uni_reader_shutdown, - .prepare = uni_reader_prepare, - .probe = sti_uniperiph_dai_probe, - .trigger = uni_reader_trigger, - .hw_params = sti_uniperiph_dai_hw_params, - .set_fmt = sti_uniperiph_dai_set_fmt, - .set_tdm_slot = sti_uniperiph_set_tdm_slot + .startup = uni_reader_startup, + .shutdown = uni_reader_shutdown, + .prepare = uni_reader_prepare, + .probe = sti_uniperiph_dai_probe, + .trigger = uni_reader_trigger, + .hw_params = sti_uniperiph_dai_hw_params, + .set_fmt = sti_uniperiph_dai_set_fmt, + .set_tdm_slot = sti_uniperiph_set_tdm_slot, + .auto_selectable_formats = &uni_selectable_formats, + .num_auto_selectable_formats = 1, }; int uni_reader_init(struct platform_device *pdev, From 419884c9f124d974103c9fde09205767ec15055e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:14:25 +0000 Subject: [PATCH 150/924] ASoC: stm: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ecfbyw3z.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/stm/stm32_i2s.c | 12 ++++++++++++ sound/soc/stm/stm32_sai_sub.c | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c index 83b51893b37c..a59ad9442372 100644 --- a/sound/soc/stm/stm32_i2s.c +++ b/sound/soc/stm/stm32_i2s.c @@ -1089,6 +1089,16 @@ static const struct regmap_config stm32_h7_i2s_regmap_conf = { .cache_type = REGCACHE_FLAT, }; +static const u64 stm32_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops stm32_i2s_pcm_dai_ops = { .probe = stm32_i2s_dai_probe, .set_sysclk = stm32_i2s_set_sysclk, @@ -1097,6 +1107,8 @@ static const struct snd_soc_dai_ops stm32_i2s_pcm_dai_ops = { .hw_params = stm32_i2s_hw_params, .trigger = stm32_i2s_trigger, .shutdown = stm32_i2s_shutdown, + .auto_selectable_formats = &stm32_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_pcm_hardware stm32_i2s_pcm_hw = { diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c index 9a201bdb306f..d00377efe3f9 100644 --- a/sound/soc/stm/stm32_sai_sub.c +++ b/sound/soc/stm/stm32_sai_sub.c @@ -1358,6 +1358,17 @@ static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai) return stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, cr1_mask, cr1); } +static const u64 stm32_sai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops = { .probe = stm32_sai_dai_probe, .set_sysclk = stm32_sai_set_sysclk, @@ -1368,6 +1379,8 @@ static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops = { .trigger = stm32_sai_trigger, .shutdown = stm32_sai_shutdown, .pcm_new = stm32_sai_pcm_new, + .auto_selectable_formats = &stm32_sai_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops2 = { @@ -1379,6 +1392,8 @@ static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops2 = { .hw_params = stm32_sai_hw_params, .trigger = stm32_sai_trigger, .shutdown = stm32_sai_shutdown, + .auto_selectable_formats = &stm32_sai_selectable_formats, + .num_auto_selectable_formats = 1, }; static int stm32_sai_pcm_process_spdif(struct snd_pcm_substream *substream, From e64e684cf2063f7ef3cb4d240d81eacb9e40f73d Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:14:28 +0000 Subject: [PATCH 151/924] ASoC: sunxi: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87cxuvyw3v.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/sunxi/sun4i-i2s.c | 13 +++++++++++++ sound/soc/sunxi/sun8i-codec.c | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c index 716fa4d872ff..43e6ea2563fe 100644 --- a/sound/soc/sunxi/sun4i-i2s.c +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -1106,6 +1106,17 @@ static int sun4i_i2s_dai_startup(struct snd_pcm_substream *sub, struct snd_soc_d i2s->variant->pcm_formats); } +static const u64 sun4i_i2s_selectable_format = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops sun4i_i2s_dai_ops = { .probe = sun4i_i2s_dai_probe, .startup = sun4i_i2s_dai_startup, @@ -1114,6 +1125,8 @@ static const struct snd_soc_dai_ops sun4i_i2s_dai_ops = { .set_sysclk = sun4i_i2s_set_sysclk, .set_tdm_slot = sun4i_i2s_set_tdm_slot, .trigger = sun4i_i2s_trigger, + .auto_selectable_formats = &sun4i_i2s_selectable_format, + .num_auto_selectable_formats = 1, }; #define SUN4I_FORMATS_ALL (SNDRV_PCM_FMTBIT_S16_LE | \ diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c index 6bd42da57ac0..2e4a205e4e56 100644 --- a/sound/soc/sunxi/sun8i-codec.c +++ b/sound/soc/sunxi/sun8i-codec.c @@ -688,12 +688,25 @@ static int sun8i_codec_hw_free(struct snd_pcm_substream *substream, return 0; } +static const u64 sun8i_codec_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops sun8i_codec_dai_ops = { .set_fmt = sun8i_codec_set_fmt, .set_tdm_slot = sun8i_codec_set_tdm_slot, .startup = sun8i_codec_startup, .hw_params = sun8i_codec_hw_params, .hw_free = sun8i_codec_hw_free, + .auto_selectable_formats = &sun8i_codec_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver sun8i_codec_dais[] = { From 17e25fe32974975a24bd6cd5286bc73dba098120 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:14:32 +0000 Subject: [PATCH 152/924] ASoC: tegra: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Tested-by: Jon Hunter Acked-by: Jon Hunter Link: https://patch.msgid.link/87bjafyw3s.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra20_i2s.c | 10 ++++++++++ sound/soc/tegra/tegra210_i2s.c | 13 +++++++++++++ sound/soc/tegra/tegra30_i2s.c | 10 ++++++++++ 3 files changed, 33 insertions(+) diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c index d9ab210ad69a..b31f9477cb2e 100644 --- a/sound/soc/tegra/tegra20_i2s.c +++ b/sound/soc/tegra/tegra20_i2s.c @@ -309,12 +309,22 @@ static int tegra20_i2s_startup(struct snd_pcm_substream *substream, SNDRV_PCM_HW_PARAM_RATE, -1); } +static const u64 tegra20_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops tegra20_i2s_dai_ops = { .probe = tegra20_i2s_probe, .set_fmt = tegra20_i2s_set_fmt, .hw_params = tegra20_i2s_hw_params, .trigger = tegra20_i2s_trigger, .startup = tegra20_i2s_startup, + .auto_selectable_formats = &tegra20_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_driver tegra20_i2s_dai_template = { diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c index 79a2f898ea28..f2cfe3b5ccce 100644 --- a/sound/soc/tegra/tegra210_i2s.c +++ b/sound/soc/tegra/tegra210_i2s.c @@ -739,11 +739,24 @@ static int tegra210_i2s_hw_params(struct snd_pcm_substream *substream, cif_conf.client_ch); } +static const u64 tegra210_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops tegra210_i2s_dai_ops = { .set_fmt = tegra210_i2s_set_fmt, .hw_params = tegra210_i2s_hw_params, .set_bclk_ratio = tegra210_i2s_set_dai_bclk_ratio, .set_tdm_slot = tegra210_i2s_set_tdm_slot, + .auto_selectable_formats = &tegra210_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver tegra210_i2s_dais[] = { diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c index 1d7b3aed51fd..69acff73f68a 100644 --- a/sound/soc/tegra/tegra30_i2s.c +++ b/sound/soc/tegra/tegra30_i2s.c @@ -302,12 +302,22 @@ static int tegra30_i2s_probe(struct snd_soc_dai *dai) return 0; } +static const u64 tegra30_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops tegra30_i2s_dai_ops = { .probe = tegra30_i2s_probe, .set_fmt = tegra30_i2s_set_fmt, .hw_params = tegra30_i2s_hw_params, .trigger = tegra30_i2s_trigger, .set_tdm_slot = tegra30_i2s_set_tdm, + .auto_selectable_formats = &tegra30_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_driver tegra30_i2s_dai_template = { From 768d917175b28586ff268ff105a6eb39f9cef3c8 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:14:35 +0000 Subject: [PATCH 153/924] ASoC: ti: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87a4pzyw3o.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/ti/davinci-i2s.c | 14 +++++++++++++- sound/soc/ti/davinci-mcasp.c | 14 ++++++++++++++ sound/soc/ti/omap-mcbsp.c | 12 ++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/sound/soc/ti/davinci-i2s.c b/sound/soc/ti/davinci-i2s.c index 059967f0e632..ec7eb2f19b6f 100644 --- a/sound/soc/ti/davinci-i2s.c +++ b/sound/soc/ti/davinci-i2s.c @@ -743,6 +743,17 @@ static int davinci_i2s_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 davinci_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_CONT | + SND_SOC_POSSIBLE_DAIFMT_GATED | + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops davinci_i2s_dai_ops = { .probe = davinci_i2s_dai_probe, .shutdown = davinci_i2s_shutdown, @@ -752,7 +763,8 @@ static const struct snd_soc_dai_ops davinci_i2s_dai_ops = { .set_fmt = davinci_i2s_set_dai_fmt, .set_clkdiv = davinci_i2s_dai_set_clkdiv, .set_tdm_slot = davinci_i2s_set_tdm_slot, - + .auto_selectable_formats = &davinci_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver davinci_i2s_dai = { diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index 79239131b659..ad9e194a3d0d 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -1993,6 +1993,18 @@ static int davinci_mcasp_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 davinci_mcasp_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_AC97 | + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF; + static const struct snd_soc_dai_ops davinci_mcasp_dai_ops = { .probe = davinci_mcasp_dai_probe, .startup = davinci_mcasp_startup, @@ -2004,6 +2016,8 @@ static const struct snd_soc_dai_ops davinci_mcasp_dai_ops = { .set_clkdiv = davinci_mcasp_set_clkdiv, .set_sysclk = davinci_mcasp_set_sysclk, .set_tdm_slot = davinci_mcasp_set_tdm_slot, + .auto_selectable_formats = &davinci_mcasp_selectable_formats, + .num_auto_selectable_formats = 1, }; #define DAVINCI_MCASP_RATES SNDRV_PCM_RATE_8000_192000 diff --git a/sound/soc/ti/omap-mcbsp.c b/sound/soc/ti/omap-mcbsp.c index 26af616c33f5..5734388a4e4b 100644 --- a/sound/soc/ti/omap-mcbsp.c +++ b/sound/soc/ti/omap-mcbsp.c @@ -1269,6 +1269,16 @@ static int omap_mcbsp_remove(struct snd_soc_dai *dai) return 0; } +static const u64 mcbsp_mcbsp_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops mcbsp_dai_ops = { .probe = omap_mcbsp_probe, .remove = omap_mcbsp_remove, @@ -1281,6 +1291,8 @@ static const struct snd_soc_dai_ops mcbsp_dai_ops = { .set_fmt = omap_mcbsp_dai_set_dai_fmt, .set_clkdiv = omap_mcbsp_dai_set_clkdiv, .set_sysclk = omap_mcbsp_dai_set_dai_sysclk, + .auto_selectable_formats = &mcbsp_mcbsp_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver omap_mcbsp_dai = { From ebf365cf26231f2a47e65d6d6801609d8e55325b Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:14:39 +0000 Subject: [PATCH 154/924] ASoC: uniphier: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/878q5jyw3k.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/uniphier/aio-cpu.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/uniphier/aio-cpu.c b/sound/soc/uniphier/aio-cpu.c index 78c82bf42a8f..b263526f1990 100644 --- a/sound/soc/uniphier/aio-cpu.c +++ b/sound/soc/uniphier/aio-cpu.c @@ -440,6 +440,11 @@ static int uniphier_aio_pxs2_probe(struct snd_soc_dai *dai) return 0; } +static const u64 uniphier_aio_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + const struct snd_soc_dai_ops uniphier_aio_i2s_ld11_ops = { .probe = uniphier_aio_ld11_probe, .remove = uniphier_aio_dai_remove, @@ -451,6 +456,8 @@ const struct snd_soc_dai_ops uniphier_aio_i2s_ld11_ops = { .hw_params = uniphier_aio_hw_params, .hw_free = uniphier_aio_hw_free, .prepare = uniphier_aio_prepare, + .auto_selectable_formats = &uniphier_aio_selectable_formats, + .num_auto_selectable_formats = 1, }; EXPORT_SYMBOL_GPL(uniphier_aio_i2s_ld11_ops); @@ -492,6 +499,8 @@ const struct snd_soc_dai_ops uniphier_aio_i2s_pxs2_ops = { .hw_params = uniphier_aio_hw_params, .hw_free = uniphier_aio_hw_free, .prepare = uniphier_aio_prepare, + .auto_selectable_formats = &uniphier_aio_selectable_formats, + .num_auto_selectable_formats = 1, }; EXPORT_SYMBOL_GPL(uniphier_aio_i2s_pxs2_ops); From 7bbcb8bac2737cf3f23c3954818a0969c6bf9915 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:14:43 +0000 Subject: [PATCH 155/924] ASoC: ux500: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/877bl3yw3g.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 499e826d7120..3c760daf9a0d 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -681,18 +681,25 @@ static int ux500_msp_dai_of_probe(struct snd_soc_dai *dai) return 0; } -static const struct snd_soc_dai_ops ux500_msp_dai_ops[] = { - { - .probe = ux500_msp_dai_of_probe, - .set_sysclk = ux500_msp_dai_set_dai_sysclk, - .set_fmt = ux500_msp_dai_set_dai_fmt, - .set_tdm_slot = ux500_msp_dai_set_tdm_slot, - .startup = ux500_msp_dai_startup, - .shutdown = ux500_msp_dai_shutdown, - .prepare = ux500_msp_dai_prepare, - .trigger = ux500_msp_dai_trigger, - .hw_params = ux500_msp_dai_hw_params, - } +static const u64 ux500_msp_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + +static const struct snd_soc_dai_ops ux500_msp_dai_ops = { + .probe = ux500_msp_dai_of_probe, + .set_sysclk = ux500_msp_dai_set_dai_sysclk, + .set_fmt = ux500_msp_dai_set_dai_fmt, + .set_tdm_slot = ux500_msp_dai_set_tdm_slot, + .startup = ux500_msp_dai_startup, + .shutdown = ux500_msp_dai_shutdown, + .prepare = ux500_msp_dai_prepare, + .trigger = ux500_msp_dai_trigger, + .hw_params = ux500_msp_dai_hw_params, + .auto_selectable_formats = &ux500_msp_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ux500_msp_dai_drv = { @@ -704,7 +711,7 @@ static struct snd_soc_dai_driver ux500_msp_dai_drv = { .capture.channels_max = UX500_MSP_MAX_CHANNELS, .capture.rates = UX500_I2S_RATES, .capture.formats = UX500_I2S_FORMATS, - .ops = ux500_msp_dai_ops, + .ops = &ux500_msp_dai_ops, }; static const struct snd_soc_component_driver ux500_msp_component = { From 514d0b41854cb0d0b1f87d0cd86f1e97687eaba9 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 3 Sep 2026 01:14:46 +0000 Subject: [PATCH 156/924] ASoC: xtensa: use .auto_selectable_formats We can use .auto_selectable_formats. Let's adds it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/875x0nyw3d.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/xtensa/xtfpga-i2s.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c index fd0990201b40..e5e8b72fd817 100644 --- a/sound/soc/xtensa/xtfpga-i2s.c +++ b/sound/soc/xtensa/xtfpga-i2s.c @@ -485,10 +485,16 @@ static const struct snd_soc_component_driver xtfpga_i2s_component = { .legacy_dai_naming = 1, }; +static const u64 xtfpga_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops xtfpga_i2s_dai_ops = { - .startup = xtfpga_i2s_startup, - .hw_params = xtfpga_i2s_hw_params, - .set_fmt = xtfpga_i2s_set_fmt, + .startup = xtfpga_i2s_startup, + .hw_params = xtfpga_i2s_hw_params, + .set_fmt = xtfpga_i2s_set_fmt, + .auto_selectable_formats = &xtfpga_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver xtfpga_i2s_dai[] = { From e0fb2b8ac3c6a9e9ce7a098c6dd9272eb4ec6848 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sat, 12 Sep 2026 11:54:30 +0900 Subject: [PATCH 157/924] selftests/alsa: Test writes to IEC958 controls Nothing checks that the put() callback of an IEC958 control reports a change, because write_valid skips these controls. Where the device publishes a mask, toggle every bit in it and check each write. A bit the mask advertises that does not stick fails the test, the way a rejected value fails for the other control types. Where none is found, keep the old single guess at the non audio bit, which the device is free to ignore, so only the notification can be counted against it. That path writes without checking so drop_events() clears what those writes leave behind. On an HDA card with four HDMI PCMs the Con Mask is 0f ff, so each control walks eleven bits instead of one and all eleven stick: -# Totals: pass:212 fail:5 xfail:0 xpass:0 skip:56 error:0 +# Totals: pass:216 fail:5 xfail:0 xpass:0 skip:52 error:0 A driver whose put() never reports a change still shows up as event_missing. hdmi-codec and img-spdif-out store every byte and return 0, so boards using them report one event_missing per walked bit, and hdmi-codec advertises the whole block so that is 191 of them. A driver whose mask claims more than its put() keeps now fails write_valid outright: au88x0 and ice1712 on Delta 1010 and Mediastation advertise bytes they never store, cs46xx, oxygen and ice1724 advertise bits their put() drops, and the RME cards regenerate the whole emphasis field from one bit so the rest of it never reads back. None of that hardware was to hand, the paths were read rather than run. write_invalid stays a skip. Every bit pattern is a valid channel status. Signed-off-by: HyeongJun An Assisted-by: Claude:claude-opus-5 Reviewed-by: Mark Brown Link: https://patch.msgid.link/20260912025430.2102149-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/mixer-test.c | 255 ++++++++++++++++++++++ 1 file changed, 255 insertions(+) diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index a329f901c5ed..6edffcd6f2bb 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -30,6 +30,12 @@ #define TESTS_PER_CONTROL 7 +/* Suffixes of the SNDRV_CTL_NAME_IEC958() names, not exported to userspace */ +#define IEC958_DEFAULT "Default" +#define IEC958_CON_MASK "Con Mask" +#define IEC958_PRO_MASK "Pro Mask" +#define IEC958_MASK "Mask" + struct card_data { snd_ctl_t *handle; int card; @@ -842,6 +848,251 @@ static bool test_ctl_write_valid_enumerated(struct ctl_data *ctl) return !fail; } +/* + * Find the read only mask control for an IEC958 value control. The two + * share device, subdevice and index but not always the interface. + */ +static struct ctl_data *find_iec958_mask_ctl(struct ctl_data *ctl, + const char *suffix) +{ + char name[64]; + int stem; + struct ctl_data *mask; + + stem = strlen(ctl->name) - strlen(IEC958_DEFAULT); + if (snprintf(name, sizeof(name), "%.*s%s", stem, ctl->name, suffix) >= + (int)sizeof(name)) + return NULL; + + for (mask = ctl_list; mask != NULL; mask = mask->next) { + if (mask->card != ctl->card) + continue; + if (snd_ctl_elem_info_get_type(mask->info) != + SND_CTL_ELEM_TYPE_IEC958) + continue; + if (snd_ctl_elem_info_is_inactive(mask->info)) + continue; + if (!snd_ctl_elem_info_is_readable(mask->info)) + continue; + if (snd_ctl_elem_id_get_device(mask->id) != + snd_ctl_elem_id_get_device(ctl->id)) + continue; + if (snd_ctl_elem_id_get_subdevice(mask->id) != + snd_ctl_elem_id_get_subdevice(ctl->id)) + continue; + if (snd_ctl_elem_id_get_index(mask->id) != + snd_ctl_elem_id_get_index(ctl->id)) + continue; + if (strcmp(mask->name, name) == 0) + return mask; + } + + return NULL; +} + +/* + * Read the bits the device says it implements. Bit 0 of the first status + * byte picks which mask applies, some devices publish only a plain Mask. + */ +static bool read_iec958_mask(struct ctl_data *ctl, + const snd_aes_iec958_t *cur, + snd_aes_iec958_t *mask) +{ + int err; + struct ctl_data *mask_ctl; + snd_ctl_elem_value_t *val; + + if (!strend(ctl->name, IEC958_DEFAULT)) + return false; + + if (cur->status[0] & IEC958_AES0_PROFESSIONAL) + mask_ctl = find_iec958_mask_ctl(ctl, IEC958_PRO_MASK); + else + mask_ctl = find_iec958_mask_ctl(ctl, IEC958_CON_MASK); + if (!mask_ctl) + mask_ctl = find_iec958_mask_ctl(ctl, IEC958_MASK); + if (!mask_ctl) + return false; + + snd_ctl_elem_value_alloca(&val); + snd_ctl_elem_value_set_id(val, mask_ctl->id); + + err = snd_ctl_elem_read(mask_ctl->card->handle, val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n", + mask_ctl->name, snd_strerror(err)); + return false; + } + + snd_ctl_elem_value_get_iec958(val, mask); + + return true; +} + +/* + * Throw away the events from a write we are not checking, one left behind + * would make a missing notification look like a notification we got. + */ +static int drop_events(struct ctl_data *ctl) +{ + int err; + + do { + err = wait_for_event(ctl, 0); + } while (err > 0); + + return err; +} + +/* + * Toggle every bit the device advertises, one at a time. Each one starts + * from the value we read since a driver can look at the rest of the block + * when it stores a bit, and bit 0 of the first status byte selects the + * layout the others are read in so leave that one alone. + */ +static bool test_ctl_write_valid_iec958_mask(struct ctl_data *ctl, + snd_ctl_elem_value_t *orig_val, + const snd_aes_iec958_t *mask) +{ + int err, j; + unsigned int byte; + unsigned char bit; + bool fail = false, found = false; + snd_aes_iec958_t iec958; + snd_ctl_elem_value_t *val; + snd_ctl_elem_value_alloca(&val); + + snd_ctl_elem_value_copy(val, orig_val); + + for (byte = 0; byte < sizeof(mask->status); byte++) { + for (j = 0; j < 8; j++) { + bit = 1 << j; + + if (byte == 0 && bit == IEC958_AES0_PROFESSIONAL) + continue; + if (!(mask->status[byte] & bit)) + continue; + found = true; + + snd_ctl_elem_value_get_iec958(orig_val, &iec958); + iec958.status[byte] ^= bit; + snd_ctl_elem_value_set_iec958(val, &iec958); + + err = write_and_verify(ctl, val, NULL); + if (err != 0) { + ksft_print_msg("%s failed to set advertised status[%u] 0x%02x\n", + ctl->name, byte, bit); + fail = true; + } + } + } + + if (!found) + ksft_print_msg("%s implements no settable status bits\n", + ctl->name); + + return !fail; +} + +/* + * With nothing advertised all we can do is try non audio, and the device + * need not implement even that. Write it once and ignore the result, then + * compare against what came back so the value cannot fail, which leaves the + * notification counted by event_missing. + */ +static bool test_ctl_write_valid_iec958_guess(struct ctl_data *ctl, + snd_ctl_elem_value_t *orig_val) +{ + int err; + snd_aes_iec958_t iec958; + snd_ctl_elem_value_t *val, *read_val, *w_val; + snd_ctl_elem_value_alloca(&val); + snd_ctl_elem_value_alloca(&read_val); + snd_ctl_elem_value_alloca(&w_val); + + snd_ctl_elem_value_get_iec958(orig_val, &iec958); + iec958.status[0] ^= IEC958_AES0_NONAUDIO; + snd_ctl_elem_value_copy(val, orig_val); + snd_ctl_elem_value_set_iec958(val, &iec958); + + /* Writing can modify the value so keep a copy to write from */ + snd_ctl_elem_value_copy(w_val, val); + err = snd_ctl_elem_write(ctl->card->handle, w_val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_write() failed: %s\n", + snd_strerror(err)); + return false; + } + + snd_ctl_elem_value_set_id(read_val, ctl->id); + err = snd_ctl_elem_read(ctl->card->handle, read_val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed: %s\n", + snd_strerror(err)); + return false; + } + + /* Put it back where we found it, then drop the events from both */ + snd_ctl_elem_value_copy(w_val, orig_val); + err = snd_ctl_elem_write(ctl->card->handle, w_val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_write() failed: %s\n", + snd_strerror(err)); + return false; + } + + err = drop_events(ctl); + if (err < 0) { + ksft_print_msg("drop_events() failed for %s: %d\n", + ctl->name, err); + return false; + } + + if (snd_ctl_elem_value_compare(val, read_val)) { + ksft_print_msg("%s does not implement status[0] 0x%02x\n", + ctl->name, IEC958_AES0_NONAUDIO); + + /* Expect what came back, the notification is still checked */ + return write_and_verify(ctl, val, read_val) == 0; + } + + return write_and_verify(ctl, val, NULL) == 0; +} + +/* + * Write the channel status bits and check that userspace is told about it. + * Where a mask was found every bit in it has to stick, otherwise we fall + * back to one guessed bit the device is free to ignore. + */ +static bool test_ctl_write_valid_iec958(struct ctl_data *ctl) +{ + int err; + snd_aes_iec958_t iec958, mask; + snd_ctl_elem_value_t *orig_val; + snd_ctl_elem_value_alloca(&orig_val); + + /* + * The bytes past the ones a driver implements are compared too, + * so start from a read rather than building a value here. + */ + snd_ctl_elem_value_set_id(orig_val, ctl->id); + err = snd_ctl_elem_read(ctl->card->handle, orig_val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed: %s\n", + snd_strerror(err)); + return false; + } + + snd_ctl_elem_value_get_iec958(orig_val, &iec958); + + if (!read_iec958_mask(ctl, &iec958, &mask)) { + ksft_print_msg("%s falling back to non audio\n", ctl->name); + return test_ctl_write_valid_iec958_guess(ctl, orig_val); + } + + return test_ctl_write_valid_iec958_mask(ctl, orig_val, &mask); +} + static void test_ctl_write_valid(struct ctl_data *ctl) { bool pass; @@ -878,6 +1129,10 @@ static void test_ctl_write_valid(struct ctl_data *ctl) pass = test_ctl_write_valid_enumerated(ctl); break; + case SND_CTL_ELEM_TYPE_IEC958: + pass = test_ctl_write_valid_iec958(ctl); + break; + default: /* No tests for this yet */ ksft_test_result_skip("write_valid.%s.%d\n", From a347cc458d97ecd8adb5462b91559682a70aced6 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 12 Sep 2026 12:05:48 +0200 Subject: [PATCH 158/924] ALSA: control: Fix virtual master creation reference in kernel-doc The snd_ctl_add_follower() comment refers to snd_ctl_create_virtual_master(), but virtual master controls are created with snd_ctl_make_virtual_master(). Correct the helper name. Fixes: 79c7cdd5441f ("ALSA: Add kernel-doc comments to vmaster stuff") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260912100548.86134-1-kmehltretter@gmail.com Signed-off-by: Takashi Iwai --- include/sound/control.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/sound/control.h b/include/sound/control.h index 909db0d0485d..37e9c545503e 100644 --- a/include/sound/control.h +++ b/include/sound/control.h @@ -244,7 +244,7 @@ int _snd_ctl_add_follower(struct snd_kcontrol *master, * @follower: follower element to add * * Add a virtual follower control to the given master element created via - * snd_ctl_create_virtual_master() beforehand. + * snd_ctl_make_virtual_master() beforehand. * * All followers must be the same type (returning the same information * via info callback). The function doesn't check it, so it's your From a41da0bb086e7c170755c4d2bbb0520e82a2a738 Mon Sep 17 00:00:00 2001 From: David Cemin Date: Sat, 12 Sep 2026 11:21:20 -0700 Subject: [PATCH 159/924] ALSA: hda/acpi: add runtime PM suspend/resume to the ACPI controller The hda-acpi platform driver never enables runtime PM, so pm_runtime_force_suspend() in hda_acpi_suspend() skips the runtime callbacks. On resume the controller is therefore never re-initialised: azx_init_chip() is called only at probe time, so after S3 the codec verbs time out and audio is dead until the driver is rebound. Add explicit runtime_suspend/runtime_resume callbacks and invoke them from the system-sleep paths, mirroring the pattern used by snd-hda-intel. Tested on an arm64 platform with the NVDA2014 controller: resume latency drops from 81.5 s to 3.6 s and audio is live immediately after wake. Signed-off-by: David Cemin Link: https://patch.msgid.link/20260912182120.1156356-1-dcemin@nvidia.com Signed-off-by: Takashi Iwai --- sound/hda/controllers/acpi.c | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/sound/hda/controllers/acpi.c b/sound/hda/controllers/acpi.c index 505cc97e0ee9..04b261dde22e 100644 --- a/sound/hda/controllers/acpi.c +++ b/sound/hda/controllers/acpi.c @@ -266,6 +266,38 @@ static void hda_acpi_shutdown(struct platform_device *pdev) azx_stop_chip(chip); } +static int hda_acpi_runtime_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + + if (chip && chip->running) { + /* enable controller wake up event */ + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) | + STATESTS_INT_MASK); + + azx_stop_chip(chip); + azx_enter_link_reset(chip); + } + + return 0; +} + +static int hda_acpi_runtime_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + + if (chip && chip->running) { + azx_init_chip(chip, 1); + /* disable controller wake up event */ + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) & + ~STATESTS_INT_MASK); + } + + return 0; +} + static int hda_acpi_suspend(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); @@ -274,6 +306,12 @@ static int hda_acpi_suspend(struct device *dev) rc = pm_runtime_force_suspend(dev); if (rc < 0) return rc; + /* + * Runtime PM is never enabled for this device, so the force_suspend + * above does not reach the runtime callback; stop the controller + * explicitly. + */ + hda_acpi_runtime_suspend(dev); snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); return 0; @@ -287,6 +325,8 @@ static int hda_acpi_resume(struct device *dev) rc = pm_runtime_force_resume(dev); if (rc < 0) return rc; + /* See hda_acpi_suspend(): re-init the controller explicitly. */ + hda_acpi_runtime_resume(dev); snd_power_change_state(card, SNDRV_CTL_POWER_D0); return 0; @@ -294,6 +334,7 @@ static int hda_acpi_resume(struct device *dev) static const struct dev_pm_ops hda_acpi_pm = { SYSTEM_SLEEP_PM_OPS(hda_acpi_suspend, hda_acpi_resume) + RUNTIME_PM_OPS(hda_acpi_runtime_suspend, hda_acpi_runtime_resume, NULL) }; static const struct hda_data nvidia_hda_data = { From 81d2ec6f924e08efde646b60473997de0f568fd3 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Sat, 12 Sep 2026 09:24:18 +0100 Subject: [PATCH 160/924] ASoC: pcm5102a: Add support for optional reset gpio Add support for optional reset/enable gpios as per the trivial-codec.yaml bindings that cover this basic PCM5102A driver. Some addons gate the PCM5102A's supply through an external GPIO, and rather than forcing this by using a manual config to drive the GPIO high use the documented mechanism to do this. Based on similar functionality in the pcm1789 driver. Signed-off-by: Peter Robinson Link: https://patch.msgid.link/20260912082422.331962-1-pbrobinson@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/pcm5102a.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/pcm5102a.c b/sound/soc/codecs/pcm5102a.c index 9bca53de2475..ed1e29e54ef8 100644 --- a/sound/soc/codecs/pcm5102a.c +++ b/sound/soc/codecs/pcm5102a.c @@ -6,12 +6,17 @@ * Copyright 2013 */ +#include #include #include #include #include +struct pcm5102a_priv { + struct gpio_desc *gpio_reset; +}; + static struct snd_soc_dai_driver pcm5102a_dai = { .name = "pcm5102a-hifi", .playback = { @@ -32,10 +37,32 @@ static const struct snd_soc_component_driver soc_component_dev_pcm5102a = { static int pcm5102a_probe(struct platform_device *pdev) { - return devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_pcm5102a, + struct device *dev = &pdev->dev; + struct pcm5102a_priv *pcm5102a; + + pcm5102a = devm_kzalloc(dev, sizeof(*pcm5102a), GFP_KERNEL); + if (!pcm5102a) + return -ENOMEM; + + pcm5102a->gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + + if (IS_ERR(pcm5102a->gpio_reset)) + return dev_err_probe(dev, PTR_ERR(pcm5102a->gpio_reset), + "failed to acquire reset gpio\n"); + + platform_set_drvdata(pdev, pcm5102a); + + return devm_snd_soc_register_component(dev, &soc_component_dev_pcm5102a, &pcm5102a_dai, 1); } +static void pcm5102a_remove(struct platform_device *pdev) +{ + struct pcm5102a_priv *priv = platform_get_drvdata(pdev); + + gpiod_set_value_cansleep(priv->gpio_reset, 1); +} + static const struct of_device_id pcm5102a_of_match[] = { { .compatible = "ti,pcm5102a", }, { } @@ -44,6 +71,7 @@ MODULE_DEVICE_TABLE(of, pcm5102a_of_match); static struct platform_driver pcm5102a_codec_driver = { .probe = pcm5102a_probe, + .remove = pcm5102a_remove, .driver = { .name = "pcm5102a-codec", .of_match_table = pcm5102a_of_match, From eccc0dc233c83a5048136cdefe1fd1dd3a3c0600 Mon Sep 17 00:00:00 2001 From: Hanh Kieu Date: Thu, 3 Sep 2026 16:16:41 -0700 Subject: [PATCH 161/924] ALSA: usb-audio: add Pioneer DJ DDJ-SZ support The Pioneer DJ DDJ-SZ exposes its audio interface as USB vendor-specific class (0xFF) rather than USB Audio Class, so it needs a quirks-table entry like its sibling Pioneer devices (DJM-750, DJM-850, DJM-900NXS2, DJM-450, DJM-V10) already have. The device presents 10 channels of S24_3LE audio in both directions, fixed at 44.1kHz, on interface 0 altsetting 1: playback on endpoint 0x01, capture on endpoint 0x82. The unit contains its own analog mixer, and each playback channel pair feeds one of its physical channel strips: 0/1, 2/3, 4/5 and 6/7 feed strips 1-4 respectively, and 8/9 feed the booth output. The master output is produced in analog by that mixer and is not carried over USB at all. On the capture side, channels 8/9 are the mic input; capture channels 0-7 are not yet mapped to specific physical inputs. Implicit feedback needs no quirk flag here: is_pioneer_implicit_fb() in implicit.c already covers vendor 0x08e4 with a vendor-spec class interface and two endpoints, and the driver duly reports endpoint 0x82 as the playback sync endpoint. Activation reuses the existing pioneer_djm_set_format_quirk() used by the DJM-750/850/900NXS2/450/V10 (SET_INTERFACE to altsetting 1, then a UAC-shaped SET_CUR sample-rate control transfer) with this device's own captured wIndex (0x0082). Unlike those devices, the DDJ-SZ additionally needs a vendor "arm" sequence before its capture path produces real audio -- without it, capture opens and runs with no USB/ALSA errors but delivers silence (a hard zero on every channel) rather than any error, so this is easy to miss. The arm sequence is six vendor control transfers (bmRequestType=0x40, bRequest=3, varying wValue/wIndex, each followed by a bmRequestType=0xc0, bRequest=0 status read), replicated byte-for-byte from a USB capture of the official Windows driver. All of the above -- endpoint numbers, format, channel mapping, and the arm sequence bytes -- were determined by capturing and decoding real USB traffic from the Windows driver (USBPcap + Wireshark) during enumeration, playback, and mic recording, then verifying the format hypothesis against actual de-interleaved payload data rather than packet-size arithmetic alone. Both playback and capture have been verified working with real audio, not just clean enumeration. Signed-off-by: Hanh Kieu Link: https://patch.msgid.link/20260903231641.18536-1-hhkieu@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 47 +++++++++++++++++++++++++++++++ sound/usb/quirks.c | 60 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index a1a33f11de4a..2da8cbe3e6de 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -3731,6 +3731,53 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, +{ + /* + * Pioneer DJ DDJ-SZ + * 10 channels playback & 10 channels capture @ 44.1kHz S24LE + */ + USB_DEVICE_VENDOR_SPEC(0x08e4, 0x0191), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + QUIRK_COMPOSITE_END + } + } +}, { /* * Pioneer DJ DJM-750MK2 diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 8c6593ed6d2e..c659d6d11b99 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -1805,6 +1805,62 @@ static void set_format_emu_quirk(struct snd_usb_substream *subs, subs->pkt_offset_adj = (emu_samplerate_id >= EMU_QUIRK_SR_176400HZ) ? 4 : 0; } +/* + * The DDJ-SZ needs a vendor "arm" sequence before its capture path + * produces real audio; without it capture runs with no USB or ALSA error + * but delivers a hard zero on every channel. The sequence is replicated + * byte-for-byte from a USB capture of the Windows driver: each write is + * followed by a status read whose content is a fixed value regardless of + * what was written, but the read is replicated too, since it is unclear + * whether the device requires it to process the preceding write. + * + * This runs from snd_usb_set_format_quirk(), i.e. on every format setup + * rather than once per device. Re-arming is harmless in practice and + * keeps the device armed if it is reset behind our back. + */ +static void ddj_sz_arm_quirk(struct usb_device *dev) +{ + static const struct { + u16 value; + u16 index; + u8 read_len; + } cmds[] = { + { 0x0100, 0x8002, 6 }, + { 0x0200, 0x8002, 6 }, + { 0x0303, 0x8002, 6 }, + { 0x0403, 0x8002, 6 }, + { 0x050a, 0x8002, 6 }, + { 0x0000, 0x8003, 2 }, + }; + u8 buf[6]; + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(cmds); i++) { + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 3, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + cmds[i].value, cmds[i].index, NULL, 0); + if (err < 0) + goto err_out; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), 0, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + 0x0000, cmds[i].index, buf, + cmds[i].read_len); + if (err < 0) + goto err_out; + } + + return; + +err_out: + dev_warn(&dev->dev, + "DDJ-SZ: arm sequence step %u failed (%d), capture may be silent\n", + i, err); +} + static int pioneer_djm_set_format_quirk(struct snd_usb_substream *subs, u16 windex) { @@ -1986,6 +2042,10 @@ void snd_usb_set_format_quirk(struct snd_usb_substream *subs, case USB_ID(0x08e4, 0x0163): /* Pioneer DJM-850 */ pioneer_djm_set_format_quirk(subs, 0x0086); break; + case USB_ID(0x08e4, 0x0191): /* Pioneer DDJ-SZ */ + ddj_sz_arm_quirk(subs->dev); + pioneer_djm_set_format_quirk(subs, 0x0082); + break; case USB_ID(0x0dba, 0x5000): mbox3_set_format_quirk(subs, fmt); /* Digidesign Mbox 3 */ break; From b9e5343ba72a07a12d364e8ba6874e2a8fb617c6 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:15:26 +0900 Subject: [PATCH 162/924] ASoC: wm8962: Fix regulator notifier and beep leaks on card re-bind wm8962 registers its regulator disable notifiers and allocates its beep input device from the ASoC component probe, but the associated devres cleanup is tied to the underlying I2C device. These resources are only leaked when the sound card is unregistered and re-registered while the I2C device stays bound. On that path the component probe runs again and: - re-registers the same notifier_block on the still-registered regulator notifier chain, which triggers notifier callback wm8962_regulator_event_N already registered WARNING: ... at kernel/notifier.c:23 notifier_chain_register and corrupts the chain. - allocates and registers a new beep input device every time while wm8962_free_beep() only clears the pointer, leaking the previous input device and its sysfs/input node. Fix both: - Move the regulator notifier registration to wm8962_i2c_probe() so it runs once per I2C device bind. - Allocate the beep device with input_allocate_device() and pair it with the component lifecycle: input_free_device() on registration failure and input_unregister_device() in wm8962_free_beep(). Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260913101531.2787654-2-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8962.c | 52 +++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index 8a9598161b35..ea984b81297a 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -3355,7 +3355,7 @@ static void wm8962_init_beep(struct snd_soc_component *component) struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); int ret; - wm8962->beep = devm_input_allocate_device(component->dev); + wm8962->beep = input_allocate_device(); if (!wm8962->beep) { dev_err(component->dev, "Failed to allocate beep device\n"); return; @@ -3376,8 +3376,10 @@ static void wm8962_init_beep(struct snd_soc_component *component) ret = input_register_device(wm8962->beep); if (ret != 0) { + input_free_device(wm8962->beep); wm8962->beep = NULL; dev_err(component->dev, "Failed to register beep device\n"); + return; } ret = device_create_file(component->dev, &dev_attr_beep); @@ -3393,7 +3395,10 @@ static void wm8962_free_beep(struct snd_soc_component *component) device_remove_file(component->dev, &dev_attr_beep); cancel_work_sync(&wm8962->beep_work); - wm8962->beep = NULL; + if (wm8962->beep) { + input_unregister_device(wm8962->beep); + wm8962->beep = NULL; + } snd_soc_component_update_bits(component, WM8962_BEEP_GENERATOR_1, WM8962_BEEP_ENA,0); } @@ -3525,34 +3530,12 @@ static void wm8962_free_gpio(struct snd_soc_component *component) static int wm8962_probe(struct snd_soc_component *component) { struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); - int ret; struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); int i; bool dmicclk, dmicdat; wm8962->component = component; - wm8962->disable_nb[0].notifier_call = wm8962_regulator_event_0; - wm8962->disable_nb[1].notifier_call = wm8962_regulator_event_1; - wm8962->disable_nb[2].notifier_call = wm8962_regulator_event_2; - wm8962->disable_nb[3].notifier_call = wm8962_regulator_event_3; - wm8962->disable_nb[4].notifier_call = wm8962_regulator_event_4; - wm8962->disable_nb[5].notifier_call = wm8962_regulator_event_5; - wm8962->disable_nb[6].notifier_call = wm8962_regulator_event_6; - wm8962->disable_nb[7].notifier_call = wm8962_regulator_event_7; - - /* This should really be moved into the regulator core */ - for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++) { - ret = devm_regulator_register_notifier( - wm8962->supplies[i].consumer, - &wm8962->disable_nb[i]); - if (ret != 0) { - dev_err(component->dev, - "Failed to register regulator notifier: %d\n", - ret); - } - } - wm8962_add_widgets(component); /* Save boards having to disable DMIC when not in use */ @@ -3742,6 +3725,27 @@ static int wm8962_i2c_probe(struct i2c_client *i2c) regcache_cache_bypass(wm8962->regmap, false); + wm8962->disable_nb[0].notifier_call = wm8962_regulator_event_0; + wm8962->disable_nb[1].notifier_call = wm8962_regulator_event_1; + wm8962->disable_nb[2].notifier_call = wm8962_regulator_event_2; + wm8962->disable_nb[3].notifier_call = wm8962_regulator_event_3; + wm8962->disable_nb[4].notifier_call = wm8962_regulator_event_4; + wm8962->disable_nb[5].notifier_call = wm8962_regulator_event_5; + wm8962->disable_nb[6].notifier_call = wm8962_regulator_event_6; + wm8962->disable_nb[7].notifier_call = wm8962_regulator_event_7; + + /* This should really be moved into the regulator core */ + for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++) { + ret = devm_regulator_register_notifier(wm8962->supplies[i].consumer, + &wm8962->disable_nb[i]); + if (ret != 0) { + dev_err(&i2c->dev, + "Failed to register regulator notifier: %d\n", + ret); + goto err_enable; + } + } + ret = wm8962_reset(wm8962); if (ret < 0) { dev_err(&i2c->dev, "Failed to issue reset\n"); From 644914663c9f9820310d0384f61c42a836d3ed3a Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:15:27 +0900 Subject: [PATCH 163/924] ASoC: wm8995: Register regulator notifiers from the bus probe wm8995 requests its regulator supplies and registers the regulator disable notifiers from the ASoC component probe, but the associated devres cleanup is tied to the underlying I2C/SPI device. These resources are only mishandled when the sound card is unregistered and re-registered while the bus device stays bound. On that path the component probe runs again and re-registers the same notifier_block on the still-registered regulator notifier chain and corrupts the chain. Move the supply request and notifier registration into a helper, wm8995_hw_init(), and call it from wm8995_i2c_probe() and wm8995_spi_probe() so they run once per bus device bind. The regulator enable and the register accesses that need the component stay in wm8995_probe(). Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260913101531.2787654-3-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8995.c | 41 ++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/sound/soc/codecs/wm8995.c b/sound/soc/codecs/wm8995.c index 799989a5bf0f..35cbd0fb7f1a 100644 --- a/sound/soc/codecs/wm8995.c +++ b/sound/soc/codecs/wm8995.c @@ -1993,23 +1993,17 @@ static int wm8995_set_bias_level(struct snd_soc_component *component, return 0; } -static int wm8995_probe(struct snd_soc_component *component) +static int wm8995_hw_init(struct device *dev, struct wm8995_priv *wm8995) { - struct wm8995_priv *wm8995; - int i; - int ret; - - wm8995 = snd_soc_component_get_drvdata(component); - wm8995->component = component; + int i, ret; for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++) wm8995->supplies[i].supply = wm8995_supply_names[i]; - ret = devm_regulator_bulk_get(component->dev, - ARRAY_SIZE(wm8995->supplies), + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(wm8995->supplies), wm8995->supplies); if (ret) { - dev_err(component->dev, "Failed to request supplies: %d\n", ret); + dev_err(dev, "Failed to request supplies: %d\n", ret); return ret; } @@ -2024,16 +2018,27 @@ static int wm8995_probe(struct snd_soc_component *component) /* This should really be moved into the regulator core */ for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++) { - ret = devm_regulator_register_notifier( - wm8995->supplies[i].consumer, - &wm8995->disable_nb[i]); + ret = devm_regulator_register_notifier(wm8995->supplies[i].consumer, + &wm8995->disable_nb[i]); if (ret) { - dev_err(component->dev, + dev_err(dev, "Failed to register regulator notifier: %d\n", ret); + return ret; } } + return 0; +} + +static int wm8995_probe(struct snd_soc_component *component) +{ + struct wm8995_priv *wm8995; + int ret; + + wm8995 = snd_soc_component_get_drvdata(component); + wm8995->component = component; + ret = regulator_bulk_enable(ARRAY_SIZE(wm8995->supplies), wm8995->supplies); if (ret) { @@ -2216,6 +2221,10 @@ static int wm8995_spi_probe(struct spi_device *spi) return ret; } + ret = wm8995_hw_init(&spi->dev, wm8995); + if (ret) + return ret; + ret = devm_snd_soc_register_component(&spi->dev, &soc_component_dev_wm8995, wm8995_dai, ARRAY_SIZE(wm8995_dai)); @@ -2249,6 +2258,10 @@ static int wm8995_i2c_probe(struct i2c_client *i2c) return ret; } + ret = wm8995_hw_init(&i2c->dev, wm8995); + if (ret) + return ret; + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_wm8995, wm8995_dai, ARRAY_SIZE(wm8995_dai)); From 10e2ccaae4971fa1812b40d37d7d6ed3fcfb9102 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:15:28 +0900 Subject: [PATCH 164/924] ASoC: tlv320aic31xx: Register regulator notifier from the I2C probe aic31xx registers its regulator disable notifiers from the ASoC component probe, but the associated devres cleanup is tied to the underlying I2C device. The notifiers are only mishandled when the sound card is unregistered and re-registered while the I2C device stays bound. On that path the component probe runs again and re-registers the same notifier_block on the still-registered regulator notifier chain and corrupts the chain. Move the notifier registration to aic31xx_i2c_probe() so it runs once per I2C device bind, right after the supplies are requested there. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260913101531.2787654-4-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic31xx.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index 43bcbc5449e1..b0df6e6435e7 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -1366,27 +1366,12 @@ static int aic31xx_set_jack(struct snd_soc_component *component, static int aic31xx_codec_probe(struct snd_soc_component *component) { struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); - int i, ret; + int ret; dev_dbg(aic31xx->dev, "## %s\n", __func__); aic31xx->component = component; - for (i = 0; i < ARRAY_SIZE(aic31xx->supplies); i++) { - aic31xx->disable_nb[i].nb.notifier_call = - aic31xx_regulator_event; - aic31xx->disable_nb[i].aic31xx = aic31xx; - ret = devm_regulator_register_notifier( - aic31xx->supplies[i].consumer, - &aic31xx->disable_nb[i].nb); - if (ret) { - dev_err(component->dev, - "Failed to request regulator notifier: %d\n", - ret); - return ret; - } - } - regcache_cache_only(aic31xx->regmap, true); regcache_mark_dirty(aic31xx->regmap); @@ -1791,6 +1776,19 @@ static int aic31xx_i2c_probe(struct i2c_client *i2c) if (ret) return dev_err_probe(aic31xx->dev, ret, "Failed to request supplies\n"); + for (i = 0; i < ARRAY_SIZE(aic31xx->supplies); i++) { + aic31xx->disable_nb[i].nb.notifier_call = aic31xx_regulator_event; + aic31xx->disable_nb[i].aic31xx = aic31xx; + ret = devm_regulator_register_notifier(aic31xx->supplies[i].consumer, + &aic31xx->disable_nb[i].nb); + if (ret) { + dev_err(aic31xx->dev, + "Failed to request regulator notifier: %d\n", + ret); + return ret; + } + } + aic31xx_configure_ocmv(aic31xx); if (aic31xx->irq > 0) { From 3efb5b99f642736d132c10d368e6a9818dfd0bd8 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:15:29 +0900 Subject: [PATCH 165/924] ASoC: tlv320aic3x: Register regulator notifier from the bus probe aic3x registers its regulator disable notifiers from the ASoC component probe, but the associated devres cleanup is tied to the underlying bus device. The notifiers are only mishandled when the sound card is unregistered and re-registered while the bus device stays bound. On that path the component probe runs again and re-registers the same notifier_block on the still-registered regulator notifier chain and corrupts the chain. Move the notifier registration to aic3x_probe(), the shared bus probe helper called from the I2C and SPI probes, so it runs once per bus device bind, right after the supplies are requested there. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260913101531.2787654-5-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic3x.c | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index b38393a8130f..405c60ec1448 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1627,24 +1627,9 @@ static int aic3x_init(struct snd_soc_component *component) static int aic3x_component_probe(struct snd_soc_component *component) { struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); - int ret, i; aic3x->component = component; - for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) { - aic3x->disable_nb[i].nb.notifier_call = aic3x_regulator_event; - aic3x->disable_nb[i].aic3x = aic3x; - ret = devm_regulator_register_notifier( - aic3x->supplies[i].consumer, - &aic3x->disable_nb[i].nb); - if (ret) { - dev_err(component->dev, - "Failed to request regulator notifier: %d\n", - ret); - return ret; - } - } - regcache_mark_dirty(aic3x->regmap); aic3x_init(component); @@ -1845,6 +1830,19 @@ int aic3x_probe(struct device *dev, struct regmap *regmap, kernel_ulong_t driver if (ret) return dev_err_probe(dev, ret, "Failed to request supplies\n"); + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) { + aic3x->disable_nb[i].nb.notifier_call = aic3x_regulator_event; + aic3x->disable_nb[i].aic3x = aic3x; + ret = devm_regulator_register_notifier(aic3x->supplies[i].consumer, + &aic3x->disable_nb[i].nb); + if (ret) { + dev_err(dev, + "Failed to request regulator notifier: %d\n", + ret); + return ret; + } + } + aic3x_configure_ocmv(dev, aic3x); ret = devm_snd_soc_register_component(dev, &soc_component_dev_aic3x, &aic3x_dai, 1); From 115a5655bcbbcc79536c00fe07a5ffeffb3ac296 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:15:30 +0900 Subject: [PATCH 166/924] ASoC: cs42l52: Fix beep input device leak on card re-bind cs42l52 allocates its beep input device with devm_input_allocate_device() from the ASoC component probe, but the associated devres cleanup is tied to the underlying I2C device, while cs42l52_free_beep() on the component remove path only clears the cs42l52->beep pointer. The input device is only leaked when the sound card is unregistered and re-registered while the I2C device stays bound. On that path the component probe runs again and allocates and registers a new input device every time, leaking the previous one and its sysfs/input node. Allocate the beep device with input_allocate_device() and pair it with the component lifecycle: input_free_device() on registration failure and input_unregister_device() in cs42l52_free_beep(). Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260913101531.2787654-6-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l52.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/cs42l52.c b/sound/soc/codecs/cs42l52.c index 9d6bcfbbf0b2..0cced269f300 100644 --- a/sound/soc/codecs/cs42l52.c +++ b/sound/soc/codecs/cs42l52.c @@ -1000,7 +1000,7 @@ static void cs42l52_init_beep(struct snd_soc_component *component) struct cs42l52_private *cs42l52 = snd_soc_component_get_drvdata(component); int ret; - cs42l52->beep = devm_input_allocate_device(component->dev); + cs42l52->beep = input_allocate_device(); if (!cs42l52->beep) { dev_err(component->dev, "Failed to allocate beep device\n"); return; @@ -1021,8 +1021,10 @@ static void cs42l52_init_beep(struct snd_soc_component *component) ret = input_register_device(cs42l52->beep); if (ret != 0) { + input_free_device(cs42l52->beep); cs42l52->beep = NULL; dev_err(component->dev, "Failed to register beep device\n"); + return; } ret = device_create_file(component->dev, &dev_attr_beep); @@ -1038,7 +1040,10 @@ static void cs42l52_free_beep(struct snd_soc_component *component) device_remove_file(component->dev, &dev_attr_beep); cancel_work_sync(&cs42l52->beep_work); - cs42l52->beep = NULL; + if (cs42l52->beep) { + input_unregister_device(cs42l52->beep); + cs42l52->beep = NULL; + } snd_soc_component_update_bits(component, CS42L52_BEEP_TONE_CTL, CS42L52_BEEP_EN_MASK, 0); From c5907dcc13ad09c577534ad4ecacce4ba482156e Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:15:31 +0900 Subject: [PATCH 167/924] ASoC: cs42l56: Fix beep input device leak on card re-bind cs42l56 allocates its beep input device with devm_input_allocate_device() from the ASoC component probe, but the associated devres cleanup is tied to the underlying I2C device, while cs42l56_free_beep() on the component remove path only clears the cs42l56->beep pointer. The input device is only leaked when the sound card is unregistered and re-registered while the I2C device stays bound. On that path the component probe runs again and allocates and registers a new input device every time, leaking the previous one and its sysfs/input node. Allocate the beep device with input_allocate_device() and pair it with the component lifecycle: input_free_device() on registration failure and input_unregister_device() in cs42l56_free_beep(). Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260913101531.2787654-7-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l56.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/cs42l56.c b/sound/soc/codecs/cs42l56.c index 4d9a22a1029c..37df94fbc887 100644 --- a/sound/soc/codecs/cs42l56.c +++ b/sound/soc/codecs/cs42l56.c @@ -1077,7 +1077,7 @@ static void cs42l56_init_beep(struct snd_soc_component *component) struct cs42l56_private *cs42l56 = snd_soc_component_get_drvdata(component); int ret; - cs42l56->beep = devm_input_allocate_device(component->dev); + cs42l56->beep = input_allocate_device(); if (!cs42l56->beep) { dev_err(component->dev, "Failed to allocate beep device\n"); return; @@ -1098,8 +1098,10 @@ static void cs42l56_init_beep(struct snd_soc_component *component) ret = input_register_device(cs42l56->beep); if (ret != 0) { + input_free_device(cs42l56->beep); cs42l56->beep = NULL; dev_err(component->dev, "Failed to register beep device\n"); + return; } ret = device_create_file(component->dev, &dev_attr_beep); @@ -1115,7 +1117,10 @@ static void cs42l56_free_beep(struct snd_soc_component *component) device_remove_file(component->dev, &dev_attr_beep); cancel_work_sync(&cs42l56->beep_work); - cs42l56->beep = NULL; + if (cs42l56->beep) { + input_unregister_device(cs42l56->beep); + cs42l56->beep = NULL; + } snd_soc_component_update_bits(component, CS42L56_BEEP_TONE_CFG, CS42L56_BEEP_EN_MASK, 0); From bb1719bd0caf84dc29ac227b01c368b7878d3095 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Mon, 14 Sep 2026 12:19:16 +0200 Subject: [PATCH 168/924] ASoC: Intel: avs: Use list_count_nodes() to simplify the code By enlisting list_count_nodes(), a recent addition to the list.h, the code responsible for manual counting of the HDAudio PCMs can be dropped. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260914101917.2969665-1-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/hdaudio.c | 8 +++----- sound/soc/intel/avs/pcm.c | 7 +++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c index 2e15a293298d..c1d2d0082b14 100644 --- a/sound/soc/intel/avs/boards/hdaudio.c +++ b/sound/soc/intel/avs/boards/hdaudio.c @@ -147,17 +147,15 @@ static int avs_probing_link_init(struct snd_soc_pcm_runtime *rtm) struct snd_soc_dai_link *links = NULL; struct snd_soc_card *card = rtm->card; struct hda_codec *codec; - struct hda_pcm *pcm; - int ret, pcm_count = 0; + int ret, pcm_count; mach = dev_get_platdata(card->dev); pdata = mach->pdata; codec = pdata->codec; + pcm_count = list_count_nodes(&codec->pcm_list_head); - if (list_empty(&codec->pcm_list_head)) + if (!pcm_count) return -EINVAL; - list_for_each_entry(pcm, &codec->pcm_list_head, list) - pcm_count++; ret = avs_create_dai_links(card->dev, codec, pcm_count, &links); if (ret < 0) { diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c index ad25bd355769..8174d2ebfa99 100644 --- a/sound/soc/intel/avs/pcm.c +++ b/sound/soc/intel/avs/pcm.c @@ -1614,7 +1614,7 @@ static int avs_component_hda_probe(struct snd_soc_component *component) struct hda_codec *codec; struct hda_pcm *pcm; const char *cname; - int pcm_count = 0, ret, i; + int pcm_count, ret, i; mach = dev_get_platdata(component->card->dev); if (!mach) @@ -1622,10 +1622,9 @@ static int avs_component_hda_probe(struct snd_soc_component *component) pdata = mach->pdata; codec = pdata->codec; - if (list_empty(&codec->pcm_list_head)) + pcm_count = list_count_nodes(&codec->pcm_list_head); + if (!pcm_count) return -EINVAL; - list_for_each_entry(pcm, &codec->pcm_list_head, list) - pcm_count++; dais = devm_kcalloc(component->dev, pcm_count, sizeof(*dais), GFP_KERNEL); From 3411fcf7e1f7414398d1f1f3ec9ba07fd6ef1684 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Mon, 14 Sep 2026 12:19:17 +0200 Subject: [PATCH 169/924] ASoC: codecs: hda: Use list_count_nodes() to simplify the code By enlisting list_count_nodes(), a recent addition to the list.h, the code responsible for manual counting of the HDAudio PCMs can be dropped. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260914101917.2969665-2-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/hda.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/hda.c b/sound/soc/codecs/hda.c index 237b0b060457..084659106d5d 100644 --- a/sound/soc/codecs/hda.c +++ b/sound/soc/codecs/hda.c @@ -85,12 +85,11 @@ static int hda_codec_register_dais(struct hda_codec *codec, struct snd_soc_compo struct snd_soc_dai_driver *drvs = NULL; struct snd_soc_dapm_context *dapm; struct hda_pcm *pcm; - int ret, pcm_count = 0; + int ret, pcm_count; - if (list_empty(&codec->pcm_list_head)) + pcm_count = list_count_nodes(&codec->pcm_list_head); + if (!pcm_count) return -EINVAL; - list_for_each_entry(pcm, &codec->pcm_list_head, list) - pcm_count++; ret = hda_codec_create_dais(codec, pcm_count, &drvs); if (ret < 0) From 4b49d276dba291d34e5e4c40a00e3a280353b652 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 14 Sep 2026 14:16:21 +0100 Subject: [PATCH 170/924] ASoC: SDCA: Remove redundant comment Whilst the comment is correct DisCo does use upper-case for hex numbers the number being printed here is decimal so the comment is rather redundant. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260914131621.1461514-1-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_functions.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index b55e3ad8ea34..f39008ac0e7d 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -1983,7 +1983,6 @@ static int find_sdca_cluster_channels(struct device *dev, char channel_property[SDCA_PROPERTY_LENGTH]; struct fwnode_handle *channel_node; - /* DisCo uses upper-case for hex numbers */ snprintf(channel_property, sizeof(channel_property), "mipi-sdca-channel-%d-subproperties", i + 1); From 5b6ea1bb8af9d554104c7f4120f61712d56e31ed Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 14 Sep 2026 16:52:48 +0700 Subject: [PATCH 171/924] ALSA: pcm: Simplify success check in snd_pcm_open_file() snd_pcm_open_file() returns zero on success and a negative error code on failure. Use !err to check for a successful return instead of checking for a non-negative value. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260914095248.33391-1-phucduc.bui@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 62324282fcae..000e926a1e6a 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2930,7 +2930,7 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) mutex_lock(&pcm->open_mutex); while (1) { err = snd_pcm_open_file(file, pcm, stream); - if (err >= 0) + if (!err) break; if (err == -EAGAIN) { if (file->f_flags & O_NONBLOCK) { From 2e4c9ab2574eeaa7b893c7d35b4a9f60fd681ddd Mon Sep 17 00:00:00 2001 From: Nicholas Hobson Date: Sat, 12 Sep 2026 03:28:42 +0000 Subject: [PATCH 172/924] ALSA: usb-audio: keep MIDI input running for Roland TD-11 The TD-11 hands MIDI handling off to the host once connected via USB. If no MIDI input port is opened on the host, the device itself becomes unusable as a standalone instrument until its input is serviced, independent of anything happening on the computer. Add a per-device keep_input_running flag, set for the TD-11 in snd_usbmidi_detect_roland(), and check it alongside the existing opened[1] check in snd_usbmidi_input_start()/snd_usbmidi_input_stop() so that input is started at device creation and never stopped for these devices, regardless of whether a client has the input port open. Signed-off-by: Nicholas Hobson Link: https://patch.msgid.link/MN2PR15MB3472557AF39DC3A4CA9E753FF8BD2@MN2PR15MB3472.namprd15.prod.outlook.com Signed-off-by: Takashi Iwai --- sound/usb/midi.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sound/usb/midi.c b/sound/usb/midi.c index 8a9bc37f0b6e..be2b178d13d6 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -113,6 +113,7 @@ struct snd_usb_midi { unsigned int opened[2]; unsigned char disconnected; unsigned char input_running; + unsigned char keep_input_running; struct snd_kcontrol *roland_load_ctl; }; @@ -1170,7 +1171,7 @@ static int substream_open(struct snd_rawmidi_substream *substream, int dir, snd_usbmidi_input_start(&umidi->list); } else { umidi->opened[dir]--; - if (!umidi->opened[1]) + if (!umidi->opened[1] && !umidi->keep_input_running) snd_usbmidi_input_stop(&umidi->list); if (!umidi->opened[0] && !umidi->opened[1]) { if (umidi->roland_load_ctl) { @@ -2260,6 +2261,10 @@ static int snd_usbmidi_detect_roland(struct snd_usb_midi *umidi, struct usb_host_interface *hostif; u8 *cs_desc; + /* TD-11 becomes unusable if input isn't serviced continuously */ + if (umidi->usb_id == USB_ID(0x0582, 0x0151)) + umidi->keep_input_running = 1; + intf = umidi->iface; if (!intf) return -ENOENT; @@ -2467,7 +2472,7 @@ void snd_usbmidi_input_start(struct list_head *p) int i; umidi = list_entry(p, struct snd_usb_midi, list); - if (umidi->input_running || !umidi->opened[1]) + if (umidi->input_running || (!umidi->opened[1] && !umidi->keep_input_running)) return; for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) snd_usbmidi_input_start_ep(umidi, umidi->endpoints[i].in); @@ -2642,6 +2647,10 @@ int __snd_usbmidi_create(struct snd_card *card, if (err < 0) goto exit; + /* start input for devices that need continuous servicing */ + if (umidi->keep_input_running) + snd_usbmidi_input_start(&umidi->list); + usb_autopm_get_interface_no_resume(umidi->iface); list_add_tail(&umidi->list, midi_list); From 14318426e2c00ffda93606df64f74c345021eaf8 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:17:01 +0530 Subject: [PATCH 173/924] ASoC: mediatek: mt8192: Use devm_of_reserved_mem_device_init() Replace the hand-rolled devm wrapper (mt8192_afe_release_reserved_mem + devm_add_action_or_reset) with the standard devm_of_reserved_mem_device_init(), letting the device resource manager handle cleanup automatically. Acked-by: Mark Brown Signed-off-by: Mukesh Ojha Link: https://patch.msgid.link/20260902194712.2003414-2-mukesh.ojha@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-pcm.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c index db0ae44a86af..e4ecd23df180 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c @@ -2155,11 +2155,6 @@ static const dai_register_cb dai_register_cbs[] = { mt8192_dai_memif_register, }; -static void mt8192_afe_release_reserved_mem(void *data) -{ - of_reserved_mem_device_release(data); -} - static int mt8192_afe_pcm_dev_probe(struct platform_device *pdev) { struct mtk_base_afe *afe; @@ -2185,14 +2180,10 @@ static int mt8192_afe_pcm_dev_probe(struct platform_device *pdev) afe->dev = dev; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) { dev_info(dev, "no reserved memory found, pre-allocating buffers instead\n"); afe->preallocate_buffers = true; - } else { - ret = devm_add_action_or_reset(dev, mt8192_afe_release_reserved_mem, dev); - if (ret) - return ret; } /* init audio related clock */ From 68cd0442f674abe29ad17d273e0e4487f9913c0c Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:17:02 +0530 Subject: [PATCH 174/924] ASoC: mediatek: mt8196: Use devm_of_reserved_mem_device_init() Replace the hand-rolled devm wrapper (mt8196_afe_release_reserved_mem + devm_add_action_or_reset) with the standard devm_of_reserved_mem_device_init(), letting the device resource manager handle cleanup automatically. Acked-by: Mark Brown Signed-off-by: Mukesh Ojha Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260902194712.2003414-3-mukesh.ojha@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8196/mt8196-afe-pcm.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c index a1ae8322d8b6..dcee037991aa 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c @@ -2309,11 +2309,6 @@ static const struct reg_sequence mt8196_cg_patch[] = { { AUDIO_TOP_CON4, 0x361c }, }; -static void mt8196_afe_release_reserved_mem(void *data) -{ - of_reserved_mem_device_release(data); -} - static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) { int ret, i; @@ -2327,14 +2322,9 @@ static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) if (ret) return ret; - ret = of_reserved_mem_device_init(dev); - if (ret) { + ret = devm_of_reserved_mem_device_init(dev); + if (ret) dev_err(dev, "failed to assign memory region: %d\n", ret); - } else { - ret = devm_add_action_or_reset(dev, mt8196_afe_release_reserved_mem, dev); - if (ret) - return ret; - } afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); if (!afe) From 573d1749fcb3f19c3c37c657011eb3f5abb17aa7 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:17:03 +0530 Subject: [PATCH 175/924] ASoC: mediatek: mt8183: Use devm_of_reserved_mem_device_init() Replace the hand-rolled devm wrapper (mt8183_afe_release_reserved_mem + devm_add_action_or_reset) with the standard devm_of_reserved_mem_device_init(), letting the device resource manager handle cleanup automatically. Acked-by: Mark Brown Signed-off-by: Mukesh Ojha Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260902194712.2003414-4-mukesh.ojha@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8183/mt8183-afe-pcm.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c index 2634699534db..6ad396e3b10d 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c @@ -766,11 +766,6 @@ static const dai_register_cb dai_register_cbs[] = { mt8183_dai_memif_register, }; -static void mt8183_afe_release_reserved_mem(void *data) -{ - of_reserved_mem_device_release(data); -} - static int mt8183_afe_pcm_dev_probe(struct platform_device *pdev) { struct mtk_base_afe *afe; @@ -795,16 +790,10 @@ static int mt8183_afe_pcm_dev_probe(struct platform_device *pdev) afe_priv = afe->platform_priv; afe->dev = dev; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) { dev_info(dev, "no reserved memory found, pre-allocating buffers instead\n"); afe->preallocate_buffers = true; - } else { - ret = devm_add_action_or_reset(dev, - mt8183_afe_release_reserved_mem, - dev); - if (ret) - return ret; } /* initial audio related clock */ From 34dbfae252c2bee4c613836a931d280fc00b45bf Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:17:04 +0530 Subject: [PATCH 176/924] ASoC: mediatek: mt8189: Use devm_of_reserved_mem_device_init() Replace the hand-rolled devm wrapper (mt8189_afe_release_reserved_mem + devm_add_action_or_reset) with the standard devm_of_reserved_mem_device_init(), letting the device resource manager handle cleanup automatically. Acked-by: Mark Brown Signed-off-by: Mukesh Ojha Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260902194712.2003414-5-mukesh.ojha@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-pcm.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c index 77cf2b604f6c..04baf7d9132a 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c @@ -2421,11 +2421,6 @@ static const struct reg_sequence mt8189_cg_patch[] = { { AUDIO_TOP_CON4, 0x361c }, }; -static void mt8189_afe_release_reserved_mem(void *data) -{ - of_reserved_mem_device_release(data); -} - static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) { int ret, i; @@ -2439,16 +2434,9 @@ static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) if (ret) return ret; - ret = of_reserved_mem_device_init(dev); - if (ret) { + ret = devm_of_reserved_mem_device_init(dev); + if (ret) dev_warn(dev, "failed to assign memory region: %d\n", ret); - } else { - ret = devm_add_action_or_reset(dev, - mt8189_afe_release_reserved_mem, - dev); - if (ret) - return ret; - } afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); if (!afe) From f33929d53089f03b1c879466b451bd0e21778de2 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:17:06 +0530 Subject: [PATCH 177/924] ASoC: mediatek: mt8173: Use devm_of_reserved_mem_device_init() Use the devres-managed devm_of_reserved_mem_device_init() to ensure the reserved memory region is released on device removal, fixing a missing cleanup in the original code. Signed-off-by: Mukesh Ojha Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260902194712.2003414-7-mukesh.ojha@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8173/mt8173-afe-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c index 69cadc91c97f..0424d79bc9b0 100644 --- a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -1072,7 +1072,7 @@ static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) afe->dev = dev; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) { dev_info(dev, "no reserved memory found, pre-allocating buffers instead\n"); afe->preallocate_buffers = true; From 6b006d357f03993da15cfa0f3e618ab09f22a12d Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:17:10 +0530 Subject: [PATCH 178/924] ASoC: SOF: mediatek: mt8186: Use devm_of_reserved_mem_device_init() Use the devres-managed devm_of_reserved_mem_device_init() to ensure the reserved memory region is released on device removal, fixing a missing cleanup in the original code. Signed-off-by: Mukesh Ojha Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260902194712.2003414-11-mukesh.ojha@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/sof/mediatek/mt8186/mt8186.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sof/mediatek/mt8186/mt8186.c b/sound/soc/sof/mediatek/mt8186/mt8186.c index c1bea967737d..a5ccee87e684 100644 --- a/sound/soc/sof/mediatek/mt8186/mt8186.c +++ b/sound/soc/sof/mediatek/mt8186/mt8186.c @@ -49,7 +49,7 @@ static int platform_parse_resource(struct platform_device *pdev, void *data) struct mtk_adsp_chip_info *adsp = data; int ret; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) { dev_err(dev, "of_reserved_mem_device_init failed\n"); return ret; From 55be77e8a5fb677cb972c04598040590cf00edf4 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:17:07 +0530 Subject: [PATCH 179/924] ASoC: mediatek: mt8186: Use devm_of_reserved_mem_device_init() Use the devres-managed devm_of_reserved_mem_device_init() to ensure the reserved memory region is released on device removal, fixing a missing cleanup in the original code. Signed-off-by: Mukesh Ojha Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260902194712.2003414-8-mukesh.ojha@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8186/mt8186-afe-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c b/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c index 44a521c3a610..60576f885acd 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c @@ -2836,7 +2836,7 @@ static int mt8186_afe_pcm_dev_probe(struct platform_device *pdev) afe_priv = afe->platform_priv; afe->dev = &pdev->dev; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) { dev_info(dev, "no reserved memory found, pre-allocating buffers instead\n"); afe->preallocate_buffers = true; From 3531143e9cecf9728bf93a94eef0622ad2fbce53 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:17:11 +0530 Subject: [PATCH 180/924] ASoC: SOF: mediatek: mt8195: Use devm_of_reserved_mem_device_init() Use the devres-managed devm_of_reserved_mem_device_init() to ensure the reserved memory region is released on device removal, fixing a missing cleanup in the original code. Signed-off-by: Mukesh Ojha Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260902194712.2003414-12-mukesh.ojha@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/sof/mediatek/mt8195/mt8195.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sof/mediatek/mt8195/mt8195.c b/sound/soc/sof/mediatek/mt8195/mt8195.c index 4d6e9300a9c0..e4b6b6c18e02 100644 --- a/sound/soc/sof/mediatek/mt8195/mt8195.c +++ b/sound/soc/sof/mediatek/mt8195/mt8195.c @@ -49,7 +49,7 @@ static int platform_parse_resource(struct platform_device *pdev, void *data) struct mtk_adsp_chip_info *adsp = data; int ret; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) { dev_err(dev, "of_reserved_mem_device_init failed\n"); return ret; From 3167f3ca6c77f51c4e9debc301fd59d1b88df276 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:17:08 +0530 Subject: [PATCH 181/924] ASoC: mediatek: mt8188: Use devm_of_reserved_mem_device_init() Use the devres-managed devm_of_reserved_mem_device_init() to ensure the reserved memory region is released on device removal, fixing a missing cleanup in the original code. Signed-off-by: Mukesh Ojha Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260902194712.2003414-9-mukesh.ojha@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c index 7b1f5d05f4d6..ab4ce5e86b9d 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c @@ -3199,7 +3199,7 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) struct regmap *infra_ao; int i, irq_id, ret; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) dev_dbg(dev, "failed to assign memory region: %d\n", ret); From e88fd9fe81d729992e270c055bf108a48ec52dea Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:17:12 +0530 Subject: [PATCH 182/924] ASoC: sprd: Use devm_of_reserved_mem_device_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the devres-managed devm_of_reserved_mem_device_init() to ensure the reserved memory region is released on device removal, fixing a missing cleanup — the driver has no remove function. Also drop the now-unused local variable np. Signed-off-by: Mukesh Ojha Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260902194712.2003414-13-mukesh.ojha@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/sprd/sprd-pcm-dma.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c index f509a4601de2..2d5ffbe17e81 100644 --- a/sound/soc/sprd/sprd-pcm-dma.c +++ b/sound/soc/sprd/sprd-pcm-dma.c @@ -459,10 +459,9 @@ static const struct snd_soc_component_driver sprd_soc_component = { static int sprd_soc_platform_probe(struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node; int ret; - ret = of_reserved_mem_device_init_by_idx(&pdev->dev, np, 0); + ret = devm_of_reserved_mem_device_init(&pdev->dev); if (ret) dev_warn(&pdev->dev, "no reserved DMA memory for audio platform device\n"); From 4169b6a5cdd350a6ad47525ec117eb887234b5f5 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:17:09 +0530 Subject: [PATCH 183/924] ASoC: mediatek: mt8195: Use devm_of_reserved_mem_device_init() Use the devres-managed devm_of_reserved_mem_device_init() to ensure the reserved memory region is released on device removal, fixing a missing cleanup in the original code. Signed-off-by: Mukesh Ojha Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260902194712.2003414-10-mukesh.ojha@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-afe-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c index bc0a63457cd7..52c3381e6766 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c @@ -3013,7 +3013,7 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) struct reset_control *rstc; int i, irq_id, ret; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) return dev_err_probe(dev, ret, "failed to assign memory region\n"); From 4de44f666d57be93bdc3b6467229a65d86195545 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:02 +0530 Subject: [PATCH 184/924] ALSA: core: seq: oss: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-2-hemanth.selam@gmail.com --- sound/core/seq/oss/seq_oss_init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c index 1aece46c8b06..6586e07431c3 100644 --- a/sound/core/seq/oss/seq_oss_init.c +++ b/sound/core/seq/oss/seq_oss_init.c @@ -376,7 +376,7 @@ delete_seq_queue(int queue) /* - * free device informations - private_free callback of port + * free device information - private_free callback of port */ static void free_devinfo(void *private) From 895cc1e1eaf9d446e20da8788c679ffc639b50ce Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:03 +0530 Subject: [PATCH 185/924] ALSA: hda: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-3-hemanth.selam@gmail.com --- sound/hda/codecs/hdmi/hdmi.c | 2 +- sound/hda/codecs/realtek/alc882.c | 2 +- sound/hda/codecs/sigmatel.c | 6 +++--- sound/hda/common/codec.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c index 1e7a05c87733..a8aac0b36c73 100644 --- a/sound/hda/codecs/hdmi/hdmi.c +++ b/sound/hda/codecs/hdmi/hdmi.c @@ -1671,7 +1671,7 @@ int snd_hda_hdmi_generic_pcm_prepare(struct hda_pcm_stream *hinfo, * After S3, the audio driver restores pin:cvt selections * but this can happen before gfx is ready and such selection * is overlooked by HW. Thus multiple pins can share a same - * default convertor and mute control will affect each other, + * default converter and mute control will affect each other, * which can cause a resumed audio playback become silent * after S3. */ diff --git a/sound/hda/codecs/realtek/alc882.c b/sound/hda/codecs/realtek/alc882.c index fd466b6985f0..800c3d6f5134 100644 --- a/sound/hda/codecs/realtek/alc882.c +++ b/sound/hda/codecs/realtek/alc882.c @@ -372,7 +372,7 @@ static const struct hda_fixup alc882_fixups[] = { [ALC883_FIXUP_ACER_EAPD] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { - /* eanable EAPD on Acer laptops */ + /* enable EAPD on Acer laptops */ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, { 0x20, AC_VERB_SET_PROC_COEF, 0x3050 }, { } diff --git a/sound/hda/codecs/sigmatel.c b/sound/hda/codecs/sigmatel.c index ee3bd21adc36..9a8ba43667f7 100644 --- a/sound/hda/codecs/sigmatel.c +++ b/sound/hda/codecs/sigmatel.c @@ -361,7 +361,7 @@ static unsigned int stac_vref_led_power_filter(struct hda_codec *codec, return snd_hda_gen_path_power_filter(codec, nid, power_state); } -/* update mute-LED accoring to the master switch */ +/* update mute-LED according to the master switch */ static void stac_update_led_status(struct hda_codec *codec, bool muted) { struct sigmatel_spec *spec = codec->spec; @@ -1710,7 +1710,7 @@ static const struct hda_pintbl ref92hd73xx_pin_configs[] = { { 0x11, 0x01014030 }, // CD in { 0x12, 0x02319040 }, - // Digial Mic ins + // Digital Mic ins { 0x13, 0x90a000f0 }, { 0x14, 0x90a000f0 }, // Digital outs @@ -3817,7 +3817,7 @@ static const struct hda_pintbl dell_3st_pin_configs[] = { static void stac927x_fixup_ref_no_jd(struct hda_codec *codec, const struct hda_fixup *fix, int action) { - /* no jack detecion for ref-no-jd model */ + /* no jack detection for ref-no-jd model */ if (action == HDA_FIXUP_ACT_PRE_PROBE) codec->no_jack_detect = 1; } diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 7d17d773cfbf..b1965fb66814 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -3772,7 +3772,7 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec, EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_open); /** - * snd_hda_multi_out_analog_prepare - Preapre the analog outputs. + * snd_hda_multi_out_analog_prepare - Prepare the analog outputs. * @codec: the HDA codec * @mout: hda_multi_out object * @stream_tag: stream tag to assign From 121f6bb1457d2f79d1139ba951dd63c1f6c6dfdb Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:04 +0530 Subject: [PATCH 186/924] ALSA: usb-audio: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-4-hemanth.selam@gmail.com --- sound/usb/mixer.c | 4 ++-- sound/usb/mixer_us16x08.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index ecaa8bc08d7c..33a6a1281410 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -786,7 +786,7 @@ static int parse_term_uac2_iterm_unit(struct mixer_build *state, return err; /* save input term properties after recursion, - * to ensure they are not overriden by the recursion calls + * to ensure they are not overridden by the recursion calls */ term->id = id; term->type = le16_to_cpu(d->wTerminalType); @@ -809,7 +809,7 @@ static int parse_term_uac3_iterm_unit(struct mixer_build *state, return err; /* save input term properties after recursion, - * to ensure they are not overriden by the recursion calls + * to ensure they are not overridden by the recursion calls */ term->id = id; term->type = le16_to_cpu(d->wTerminalType); diff --git a/sound/usb/mixer_us16x08.h b/sound/usb/mixer_us16x08.h index 7b8583dd3b07..e98fe55265d1 100644 --- a/sound/usb/mixer_us16x08.h +++ b/sound/usb/mixer_us16x08.h @@ -60,7 +60,7 @@ #define SND_US16X08_ID_EQHIGHMIDLEVEL 0x03 #define SND_US16X08_ID_EQHIGHLEVEL 0x04 -/* EQ frequence IDs */ +/* EQ frequency IDs */ #define SND_US16X08_ID_EQLOWFREQ 0x11 #define SND_US16X08_ID_EQLOWMIDFREQ 0x12 #define SND_US16X08_ID_EQHIGHMIDFREQ 0x13 From 6dfd44c8b49bc8e1320ed5763e4fc2e73bae2bab Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:05 +0530 Subject: [PATCH 187/924] ALSA: i2c: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-5-hemanth.selam@gmail.com --- sound/i2c/other/ak4113.c | 2 +- sound/i2c/other/ak4114.c | 2 +- sound/i2c/other/ak4xxx-adda.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/i2c/other/ak4113.c b/sound/i2c/other/ak4113.c index 6ef5fffda8d7..9405d985a87d 100644 --- a/sound/i2c/other/ak4113.c +++ b/sound/i2c/other/ak4113.c @@ -130,7 +130,7 @@ void snd_ak4113_reinit(struct ak4113 *chip) scoped_guard(mutex, &chip->reinit_mutex) { ak4113_init_regs(chip); } - /* bring up statistics / event queing */ + /* bring up statistics / event queuing */ if (atomic_dec_and_test(&chip->wq_processing)) schedule_delayed_work(&chip->work, HZ / 10); } diff --git a/sound/i2c/other/ak4114.c b/sound/i2c/other/ak4114.c index 71efb29b6c7c..4c3dbbf8c316 100644 --- a/sound/i2c/other/ak4114.c +++ b/sound/i2c/other/ak4114.c @@ -135,7 +135,7 @@ void snd_ak4114_reinit(struct ak4114 *chip) scoped_guard(mutex, &chip->reinit_mutex) { ak4114_init_regs(chip); } - /* bring up statistics / event queing */ + /* bring up statistics / event queuing */ if (atomic_dec_and_test(&chip->wq_processing)) schedule_delayed_work(&chip->work, HZ / 10); } diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c index 9dd36b82a6ac..e7410f0f8660 100644 --- a/sound/i2c/other/ak4xxx-adda.c +++ b/sound/i2c/other/ak4xxx-adda.c @@ -147,7 +147,7 @@ EXPORT_SYMBOL(snd_akm4xxx_reset); * Volume conversion table for non-linear volumes * from -63.5dB (mute) to 0dB step 0.5dB * - * Used for AK4524/AK4620 input/ouput attenuation, AK4528, and + * Used for AK4524/AK4620 input/output attenuation, AK4528, and * AK5365 input attenuation */ static const unsigned char vol_cvt_datt[128] = { From 668f4f105e95a746111e8aa80c75276f6222fcb0 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:06 +0530 Subject: [PATCH 188/924] ALSA: emu10k1: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-6-hemanth.selam@gmail.com --- include/sound/emu10k1.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 4f94565c9d15..6474239a11c1 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -633,8 +633,8 @@ SUB_REG(PEFE, FILTERAMOUNT, 0x000000ff) /* Filter envlope amount */ #define A_ADCCR_RCHANENABLE 0x00000020 #define A_ADCCR_LCHANENABLE 0x00000010 -#define A_ADCCR_SAMPLERATE_MASK 0x0000000F /* Audigy sample rate convertor output rate */ -#define ADCCR_SAMPLERATE_MASK 0x00000007 /* Sample rate convertor output rate */ +#define A_ADCCR_SAMPLERATE_MASK 0x0000000F /* Audigy sample rate converter output rate */ +#define ADCCR_SAMPLERATE_MASK 0x00000007 /* Sample rate converter output rate */ #define ADCCR_SAMPLERATE_48 0x00000000 /* 48kHz sample rate */ #define ADCCR_SAMPLERATE_44 0x00000001 /* 44.1kHz sample rate */ #define ADCCR_SAMPLERATE_32 0x00000002 /* 32kHz sample rate */ From 96d3a4224ddd70d0c6d187138fcf77f2632b4af0 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:07 +0530 Subject: [PATCH 189/924] ALSA: emux: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-7-hemanth.selam@gmail.com --- include/sound/emux_legacy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/sound/emux_legacy.h b/include/sound/emux_legacy.h index 1127e30d33bb..55a7fbfb9bfa 100644 --- a/include/sound/emux_legacy.h +++ b/include/sound/emux_legacy.h @@ -5,7 +5,7 @@ /* * Copyright (c) 1999-2000 Takashi Iwai * - * Definitions of OSS compatible headers for Emu8000 device informations + * Definitions of OSS compatible headers for Emu8000 device information */ #include From 0dd77b42463e74a1ca4726afea3929d26f470041 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:08 +0530 Subject: [PATCH 190/924] ALSA: firewire: bebob: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-8-hemanth.selam@gmail.com --- sound/firewire/bebob/bebob_hwdep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c index 216d1fceb6e7..1c523525fad2 100644 --- a/sound/firewire/bebob/bebob_hwdep.c +++ b/sound/firewire/bebob/bebob_hwdep.c @@ -8,7 +8,7 @@ /* * This codes give three functionality. * - * 1.get firewire node infomation + * 1.get firewire node information * 2.get notification about starting/stopping stream * 3.lock/unlock stream */ From f86c83624a060b56e5d05828a98704534254b099 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:09 +0530 Subject: [PATCH 191/924] ALSA: opti9xx: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-9-hemanth.selam@gmail.com --- sound/isa/opti9xx/opti92x-ad1848.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/isa/opti9xx/opti92x-ad1848.c b/sound/isa/opti9xx/opti92x-ad1848.c index abaa3ed3ab5c..8a6db40ff893 100644 --- a/sound/isa/opti9xx/opti92x-ad1848.c +++ b/sound/isa/opti9xx/opti92x-ad1848.c @@ -378,7 +378,7 @@ static int snd_opti9xx_configure(struct snd_opti9xx *chip, */ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(21), 0x82, 0xff); /* - * This bit sets OPTI931 to automaticaly select FM + * This bit sets OPTI931 to automatically select FM * or digital input signal. */ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(26), 0x01, 0x01); From ee46d680ec067e8842ebec739781c46cc1f8d388 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:10 +0530 Subject: [PATCH 192/924] ALSA: hal2: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-10-hemanth.selam@gmail.com --- sound/mips/hal2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/mips/hal2.c b/sound/mips/hal2.c index 2beb6a0dc3b7..84f265335cf9 100644 --- a/sound/mips/hal2.c +++ b/sound/mips/hal2.c @@ -375,7 +375,7 @@ static void hal2_setup_dac(struct snd_hal2 *hal2) hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECTX); /* Setup the HAL2 for playback */ hal2_set_dac_rate(hal2); - /* Set endianess */ + /* Set endianness */ hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECTX); /* Set DMA bus */ hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr)); @@ -400,7 +400,7 @@ static void hal2_setup_adc(struct snd_hal2 *hal2) hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECR); /* Setup the HAL2 for record */ hal2_set_adc_rate(hal2); - /* Set endianess */ + /* Set endianness */ hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECR); /* Set DMA bus */ hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr)); From 73208e15dd559d2163425b09358aa0eda730d907 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:11 +0530 Subject: [PATCH 193/924] ALSA: ppc: tumbler: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-11-hemanth.selam@gmail.com --- sound/ppc/tumbler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c index 139c7b25927a..7ba52e7a24d7 100644 --- a/sound/ppc/tumbler.c +++ b/sound/ppc/tumbler.c @@ -1134,7 +1134,7 @@ static long tumbler_find_device(const char *device, const char *platform, gp->active_val = 0x4; gp->inactive_val = 0x5; /* Here are some crude hacks to extract the GPIO polarity and - * open collector informations out of the do-platform script + * open collector information out of the do-platform script * as we don't yet have an interpreter for these things */ if (platform) From ea68d0abc10b5a59841548c7efc62485c2f95ce8 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:12 +0530 Subject: [PATCH 194/924] ALSA: ac97: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-12-hemanth.selam@gmail.com --- sound/pci/ac97/ac97_codec.c | 2 +- sound/pci/ac97/ac97_patch.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c index 0bb65be021d9..207ce083593f 100644 --- a/sound/pci/ac97/ac97_codec.c +++ b/sound/pci/ac97/ac97_codec.c @@ -2960,7 +2960,7 @@ int snd_ac97_tune_hardware(struct snd_ac97 *ac97, { int result; - /* quirk overriden? */ + /* quirk overridden? */ if (override && strcmp(override, "-1") && strcmp(override, "default")) { result = apply_quirk_str(ac97, override); if (result < 0) diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c index 64cc39dd2008..8577a2e0510c 100644 --- a/sound/pci/ac97/ac97_patch.c +++ b/sound/pci/ac97/ac97_patch.c @@ -430,7 +430,7 @@ static int patch_yamaha_ymf753(struct snd_ac97 * ac97) /* Patch for Yamaha YMF753, Copyright (c) by David Shust, dshust@shustring.com. This chip has nonstandard and extended behaviour with regard to its S/PDIF output. The AC'97 spec states that the S/PDIF signal is to be output at pin 48. - The YMF753 will ouput the S/PDIF signal to pin 43, 47 (EAPD), or 48. + The YMF753 will output the S/PDIF signal to pin 43, 47 (EAPD), or 48. By default, no output pin is selected, and the S/PDIF signal is not output. There is also a bit to mute S/PDIF output in a vendor-specific register. */ From 4b3a2df06fbedca410497aceb6a15cbdd1848b28 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:13 +0530 Subject: [PATCH 195/924] ALSA: au88x0: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-13-hemanth.selam@gmail.com --- sound/pci/au88x0/au88x0.h | 2 +- sound/pci/au88x0/au88x0_mpu401.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/au88x0/au88x0.h b/sound/pci/au88x0/au88x0.h index 6cbb2bc4a048..f97a0303da3e 100644 --- a/sound/pci/au88x0/au88x0.h +++ b/sound/pci/au88x0/au88x0.h @@ -28,7 +28,7 @@ /* Vortex MPU401 defines. */ #define MIDI_CLOCK_DIV 0x61 -/* Standart MPU401 defines. */ +/* Standard MPU401 defines. */ #define MPU401_RESET 0xff #define MPU401_ENTER_UART 0x3f #define MPU401_ACK 0xfe diff --git a/sound/pci/au88x0/au88x0_mpu401.c b/sound/pci/au88x0/au88x0_mpu401.c index 164f6b7039ab..dff4bf80ab96 100644 --- a/sound/pci/au88x0/au88x0_mpu401.c +++ b/sound/pci/au88x0/au88x0_mpu401.c @@ -22,7 +22,7 @@ /* Vortex MPU401 defines. */ #define MIDI_CLOCK_DIV 0x61 -/* Standart MPU401 defines. */ +/* Standard MPU401 defines. */ #define MPU401_RESET 0xff #define MPU401_ENTER_UART 0x3f #define MPU401_ACK 0xfe From f1e7c286f48111dd21149cbf8d4979528c2e7e7d Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:14 +0530 Subject: [PATCH 196/924] ALSA: aw2: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-14-hemanth.selam@gmail.com --- sound/pci/aw2/aw2-saa7146.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/aw2/aw2-saa7146.c b/sound/pci/aw2/aw2-saa7146.c index c84f1a45194f..6857a0ec2924 100644 --- a/sound/pci/aw2/aw2-saa7146.c +++ b/sound/pci/aw2/aw2-saa7146.c @@ -147,7 +147,7 @@ void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip, unsigned long dw_page, dw_limit; /* Configure DMA for substream - Configuration informations: ALSA has allocated continuous memory + Configuration information: ALSA has allocated continuous memory pages. So we don't need to use MMU of saa7146. */ @@ -204,7 +204,7 @@ void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip, unsigned long dw_page, dw_limit; /* Configure DMA for substream - Configuration informations: ALSA has allocated continuous memory + Configuration information: ALSA has allocated continuous memory pages. So we don't need to use MMU of saa7146. */ From 1e1eeab47c3223676a61fa49869d45636f68fff3 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:15 +0530 Subject: [PATCH 197/924] ALSA: cs4281: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-15-hemanth.selam@gmail.com --- sound/pci/cs4281.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index f51f4bb63766..27ceda6278c5 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -324,7 +324,7 @@ MODULE_PARM_DESC(dual_codec, "Secondary Codec ID (0 = disabled)."); #define BA0_SSCR_XLPSRC (1<<8) /* External SRC Loopback Mode */ #define BA0_SSCR_LPSRC (1<<7) /* SRC Loopback Mode */ #define BA0_SSCR_CDTX (1<<5) /* CD Transfer Data */ -#define BA0_SSCR_HVC (1<<3) /* Harware Volume Control Enable */ +#define BA0_SSCR_HVC (1<<3) /* Hardware Volume Control Enable */ #define BA0_FMLVC 0x0754 /* FM Synthesis Left Volume Control */ #define BA0_FMRVC 0x0758 /* FM Synthesis Right Volume Control */ From 54999c03def8f667f9105d2f6e75e82e5d05a759 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:16 +0530 Subject: [PATCH 198/924] ALSA: cs46xx: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-16-hemanth.selam@gmail.com --- sound/pci/cs46xx/cs46xx_lib.c | 6 +++--- sound/pci/cs46xx/dsp_spos_scb_lib.c | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c index 19a6927c079d..a16586f7d913 100644 --- a/sound/pci/cs46xx/cs46xx_lib.c +++ b/sound/pci/cs46xx/cs46xx_lib.c @@ -315,7 +315,7 @@ int snd_cs46xx_download(struct snd_cs46xx *chip, dst = chip->region.idx[bank+1].remap_addr + offset; len /= sizeof(u32); - /* writel already converts 32-bit value to right endianess */ + /* writel already converts 32-bit value to right endianness */ while (len-- > 0) { writel(*src++, dst); dst += sizeof(u32); @@ -474,7 +474,7 @@ int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip, dst = chip->region.idx[bank+1].remap_addr + offset; len /= sizeof(u32); - /* writel already converts 32-bit value to right endianess */ + /* writel already converts 32-bit value to right endianness */ while (len-- > 0) { writel(0, dst); dst += sizeof(u32); @@ -3039,7 +3039,7 @@ static int snd_cs46xx_chip_init(struct snd_cs46xx *chip) #endif /* - * Assert the vaid frame signal so that we can start sending commands + * Assert the valid frame signal so that we can start sending commands * to the AC97 codec. */ snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); diff --git a/sound/pci/cs46xx/dsp_spos_scb_lib.c b/sound/pci/cs46xx/dsp_spos_scb_lib.c index fd19365026b4..71eee5cb478f 100644 --- a/sound/pci/cs46xx/dsp_spos_scb_lib.c +++ b/sound/pci/cs46xx/dsp_spos_scb_lib.c @@ -160,15 +160,15 @@ void cs46xx_dsp_remove_scb (struct snd_cs46xx *chip, struct dsp_scb_descriptor * { struct dsp_spos_instance * ins = chip->dsp_spos_instance; - /* check integrety */ + /* check integrity */ if (snd_BUG_ON(scb->index < 0 || scb->index >= ins->nscb || (ins->scbs + scb->index) != scb)) return; #if 0 - /* can't remove a SCB with childs before - removing childs first */ + /* can't remove a SCB with children before + removing children first */ if (snd_BUG_ON(scb->sub_list_ptr != ins->the_null_scb || scb->next_scb_ptr != ins->the_null_scb)) goto _end; @@ -1656,7 +1656,7 @@ int cs46xx_dsp_disable_spdif_out (struct snd_cs46xx *chip) return -EBUSY; } - /* check integrety */ + /* check integrity */ if (snd_BUG_ON(!ins->asynch_tx_scb)) return -EINVAL; if (snd_BUG_ON(!ins->spdif_pcm_input_scb)) From 77db026cb8d90529e856dcfd0304b4eff5ce89b7 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:17 +0530 Subject: [PATCH 199/924] ALSA: echoaudio: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-17-hemanth.selam@gmail.com --- sound/pci/echoaudio/echoaudio.c | 4 ++-- sound/pci/echoaudio/echoaudio.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c index 214d68657707..8b4637231deb 100644 --- a/sound/pci/echoaudio/echoaudio.c +++ b/sound/pci/echoaudio/echoaudio.c @@ -270,7 +270,7 @@ static int pcm_open(struct snd_pcm_substream *substream, return -ENOMEM; pipe->index = -1; /* Not configured yet */ - /* Set up hw capabilities and contraints */ + /* Set up hw capabilities and constraints */ memcpy(&pipe->hw, &pcm_hardware_skel, sizeof(struct snd_pcm_hardware)); dev_dbg(chip->card->dev, "max_channels=%d\n", max_channels); pipe->constr.list = channels_list; @@ -1720,7 +1720,7 @@ static const struct snd_kcontrol_new snd_echo_vumeters = { -/*** Channels info - it exports informations about the number of channels ***/ +/*** Channels info - it exports information about the number of channels ***/ static int snd_echo_channels_info_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { diff --git a/sound/pci/echoaudio/echoaudio.h b/sound/pci/echoaudio/echoaudio.h index 7a5744bf3155..ac4507b7cc78 100644 --- a/sound/pci/echoaudio/echoaudio.h +++ b/sound/pci/echoaudio/echoaudio.h @@ -394,8 +394,8 @@ struct echoaudio { u16 digital_modes; /* Bitmask of supported modes * (see ECHOCAPS_HAS_DIGITAL_MODE_*) */ - u16 input_clock_types; /* Suppoted input clock types */ - u16 output_clock_types; /* Suppoted output clock types - + u16 input_clock_types; /* Supported input clock types */ + u16 output_clock_types; /* Supported output clock types - * Layla20 only */ u16 device_id, subdevice_id; u16 *dsp_code; /* Current DSP code loaded, From 948f8c97b1ba941ac59e4dcc149513bbcf7724c6 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:18 +0530 Subject: [PATCH 200/924] ALSA: lx6464es: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-18-hemanth.selam@gmail.com --- sound/pci/lx6464es/lx_core.c | 2 +- sound/pci/lx6464es/lx_defs.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/lx6464es/lx_core.c b/sound/pci/lx6464es/lx_core.c index 9909f1067871..ae265dd19333 100644 --- a/sound/pci/lx6464es/lx_core.c +++ b/sound/pci/lx6464es/lx_core.c @@ -364,7 +364,7 @@ int lx_dsp_get_mac(struct lx6464es *chip) macmsb = lx_dsp_reg_read(chip, eReg_ADMACESMSB) & 0x00FFFFFF; maclsb = lx_dsp_reg_read(chip, eReg_ADMACESLSB) & 0x00FFFFFF; - /* todo: endianess handling */ + /* todo: endianness handling */ chip->mac_address[5] = ((u8 *)(&maclsb))[0]; chip->mac_address[4] = ((u8 *)(&maclsb))[1]; chip->mac_address[3] = ((u8 *)(&maclsb))[2]; diff --git a/sound/pci/lx6464es/lx_defs.h b/sound/pci/lx6464es/lx_defs.h index eca5367ba561..89e433d3f7a2 100644 --- a/sound/pci/lx6464es/lx_defs.h +++ b/sound/pci/lx6464es/lx_defs.h @@ -315,7 +315,7 @@ enum stream_flags { #define ED_XILINX_ERROR (ED_RT | 0x07) #define ED_COBRANET_ITF_NOT_RESPONDING (ED_RT | 0x08) -/* Complete BOARD error code for the invaid standard object class */ +/* Complete BOARD error code for the invalid standard object class */ #define EB_ISO (ERROR_VALUE | E_SOURCE_BOARD | \ E_CLASS_INVALID_STD_OBJECT) #define EB_INVALID_EFFECT (EB_ISO | 0x00) From b86af3864b395ffc3484839408644a0bb614ee51 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:19 +0530 Subject: [PATCH 201/924] ALSA: pcxhr: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-19-hemanth.selam@gmail.com --- sound/pci/pcxhr/pcxhr_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/pcxhr/pcxhr_core.c b/sound/pci/pcxhr/pcxhr_core.c index b3b9ab4f303e..f855d3bfad4b 100644 --- a/sound/pci/pcxhr/pcxhr_core.c +++ b/sound/pci/pcxhr/pcxhr_core.c @@ -802,7 +802,7 @@ static int pcxhr_prepair_pipe_start(struct pcxhr_mgr *mgr, err); return err; } - /* if the pipe couldn't be prepaired for start, + /* if the pipe couldn't be prepared for start, * retry it later */ if (rmh.stat[0] == 0) @@ -913,7 +913,7 @@ int pcxhr_set_pipe_state(struct pcxhr_mgr *mgr, int playback_mask, if (err) return err; if (state == 0) - break; /* success, all pipes prepaired */ + break; /* success, all pipes prepared */ mdelay(1); /* wait 1 millisecond and retry */ } } else { From 515b9e296462033cece3a7918ecfb89e96f492ed Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:20 +0530 Subject: [PATCH 202/924] ALSA: riptide: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-20-hemanth.selam@gmail.com --- sound/pci/riptide/riptide.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/riptide/riptide.c b/sound/pci/riptide/riptide.c index 99c00e46ce23..697fece744e7 100644 --- a/sound/pci/riptide/riptide.c +++ b/sound/pci/riptide/riptide.c @@ -264,7 +264,7 @@ MODULE_PARM_DESC(opl3_port, "OPL3 port # for Riptide driver."); #define SEND_SI2S(p,b) sendcmd(p,PARM,SI2S,WORD2(b),RET(0)) #define EOB_STATUS 0x80000000 /* status flags : block boundary */ -#define EOS_STATUS 0x40000000 /* : stoppped */ +#define EOS_STATUS 0x40000000 /* : stopped */ #define EOC_STATUS 0x20000000 /* : stream end */ #define ERR_STATUS 0x10000000 #define EMPTY_STATUS 0x08000000 @@ -453,7 +453,7 @@ struct snd_riptide { int in_suspend; }; -struct sgd { /* scatter gather desriptor */ +struct sgd { /* scatter gather descriptor */ __le32 dwNextLink; __le32 dwSegPtrPhys; __le32 dwSegLen; From 648324a4d3421828d7d40c93782ac980b16a8ba3 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:21 +0530 Subject: [PATCH 203/924] ALSA: rme32: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-21-hemanth.selam@gmail.com --- sound/pci/rme32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/rme32.c b/sound/pci/rme32.c index 454a30a2c07e..1d5390621908 100644 --- a/sound/pci/rme32.c +++ b/sound/pci/rme32.c @@ -8,7 +8,7 @@ * Thanks to : Anders Torger , * Henk Hesselink * for writing the digi96-driver - * and RME for all informations. + * and RME for all information. * * **************************************************************************** * From 17e335c45b61069caaeeafe879554f743635eec2 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:22 +0530 Subject: [PATCH 204/924] ALSA: hdsp: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-22-hemanth.selam@gmail.com --- sound/pci/rme9652/hdsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c index 31cc2d91c8d2..0ade73057731 100644 --- a/sound/pci/rme9652/hdsp.c +++ b/sound/pci/rme9652/hdsp.c @@ -3664,7 +3664,7 @@ snd_hdsp_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) snd_iprintf(buffer, "\n"); - /* Informations about H9632 specific controls */ + /* Information about H9632 specific controls */ if (hdsp->io_type == H9632) { char *tmp; From 162c237ca2fc5a2619c7fc156941f681b9acf8dd Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:23 +0530 Subject: [PATCH 205/924] ALSA: trident: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-23-hemanth.selam@gmail.com --- sound/pci/trident/trident_memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/trident/trident_memory.c b/sound/pci/trident/trident_memory.c index 81f6348191dc..4ed0e7d68322 100644 --- a/sound/pci/trident/trident_memory.c +++ b/sound/pci/trident/trident_memory.c @@ -27,7 +27,7 @@ #if PAGE_SIZE == 4096 /* page size == SNDRV_TRIDENT_PAGE_SIZE */ #define ALIGN_PAGE_SIZE PAGE_SIZE /* minimum page size for allocation */ -#define MAX_ALIGN_PAGES SNDRV_TRIDENT_MAX_PAGES /* maxmium aligned pages */ +#define MAX_ALIGN_PAGES SNDRV_TRIDENT_MAX_PAGES /* maximum aligned pages */ /* fill TLB entrie(s) corresponding to page with ptr */ #define set_tlb_bus(trident,page,addr) __set_tlb_bus(trident,page,addr) /* fill TLB entrie(s) corresponding to page with silence pointer */ From ad563e61f576b269e09e53232a1809541c87c8c1 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:24 +0530 Subject: [PATCH 206/924] ALSA: vx: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-24-hemanth.selam@gmail.com --- sound/pci/vx222/vx222_ops.c | 2 +- sound/pcmcia/vx/vxp_ops.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/vx222/vx222_ops.c b/sound/pci/vx222/vx222_ops.c index b6459dbdb1b0..40f3716c478f 100644 --- a/sound/pci/vx222/vx222_ops.c +++ b/sound/pci/vx222/vx222_ops.c @@ -431,7 +431,7 @@ static int vx2_load_dsp(struct vx_core *vx, int index, const struct firmware *ds /* * vx_test_and_ack - test and acknowledge interrupt * - * called from irq hander, too + * called from irq handler, too * * spinlock held! */ diff --git a/sound/pcmcia/vx/vxp_ops.c b/sound/pcmcia/vx/vxp_ops.c index 4211e7239138..e52b61125629 100644 --- a/sound/pcmcia/vx/vxp_ops.c +++ b/sound/pcmcia/vx/vxp_ops.c @@ -265,7 +265,7 @@ static int vxp_load_dsp(struct vx_core *vx, int index, const struct firmware *fw /* * vx_test_and_ack - test and acknowledge interrupt * - * called from irq hander, too + * called from irq handler, too * * spinlock held! */ From bd1041d3e1c03e17c2e594a271d2fc90da3a227a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 15 Sep 2026 08:33:18 +0200 Subject: [PATCH 207/924] ASoC: dt-bindings: Use consistent indentation in the example Correct indentation in the examples to consistent 2- or 4-spaces indentation to fix dt-check-style warnings ("example 0 [indent-consistent] indent mismatch ..."). Preferred is 4-spaces, but re-indenting entire example just for that is too much churn. While changing the lines, correct also node name to be generic in cirrus,cs35l45.yaml, cirrus,cs42l42.yaml and dialog,da7219.yaml. Acked-by: Charles Keepax Signed-off-by: Krzysztof Kozlowski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260915063317.28971-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Mark Brown --- .../bindings/sound/adi,adau1372.yaml | 10 +-- .../bindings/sound/adi,adau7118.yaml | 22 ++--- .../bindings/sound/amlogic,gx-sound-card.yaml | 20 ++--- .../bindings/sound/asahi-kasei,ak4619.yaml | 6 +- .../bindings/sound/cirrus,cs35l45.yaml | 44 +++++----- .../bindings/sound/cirrus,cs4270.yaml | 12 +-- .../bindings/sound/cirrus,cs42l42.yaml | 42 +++++----- .../bindings/sound/cirrus,cs42l84.yaml | 18 ++-- .../bindings/sound/cirrus,cs42xx8.yaml | 56 ++++++------- .../bindings/sound/dialog,da7219.yaml | 62 +++++++------- .../bindings/sound/fsl,imx-asrc.yaml | 2 +- .../devicetree/bindings/sound/fsl,sai.yaml | 14 ++-- .../bindings/sound/imx-audio-card.yaml | 4 +- .../bindings/sound/invensense,ics43432.yaml | 8 +- .../sound/loongson,ls-audio-card.yaml | 4 +- .../sound/mediatek,mt8173-afe-pcm.yaml | 22 ++--- .../bindings/sound/mediatek,mt8183-audio.yaml | 82 +++++++++---------- .../sound/mt8186-mt6366-da7219-max98357.yaml | 36 ++++---- .../sound/mt8186-mt6366-rt1019-rt5682s.yaml | 36 ++++---- .../sound/mt8192-mt6359-rt1015-rt5682.yaml | 46 +++++------ .../bindings/sound/mt8195-mt6359.yaml | 50 +++++------ .../bindings/sound/qcom,q6apm-lpass-dais.yaml | 12 +-- .../bindings/sound/renesas,rsnd.yaml | 16 ++-- .../bindings/sound/renesas,rz-ssi.yaml | 32 ++++---- .../bindings/sound/ti,tas5805m.yaml | 10 +-- .../bindings/sound/ti,tlv320dac3100.yaml | 2 +- .../bindings/sound/ti,tpa6130a2.yaml | 2 +- 27 files changed, 335 insertions(+), 335 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/adi,adau1372.yaml b/Documentation/devicetree/bindings/sound/adi,adau1372.yaml index 9a7ff50a0a22..532550de62d7 100644 --- a/Documentation/devicetree/bindings/sound/adi,adau1372.yaml +++ b/Documentation/devicetree/bindings/sound/adi,adau1372.yaml @@ -52,11 +52,11 @@ examples: #address-cells = <1>; #size-cells = <0>; audio-codec@3c { - compatible = "adi,adau1372"; - reg = <0x3c>; - #sound-dai-cells = <0>; - clock-names = "mclk"; - clocks = <&adau1372z_xtal>; + compatible = "adi,adau1372"; + reg = <0x3c>; + #sound-dai-cells = <0>; + clock-names = "mclk"; + clocks = <&adau1372z_xtal>; }; }; diff --git a/Documentation/devicetree/bindings/sound/adi,adau7118.yaml b/Documentation/devicetree/bindings/sound/adi,adau7118.yaml index 11f59c29b575..8272cdaf66a8 100644 --- a/Documentation/devicetree/bindings/sound/adi,adau7118.yaml +++ b/Documentation/devicetree/bindings/sound/adi,adau7118.yaml @@ -68,20 +68,20 @@ examples: #address-cells = <1>; #size-cells = <0>; adau7118_codec: audio-codec@14 { - compatible = "adi,adau7118"; - reg = <0x14>; - #sound-dai-cells = <0>; - iovdd-supply = <&supply>; - dvdd-supply = <&supply>; - adi,pdm-clk-map = <1 1 0 0>; - adi,decimation-ratio = <16>; + compatible = "adi,adau7118"; + reg = <0x14>; + #sound-dai-cells = <0>; + iovdd-supply = <&supply>; + dvdd-supply = <&supply>; + adi,pdm-clk-map = <1 1 0 0>; + adi,decimation-ratio = <16>; }; }; /* example with hw standalone mode */ adau7118_codec_hw: adau7118-codec-hw { - compatible = "adi,adau7118"; - #sound-dai-cells = <0>; - iovdd-supply = <&supply>; - dvdd-supply = <&supply>; + compatible = "adi,adau7118"; + #sound-dai-cells = <0>; + iovdd-supply = <&supply>; + dvdd-supply = <&supply>; }; diff --git a/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml b/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml index 6fdf605ad59c..2be3b34e4f5f 100644 --- a/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml +++ b/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml @@ -94,20 +94,20 @@ examples: audio-routing = "I2S ENCODER I2S IN", "I2S FIFO Playback"; dai-link-0 { - sound-dai = <&i2s_fifo>; + sound-dai = <&i2s_fifo>; }; dai-link-1 { - sound-dai = <&i2s_encoder>; - dai-format = "i2s"; - mclk-fs = <256>; + sound-dai = <&i2s_encoder>; + dai-format = "i2s"; + mclk-fs = <256>; - codec-0 { - sound-dai = <&codec0>; - }; + codec-0 { + sound-dai = <&codec0>; + }; - codec-1 { - sound-dai = <&codec1>; - }; + codec-1 { + sound-dai = <&codec1>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml index d412531ef9a2..2e81947bb8dd 100644 --- a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml +++ b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml @@ -54,9 +54,9 @@ examples: #sound-dai-cells = <0>; port { - ak4619_endpoint: endpoint { - remote-endpoint = <&rsnd_endpoint>; - }; + ak4619_endpoint: endpoint { + remote-endpoint = <&rsnd_endpoint>; + }; }; }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml index fd0646b609f9..1ba809888a88 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml @@ -153,27 +153,27 @@ examples: #address-cells = <1>; #size-cells = <0>; - cs35l45: cs35l45@2 { - #sound-dai-cells = <1>; - compatible = "cirrus,cs35l45"; - reg = <2>; - spi-max-frequency = <5000000>; - vdd-a-supply = <&dummy_vreg>; - vdd-batt-supply = <&dummy_vreg>; - reset-gpios = <&gpio 110 0>; - cirrus,asp-sdout-hiz-ctrl = <(CS35L45_ASP_TX_HIZ_UNUSED | - CS35L45_ASP_TX_HIZ_DISABLED)>; - cirrus,sync-lsw-txid = <0x1>; - cirrus,sync-sw-txid = <0x1>; - cirrus,gpio-ctrl1 { - gpio-ctrl = <0x2>; - }; - cirrus,gpio-ctrl2 { - gpio-ctrl = <0x2>; - }; - cirrus,gpio-ctrl3 { - gpio-ctrl = <0x1>; - gpio-dir = <0x1>; - }; + cs35l45: audio-codec@2 { + #sound-dai-cells = <1>; + compatible = "cirrus,cs35l45"; + reg = <2>; + spi-max-frequency = <5000000>; + vdd-a-supply = <&dummy_vreg>; + vdd-batt-supply = <&dummy_vreg>; + reset-gpios = <&gpio 110 0>; + cirrus,asp-sdout-hiz-ctrl = <(CS35L45_ASP_TX_HIZ_UNUSED | + CS35L45_ASP_TX_HIZ_DISABLED)>; + cirrus,sync-lsw-txid = <0x1>; + cirrus,sync-sw-txid = <0x1>; + cirrus,gpio-ctrl1 { + gpio-ctrl = <0x2>; + }; + cirrus,gpio-ctrl2 { + gpio-ctrl = <0x2>; + }; + cirrus,gpio-ctrl3 { + gpio-ctrl = <0x1>; + gpio-dir = <0x1>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs4270.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs4270.yaml index 336e11773694..61106294a1d5 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs4270.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs4270.yaml @@ -49,11 +49,11 @@ unevaluatedProperties: false examples: - | i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - codec@48 { - compatible = "cirrus,cs4270"; - reg = <0x48>; - }; + codec@48 { + compatible = "cirrus,cs4270"; + reg = <0x48>; + }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs42l42.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs42l42.yaml index af599d8735e2..ab4c7d22dc0e 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs42l42.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs42l42.yaml @@ -199,28 +199,28 @@ examples: - | #include i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - cs42l42: cs42l42@48 { - compatible = "cirrus,cs42l42"; - reg = <0x48>; - VA-supply = <&dummy_vreg>; - VP-supply = <&dummy_vreg>; - VCP-supply = <&dummy_vreg>; - VD_FILT-supply = <&dummy_vreg>; - VL-supply = <&dummy_vreg>; + audio-codec@48 { + compatible = "cirrus,cs42l42"; + reg = <0x48>; + VA-supply = <&dummy_vreg>; + VP-supply = <&dummy_vreg>; + VCP-supply = <&dummy_vreg>; + VD_FILT-supply = <&dummy_vreg>; + VL-supply = <&dummy_vreg>; - reset-gpios = <&axi_gpio_0 1 0>; - interrupt-parent = <&gpio0>; - interrupts = <55 8>; + reset-gpios = <&axi_gpio_0 1 0>; + interrupt-parent = <&gpio0>; + interrupts = <55 8>; - cirrus,ts-inv = ; - cirrus,ts-dbnc-rise = ; - cirrus,ts-dbnc-fall = ; - cirrus,btn-det-init-dbnce = <100>; - cirrus,btn-det-event-dbnce = <10>; - cirrus,bias-lvls = <0x0F 0x08 0x04 0x01>; - cirrus,hs-bias-ramp-rate = ; - }; + cirrus,ts-inv = ; + cirrus,ts-dbnc-rise = ; + cirrus,ts-dbnc-fall = ; + cirrus,btn-det-init-dbnce = <100>; + cirrus,btn-det-event-dbnce = <10>; + cirrus,bias-lvls = <0x0F 0x08 0x04 0x01>; + cirrus,hs-bias-ramp-rate = ; + }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml index 7f8338e8ae36..94062c1d9d45 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml @@ -43,14 +43,14 @@ examples: #include #include i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - jack_codec: codec@4b { - compatible = "cirrus,cs42l84"; - reg = <0x4b>; - reset-gpios = <&pinctrl_nub 4 GPIO_ACTIVE_LOW>; - interrupts-extended = <&pinctrl_ap 180 IRQ_TYPE_LEVEL_LOW>; - #sound-dai-cells = <0>; - }; + jack_codec: codec@4b { + compatible = "cirrus,cs42l84"; + reg = <0x4b>; + reset-gpios = <&pinctrl_nub 4 GPIO_ACTIVE_LOW>; + interrupts-extended = <&pinctrl_ap 180 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs42xx8.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs42xx8.yaml index a1ae548c4b7b..b5cdc7e537f8 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs42xx8.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs42xx8.yaml @@ -75,37 +75,37 @@ unevaluatedProperties: false examples: - | i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - codec@48 { - compatible = "cirrus,cs42888"; - reg = <0x48>; - clocks = <&codec_mclk 0>; - clock-names = "mclk"; - VA-supply = <®_audio>; - VD-supply = <®_audio>; - VLS-supply = <®_audio>; - VLC-supply = <®_audio>; - reset-gpios = <&gpio 1>; - }; + codec@48 { + compatible = "cirrus,cs42888"; + reg = <0x48>; + clocks = <&codec_mclk 0>; + clock-names = "mclk"; + VA-supply = <®_audio>; + VD-supply = <®_audio>; + VLS-supply = <®_audio>; + VLC-supply = <®_audio>; + reset-gpios = <&gpio 1>; + }; }; spi { - #address-cells = <1>; - #size-cells = <0>; - cs-gpios = <&gpio 8 0>; + #address-cells = <1>; + #size-cells = <0>; + cs-gpios = <&gpio 8 0>; - codec@0 { - compatible = "cirrus,cs42888"; - reg = <0>; - spi-max-frequency = <6000000>; - clocks = <&codec_mclk 0>; - clock-names = "mclk"; - VA-supply = <®_audio>; - VD-supply = <®_audio>; - VLS-supply = <®_audio>; - VLC-supply = <®_audio>; - reset-gpios = <&gpio 1>; - }; + codec@0 { + compatible = "cirrus,cs42888"; + reg = <0>; + spi-max-frequency = <6000000>; + clocks = <&codec_mclk 0>; + clock-names = "mclk"; + VA-supply = <®_audio>; + VD-supply = <®_audio>; + VLS-supply = <®_audio>; + VLC-supply = <®_audio>; + reset-gpios = <&gpio 1>; + }; }; diff --git a/Documentation/devicetree/bindings/sound/dialog,da7219.yaml b/Documentation/devicetree/bindings/sound/dialog,da7219.yaml index 19137abdba3e..852f27e9dffe 100644 --- a/Documentation/devicetree/bindings/sound/dialog,da7219.yaml +++ b/Documentation/devicetree/bindings/sound/dialog,da7219.yaml @@ -195,44 +195,44 @@ examples: - | #include i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - codec: da7219@1a { - compatible = "dlg,da7219"; - reg = <0x1a>; + codec: audio-codec@1a { + compatible = "dlg,da7219"; + reg = <0x1a>; - interrupt-parent = <&gpio6>; - interrupts = <11 IRQ_TYPE_LEVEL_LOW>; + interrupt-parent = <&gpio6>; + interrupts = <11 IRQ_TYPE_LEVEL_LOW>; - VDD-supply = <&vdd_reg>; - VDDMIC-supply = <&vddmic_reg>; - VDDIO-supply = <&vddio_reg>; + VDD-supply = <&vdd_reg>; + VDDMIC-supply = <&vddmic_reg>; + VDDIO-supply = <&vddio_reg>; - #clock-cells = <1>; - clock-output-names = "da7219-dai-wclk", "da7219-dai-bclk"; + #clock-cells = <1>; + clock-output-names = "da7219-dai-wclk", "da7219-dai-bclk"; - clocks = <&clks 201>; - clock-names = "mclk"; + clocks = <&clks 201>; + clock-names = "mclk"; - dlg,micbias-lvl = <2600>; - dlg,mic-amp-in-sel = "diff"; + dlg,micbias-lvl = <2600>; + dlg,mic-amp-in-sel = "diff"; - da7219_aad { - dlg,btn-cfg = <50>; - dlg,mic-det-thr = <500>; - dlg,jack-ins-deb = <20>; - dlg,jack-ins-det-pty = "low"; - dlg,jack-det-rate = "32_64"; - dlg,jack-rem-deb = <1>; + da7219_aad { + dlg,btn-cfg = <50>; + dlg,mic-det-thr = <500>; + dlg,jack-ins-deb = <20>; + dlg,jack-ins-det-pty = "low"; + dlg,jack-det-rate = "32_64"; + dlg,jack-rem-deb = <1>; - dlg,a-d-btn-thr = <0xa>; - dlg,d-b-btn-thr = <0x16>; - dlg,b-c-btn-thr = <0x21>; - dlg,c-mic-btn-thr = <0x3E>; + dlg,a-d-btn-thr = <0xa>; + dlg,d-b-btn-thr = <0x16>; + dlg,b-c-btn-thr = <0x21>; + dlg,c-mic-btn-thr = <0x3E>; - dlg,btn-avg = <4>; - dlg,adc-1bit-rpt = <1>; - }; - }; + dlg,btn-avg = <4>; + dlg,adc-1bit-rpt = <1>; + }; + }; }; diff --git a/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml b/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml index 608defc93c1e..cd69bad107c7 100644 --- a/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml @@ -186,5 +186,5 @@ examples: asrc_endpoint: endpoint { remote-endpoint = <&fe00_ep>; }; - }; + }; }; diff --git a/Documentation/devicetree/bindings/sound/fsl,sai.yaml b/Documentation/devicetree/bindings/sound/fsl,sai.yaml index ba65b3f3d066..eb0b9d8d907f 100644 --- a/Documentation/devicetree/bindings/sound/fsl,sai.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,sai.yaml @@ -260,13 +260,13 @@ examples: playback-only; sai1_endpoint0: endpoint { - dai-tdm-slot-num = <8>; - dai-tdm-slot-width = <32>; - dai-tdm-slot-width-map = <32 8 32>; - dai-format = "dsp_a"; - bitclock-master; - frame-master; - remote-endpoint = <&mcodec01_ep>; + dai-tdm-slot-num = <8>; + dai-tdm-slot-width = <32>; + dai-tdm-slot-width-map = <32 8 32>; + dai-format = "dsp_a"; + bitclock-master; + frame-master; + remote-endpoint = <&mcodec01_ep>; }; }; diff --git a/Documentation/devicetree/bindings/sound/imx-audio-card.yaml b/Documentation/devicetree/bindings/sound/imx-audio-card.yaml index 950e3eab2942..27636466ba63 100644 --- a/Documentation/devicetree/bindings/sound/imx-audio-card.yaml +++ b/Documentation/devicetree/bindings/sound/imx-audio-card.yaml @@ -105,10 +105,10 @@ examples: format = "i2s"; fsl,mclk-equal-bclk; cpu { - sound-dai = <&sai1>; + sound-dai = <&sai1>; }; codec { - sound-dai = <&ak4458_1>, <&ak4458_2>; + sound-dai = <&ak4458_1>, <&ak4458_2>; }; }; fe-dai-link { diff --git a/Documentation/devicetree/bindings/sound/invensense,ics43432.yaml b/Documentation/devicetree/bindings/sound/invensense,ics43432.yaml index 7bd984817aa9..054f80390b8d 100644 --- a/Documentation/devicetree/bindings/sound/invensense,ics43432.yaml +++ b/Documentation/devicetree/bindings/sound/invensense,ics43432.yaml @@ -42,10 +42,10 @@ examples: compatible = "invensense,ics43432"; port { - endpoint { - remote-endpoint = <&i2s1_endpoint>; - dai-format = "i2s"; - }; + endpoint { + remote-endpoint = <&i2s1_endpoint>; + dai-format = "i2s"; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml index dc7f4afbb777..69ea611d4250 100644 --- a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml +++ b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml @@ -79,7 +79,7 @@ examples: sound-dai = <&i2s>; }; codec { - sound-dai = <&es8323>; + sound-dai = <&es8323>; }; }; @@ -104,6 +104,6 @@ examples: }; codec { - sound-dai = <&es8388>; + sound-dai = <&es8388>; }; }; diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt8173-afe-pcm.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt8173-afe-pcm.yaml index d8993b5d457a..4c66160e9e1b 100644 --- a/Documentation/devicetree/bindings/sound/mediatek,mt8173-afe-pcm.yaml +++ b/Documentation/devicetree/bindings/sound/mediatek,mt8173-afe-pcm.yaml @@ -84,15 +84,15 @@ examples: <&topckgen CLK_TOP_I2S2_M_SEL>, <&topckgen CLK_TOP_I2S3_M_SEL>, <&topckgen CLK_TOP_I2S3_B_SEL>; - clock-names = "infra_sys_audio_clk", - "top_pdn_audio", - "top_pdn_aud_intbus", - "bck0", - "bck1", - "i2s0_m", - "i2s1_m", - "i2s2_m", - "i2s3_m", - "i2s3_b"; - memory-region = <&afe_dma_mem>; + clock-names = "infra_sys_audio_clk", + "top_pdn_audio", + "top_pdn_aud_intbus", + "bck0", + "bck1", + "i2s0_m", + "i2s1_m", + "i2s2_m", + "i2s3_m", + "i2s3_b"; + memory-region = <&afe_dma_mem>; }; diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt8183-audio.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt8183-audio.yaml index 031b0fa7b4dc..261247ca2c87 100644 --- a/Documentation/devicetree/bindings/sound/mediatek,mt8183-audio.yaml +++ b/Documentation/devicetree/bindings/sound/mediatek,mt8183-audio.yaml @@ -182,47 +182,47 @@ examples: <&topckgen CLK_TOP_APLL12_DIV4>, <&topckgen CLK_TOP_APLL12_DIVB>, <&clk26m>; - clock-names = "aud_afe_clk", - "aud_dac_clk", - "aud_dac_predis_clk", - "aud_adc_clk", - "aud_adc_adda6_clk", - "aud_apll22m_clk", - "aud_apll24m_clk", - "aud_apll1_tuner_clk", - "aud_apll2_tuner_clk", - "aud_i2s1_bclk_sw", - "aud_i2s2_bclk_sw", - "aud_i2s3_bclk_sw", - "aud_i2s4_bclk_sw", - "aud_tdm_clk", - "aud_tml_clk", - "aud_infra_clk", - "mtkaif_26m_clk", - "top_mux_audio", - "top_mux_aud_intbus", - "top_syspll_d2_d4", - "top_mux_aud_1", - "top_apll1_ck", - "top_mux_aud_2", - "top_apll2_ck", - "top_mux_aud_eng1", - "top_apll1_d8", - "top_mux_aud_eng2", - "top_apll2_d8", - "top_i2s0_m_sel", - "top_i2s1_m_sel", - "top_i2s2_m_sel", - "top_i2s3_m_sel", - "top_i2s4_m_sel", - "top_i2s5_m_sel", - "top_apll12_div0", - "top_apll12_div1", - "top_apll12_div2", - "top_apll12_div3", - "top_apll12_div4", - "top_apll12_divb", - "top_clk26m_clk"; + clock-names = "aud_afe_clk", + "aud_dac_clk", + "aud_dac_predis_clk", + "aud_adc_clk", + "aud_adc_adda6_clk", + "aud_apll22m_clk", + "aud_apll24m_clk", + "aud_apll1_tuner_clk", + "aud_apll2_tuner_clk", + "aud_i2s1_bclk_sw", + "aud_i2s2_bclk_sw", + "aud_i2s3_bclk_sw", + "aud_i2s4_bclk_sw", + "aud_tdm_clk", + "aud_tml_clk", + "aud_infra_clk", + "mtkaif_26m_clk", + "top_mux_audio", + "top_mux_aud_intbus", + "top_syspll_d2_d4", + "top_mux_aud_1", + "top_apll1_ck", + "top_mux_aud_2", + "top_apll2_ck", + "top_mux_aud_eng1", + "top_apll1_d8", + "top_mux_aud_eng2", + "top_apll2_d8", + "top_i2s0_m_sel", + "top_i2s1_m_sel", + "top_i2s2_m_sel", + "top_i2s3_m_sel", + "top_i2s4_m_sel", + "top_i2s5_m_sel", + "top_apll12_div0", + "top_apll12_div1", + "top_apll12_div2", + "top_apll12_div3", + "top_apll12_div4", + "top_apll12_divb", + "top_clk26m_clk"; }; ... diff --git a/Documentation/devicetree/bindings/sound/mt8186-mt6366-da7219-max98357.yaml b/Documentation/devicetree/bindings/sound/mt8186-mt6366-da7219-max98357.yaml index 037f21443ad1..d5b358838e2a 100644 --- a/Documentation/devicetree/bindings/sound/mt8186-mt6366-da7219-max98357.yaml +++ b/Documentation/devicetree/bindings/sound/mt8186-mt6366-da7219-max98357.yaml @@ -167,30 +167,30 @@ examples: "HDMI1", "TX"; hs-playback-dai-link { - link-name = "I2S0"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&da7219>; - }; + link-name = "I2S0"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&da7219>; + }; }; hs-capture-dai-link { - link-name = "I2S1"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&da7219>; - }; + link-name = "I2S1"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&da7219>; + }; }; spk-dp-playback-dai-link { - link-name = "I2S3"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&anx_bridge_dp>, <&max98357a>; - }; + link-name = "I2S3"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&anx_bridge_dp>, <&max98357a>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/mt8186-mt6366-rt1019-rt5682s.yaml b/Documentation/devicetree/bindings/sound/mt8186-mt6366-rt1019-rt5682s.yaml index ed93f18ef985..7f553575c73d 100644 --- a/Documentation/devicetree/bindings/sound/mt8186-mt6366-rt1019-rt5682s.yaml +++ b/Documentation/devicetree/bindings/sound/mt8186-mt6366-rt1019-rt5682s.yaml @@ -171,30 +171,30 @@ examples: "HDMI1", "TX"; hs-playback-dai-link { - link-name = "I2S0"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt5682s 0>; - }; + link-name = "I2S0"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt5682s 0>; + }; }; hs-capture-dai-link { - link-name = "I2S1"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt5682s 0>; - }; + link-name = "I2S1"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt5682s 0>; + }; }; spk-hdmi-playback-dai-link { - link-name = "I2S3"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&it6505dptx>, <&rt1019p>; - }; + link-name = "I2S3"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&it6505dptx>, <&rt1019p>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/mt8192-mt6359-rt1015-rt5682.yaml b/Documentation/devicetree/bindings/sound/mt8192-mt6359-rt1015-rt5682.yaml index c4e68f31aaab..841aab0178e3 100644 --- a/Documentation/devicetree/bindings/sound/mt8192-mt6359-rt1015-rt5682.yaml +++ b/Documentation/devicetree/bindings/sound/mt8192-mt6359-rt1015-rt5682.yaml @@ -165,38 +165,38 @@ examples: "Speakers", "Speaker"; spk-playback-dai-link { - link-name = "I2S3"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt1015p>; - }; + link-name = "I2S3"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt1015p>; + }; }; hs-playback-dai-link { - link-name = "I2S8"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt5682 0>; - }; + link-name = "I2S8"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt5682 0>; + }; }; hs-capture-dai-link { - link-name = "I2S9"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt5682 0>; - }; + link-name = "I2S9"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt5682 0>; + }; }; displayport-dai-link { - link-name = "TDM"; - dai-format = "dsp_a"; - codec { - sound-dai = <&anx_bridge_dp>; - }; + link-name = "TDM"; + dai-format = "dsp_a"; + codec { + sound-dai = <&anx_bridge_dp>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/mt8195-mt6359.yaml b/Documentation/devicetree/bindings/sound/mt8195-mt6359.yaml index 356e1feee962..c6bd286780c9 100644 --- a/Documentation/devicetree/bindings/sound/mt8195-mt6359.yaml +++ b/Documentation/devicetree/bindings/sound/mt8195-mt6359.yaml @@ -156,46 +156,46 @@ examples: "Ext Spk", "Speaker"; mm-dai-link { - link-name = "ETDM1_IN_BE"; - mediatek,clk-provider = "cpu"; + link-name = "ETDM1_IN_BE"; + mediatek,clk-provider = "cpu"; }; hs-playback-dai-link { - link-name = "ETDM1_OUT_BE"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&headset_codec>; - }; + link-name = "ETDM1_OUT_BE"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&headset_codec>; + }; }; hs-capture-dai-link { - link-name = "ETDM2_IN_BE"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&headset_codec>; - }; + link-name = "ETDM2_IN_BE"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&headset_codec>; + }; }; spk-playback-dai-link { - link-name = "ETDM2_OUT_BE"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&spk_amplifier>; - }; + link-name = "ETDM2_OUT_BE"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&spk_amplifier>; + }; }; hdmi-dai-link { - link-name = "ETDM3_OUT_BE"; - codec { - sound-dai = <&hdmi_tx>; - }; + link-name = "ETDM3_OUT_BE"; + codec { + sound-dai = <&hdmi_tx>; + }; }; displayport-dai-link { - link-name = "DPTX_BE"; - codec { - sound-dai = <&dp_tx>; - }; + link-name = "DPTX_BE"; + codec { + sound-dai = <&dp_tx>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml b/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml index 4878c424ef03..78587139f155 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml @@ -75,11 +75,11 @@ examples: #size-cells = <0>; dai@10 { - reg = ; - clocks = <&q6prmcc LPASS_CLK_ID_PRI_MI2S_IBIT - LPASS_CLK_ATTRIBUTE_COUPLE_NO>, - <&q6prmcc LPASS_CLK_ID_MCLK_1 - LPASS_CLK_ATTRIBUTE_COUPLE_NO>; - clock-names = "bclk", "mclk"; + reg = ; + clocks = <&q6prmcc LPASS_CLK_ID_PRI_MI2S_IBIT + LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_CLK_ID_MCLK_1 + LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "bclk", "mclk"; }; }; diff --git a/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml b/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml index e8a2acb92646..d3516fb1b130 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml +++ b/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml @@ -432,14 +432,14 @@ examples: "ssi.1", "ssi.0"; rcar_sound,dvc { - dvc0: dvc-0 { - dmas = <&audma0 0xbc>; - dma-names = "tx"; - }; - dvc1: dvc-1 { - dmas = <&audma0 0xbe>; - dma-names = "tx"; - }; + dvc0: dvc-0 { + dmas = <&audma0 0xbc>; + dma-names = "tx"; + }; + dvc1: dvc-1 { + dmas = <&audma0 0xbe>; + dma-names = "tx"; + }; }; rcar_sound,mix { diff --git a/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml b/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml index 1394f78281fc..d696fb33d9b8 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml +++ b/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml @@ -95,20 +95,20 @@ examples: ssi0: ssi@10049c00 { compatible = "renesas,r9a07g044-ssi", "renesas,rz-ssi"; - reg = <0x10049c00 0x400>; - interrupts = , - , - ; - interrupt-names = "int_req", "dma_rx", "dma_tx"; - clocks = <&cpg CPG_MOD R9A07G044_SSI0_PCLK2>, - <&cpg CPG_MOD R9A07G044_SSI0_PCLK_SFR>, - <&audio_clk1>, - <&audio_clk2>; - clock-names = "ssi", "ssi_sfr", "audio_clk1", "audio_clk2"; - power-domains = <&cpg>; - resets = <&cpg R9A07G044_SSI0_RST_M2_REG>; - dmas = <&dmac 0x2655>, - <&dmac 0x2656>; - dma-names = "tx", "rx"; - #sound-dai-cells = <0>; + reg = <0x10049c00 0x400>; + interrupts = , + , + ; + interrupt-names = "int_req", "dma_rx", "dma_tx"; + clocks = <&cpg CPG_MOD R9A07G044_SSI0_PCLK2>, + <&cpg CPG_MOD R9A07G044_SSI0_PCLK_SFR>, + <&audio_clk1>, + <&audio_clk2>; + clock-names = "ssi", "ssi_sfr", "audio_clk1", "audio_clk2"; + power-domains = <&cpg>; + resets = <&cpg R9A07G044_SSI0_RST_M2_REG>; + dmas = <&dmac 0x2655>, + <&dmac 0x2656>; + dma-names = "tx", "rx"; + #sound-dai-cells = <0>; }; diff --git a/Documentation/devicetree/bindings/sound/ti,tas5805m.yaml b/Documentation/devicetree/bindings/sound/ti,tas5805m.yaml index c2c2835a9e1d..f2ab274b02cd 100644 --- a/Documentation/devicetree/bindings/sound/ti,tas5805m.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tas5805m.yaml @@ -45,13 +45,13 @@ examples: #address-cells = <1>; #size-cells = <0>; tas5805m: tas5805m@2c { - reg = <0x2c>; - compatible = "ti,tas5805m"; + reg = <0x2c>; + compatible = "ti,tas5805m"; - pvdd-supply = <&audiopwr>; - pdn-gpios = <&tlmm 160 0>; + pvdd-supply = <&audiopwr>; + pdn-gpios = <&tlmm 160 0>; - ti,dsp-config-name = "mono_pbtl_48khz"; + ti,dsp-config-name = "mono_pbtl_48khz"; }; }; ... diff --git a/Documentation/devicetree/bindings/sound/ti,tlv320dac3100.yaml b/Documentation/devicetree/bindings/sound/ti,tlv320dac3100.yaml index 10299064cbc6..3bdaab1e5084 100644 --- a/Documentation/devicetree/bindings/sound/ti,tlv320dac3100.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tlv320dac3100.yaml @@ -121,6 +121,6 @@ examples: AVDD-supply = <®ulator>; IOVDD-supply = <®ulator>; DVDD-supply = <®ulator>; - }; + }; }; diff --git a/Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml b/Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml index a42bf9bde694..5634b601ce65 100644 --- a/Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml @@ -50,6 +50,6 @@ examples: reg = <0x60>; Vdd-supply = <&vmmc2>; power-gpio = <&gpio4 2 GPIO_ACTIVE_HIGH>; - }; + }; }; From f4c49ebff706f6bf2a0ffa8e06a8f94b695cbde9 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 15 Sep 2026 11:31:21 +0100 Subject: [PATCH 208/924] ASoC: cs35l56: Add KUnit tests for regmap defaults table Add test cases to cs35l56-shared-test to sanity-check the regmap defaults table: - Table is sorted in order of increasing address (which also means there cannot be duplicate entries). - Volatile registers don't have a default. - Defaulted registers are readable. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260915103121.3306373-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56-shared-test.c | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/sound/soc/codecs/cs35l56-shared-test.c b/sound/soc/codecs/cs35l56-shared-test.c index 4f52c8a192e5..34bb3d44b51f 100644 --- a/sound/soc/codecs/cs35l56-shared-test.c +++ b/sound/soc/codecs/cs35l56-shared-test.c @@ -29,6 +29,7 @@ struct cs35l56_shared_test_priv { struct faux_device *gpio_dev; struct cs35l56_shared_test_mock_gpio *gpio_priv; struct regmap *registers; + const struct regmap_config *regmap_config; unsigned int reg_offset; struct cs35l56_base *cs35l56_base; u8 applied_pad_pull_state[CS35L56_MAX_GPIO]; @@ -614,6 +615,55 @@ static void cs35l56_shared_test_get_speaker_id_from_host_gpio(struct kunit *test KUNIT_EXPECT_EQ(test, cs35l56_get_speaker_id(priv->cs35l56_base), param->spkid); } +static void cs35l56_shared_test_sorted_regmap_defaults(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + const struct reg_default *defaults; + int i; + + KUNIT_ASSERT_NOT_NULL(test, priv->regmap_config); + defaults = priv->regmap_config->reg_defaults; + KUNIT_ASSERT_NOT_NULL(test, defaults); + KUNIT_ASSERT_NE(test, priv->regmap_config->num_reg_defaults, 0); + + for (i = 1; i < priv->regmap_config->num_reg_defaults; i++) + KUNIT_EXPECT_LT(test, defaults[i - 1].reg, defaults[i].reg); +} + +static void cs35l56_shared_test_regmap_defaults_not_volatile(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + const struct regmap_config *config = priv->regmap_config; + int i; + + KUNIT_ASSERT_NOT_NULL(test, config); + KUNIT_ASSERT_NOT_NULL(test, config->volatile_reg); + + for (i = 0; i < config->num_reg_defaults; i++) { + KUNIT_EXPECT_FALSE(test, + config->volatile_reg(cs35l56_base->dev, + config->reg_defaults[i].reg)); + } +} + +static void cs35l56_shared_test_regmap_defaults_readable(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + const struct regmap_config *config = priv->regmap_config; + int i; + + KUNIT_ASSERT_NOT_NULL(test, config); + KUNIT_ASSERT_NOT_NULL(test, config->readable_reg); + + for (i = 0; i < config->num_reg_defaults; i++) { + KUNIT_EXPECT_TRUE(test, + config->readable_reg(cs35l56_base->dev, + config->reg_defaults[i].reg)); + } +} + static int cs35l56_shared_test_case_regmap_init(struct kunit *test, const struct regmap_config *regmap_config) { @@ -645,6 +695,8 @@ static int cs35l56_shared_test_case_regmap_init(struct kunit *test, kunit_add_action_or_reset(test, regmap_exit_wrapper, cs35l56_base->regmap)); + priv->regmap_config = regmap_config; + return 0; } @@ -862,6 +914,10 @@ static struct kunit_case cs35l56_shared_test_cases[] = { cs35l56_shared_test_host_gpio_spkid_gen_params, { KUNIT_SPEED_SLOW }), + KUNIT_CASE(cs35l56_shared_test_sorted_regmap_defaults), + KUNIT_CASE(cs35l56_shared_test_regmap_defaults_not_volatile), + KUNIT_CASE(cs35l56_shared_test_regmap_defaults_readable), + { } }; From 99c3d59542935bc838ee1567da473388e81ea060 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Tue, 15 Sep 2026 12:10:37 +0200 Subject: [PATCH 209/924] ASoC: Intel: avs: Remove topology-loading wrappers With recent changes avs_load_topology() became unused whereas avs_remove_topology() is a simple wrapper. Drop them both. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260915101037.3087956-1-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/pcm.c | 4 ++-- sound/soc/intel/avs/topology.c | 25 ------------------------- sound/soc/intel/avs/topology.h | 3 --- 3 files changed, 2 insertions(+), 30 deletions(-) diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c index 8174d2ebfa99..2d7ced953fde 100644 --- a/sound/soc/intel/avs/pcm.c +++ b/sound/soc/intel/avs/pcm.c @@ -1065,7 +1065,7 @@ static int avs_component_probe(struct snd_soc_component *component) return 0; err_load_libs: - avs_remove_topology(component); + snd_soc_tplg_component_remove(component); return ret; } @@ -1083,7 +1083,7 @@ static void avs_component_remove(struct snd_soc_component *component) mutex_unlock(&adev->comp_list_mutex); if (mach->tplg_filename) { - ret = avs_remove_topology(component); + ret = snd_soc_tplg_component_remove(component); if (ret < 0) dev_err(component->dev, "unload topology failed: %d\n", ret); } diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c index 5d70be63a4a7..1085090d5a9c 100644 --- a/sound/soc/intel/avs/topology.c +++ b/sound/soc/intel/avs/topology.c @@ -2226,28 +2226,3 @@ struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp) return tplg; } - -int avs_load_topology(struct snd_soc_component *comp, const char *filename) -{ - const struct firmware *fw __free(firmware) = NULL; - int ret; - - ret = request_firmware(&fw, filename, comp->dev); - if (ret < 0) { - dev_err(comp->dev, "request topology \"%s\" failed: %d\n", filename, ret); - return ret; - } - - ret = snd_soc_tplg_component_load(comp, &avs_tplg_ops, fw); - if (ret < 0) - dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret); - - return ret; -} - -int avs_remove_topology(struct snd_soc_component *comp) -{ - snd_soc_tplg_component_remove(comp); - - return 0; -} diff --git a/sound/soc/intel/avs/topology.h b/sound/soc/intel/avs/topology.h index 189984ce7b51..25f3888a1939 100644 --- a/sound/soc/intel/avs/topology.h +++ b/sound/soc/intel/avs/topology.h @@ -233,7 +233,4 @@ struct avs_tplg_module { extern const struct snd_soc_tplg_ops avs_tplg_ops; struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp); -int avs_load_topology(struct snd_soc_component *comp, const char *filename); -int avs_remove_topology(struct snd_soc_component *comp); - #endif From 247b84c07b7ea4940f69995cc4fc4f20d8c0c615 Mon Sep 17 00:00:00 2001 From: Oder Chiou Date: Fri, 11 Sep 2026 16:45:46 +0800 Subject: [PATCH 210/924] ASoC: rt721: Use the function_status check to avoid duplicate presets This patch adds the function_status check to avoid duplicate presets. The codec driver reinitializes when the 'FUNCTION_NEEDS_INITIALIZATION' flag is raised. Signed-off-by: Oder Chiou Link: https://patch.msgid.link/20260911084546.1342894-1-oder_chiou@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca-sdw.c | 12 ++ sound/soc/codecs/rt721-sdca.c | 313 +++++++++++++++++------------- sound/soc/codecs/rt721-sdca.h | 5 + 3 files changed, 194 insertions(+), 136 deletions(-) diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c index eae7d662efae..d0f3fc1978c3 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.c +++ b/sound/soc/codecs/rt721-sdca-sdw.c @@ -25,16 +25,22 @@ static bool rt721_sdca_readable_register(struct device *dev, unsigned int reg) case 0x2f50: case 0x2f51: case 0x2f58 ... 0x2f5d: + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_XUV, RT721_SDCA_CTL_XUV, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_GE49, RT721_SDCA_CTL_SELECTED_MODE, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_GE49, RT721_SDCA_CTL_DETECTED_MODE, 0): + case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): case SDW_SDCA_CTL(FUNC_NUM_HID, RT721_SDCA_ENT_HID01, RT721_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... SDW_SDCA_CTL(FUNC_NUM_HID, RT721_SDCA_ENT_HID01, RT721_SDCA_CTL_HIDTX_MESSAGE_LENGTH, 0): case RT721_BUF_ADDR_HID1 ... RT721_BUF_ADDR_HID2: + case SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): return true; default: return false; @@ -46,14 +52,20 @@ static bool rt721_sdca_volatile_register(struct device *dev, unsigned int reg) switch (reg) { case 0x2f01: case 0x2f51: + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_GE49, RT721_SDCA_CTL_DETECTED_MODE, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_XUV, RT721_SDCA_CTL_XUV, 0): + case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): case SDW_SDCA_CTL(FUNC_NUM_HID, RT721_SDCA_ENT_HID01, RT721_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... SDW_SDCA_CTL(FUNC_NUM_HID, RT721_SDCA_ENT_HID01, RT721_SDCA_CTL_HIDTX_MESSAGE_LENGTH, 0): case RT721_BUF_ADDR_HID1 ... RT721_BUF_ADDR_HID2: + case SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): return true; default: return false; diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index a9479d0e4941..0a936938385c 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -142,151 +142,192 @@ static void rt721_sdca_btn_check_handler(struct work_struct *work) static void rt721_sdca_dmic_preset(struct rt721_sdca_priv *rt721) { - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_VREF1_HV_CTRL1, 0xe000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8007); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL9, 0x2a2a); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL10, 0x2a00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL6, 0x2a2a); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL5, 0x2626); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL8, 0x1e00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL7, 0x1515); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_CH_FLOAT_CTL3, 0x0304); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_CH_FLOAT_CTL4, 0x0304); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_HDA_LEGACY_CTL1, 0x0000); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT_IT26, - RT721_SDCA_CTL_VENDOR_DEF, 0), 0x01); - regmap_write(rt721->mbq_regmap, 0x5910009, 0x2e01); - rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, - RT721_RC_CALIB_CTRL0, 0x0b00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, - RT721_RC_CALIB_CTRL0, 0x0b40); - regmap_write(rt721->regmap, 0x2f5c, 0x25); + unsigned int mic_func_status; + struct device *dev = &rt721->slave->dev; + + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + &mic_func_status); + dev_dbg(dev, "%s mic func_status=0x%x\n", __func__, mic_func_status); + + if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt721->first_hw_init)) { + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF1_HV_CTRL1, 0xe000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8007); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL9, 0x2a2a); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL10, 0x2a00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL6, 0x2a2a); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL5, 0x2626); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL8, 0x1e00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL7, 0x1515); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL3, 0x0304); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL4, 0x0304); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_CTL1, 0x0000); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT_IT26, + RT721_SDCA_CTL_VENDOR_DEF, 0), 0x01); + regmap_write(rt721->mbq_regmap, 0x5910009, 0x2e01); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b40); + regmap_write(rt721->regmap, 0x2f5c, 0x25); + /* clear flag */ + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0), FUNCTION_NEEDS_INITIALIZATION); + } } static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) { - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_VREF1_HV_CTRL1, 0xe000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8007); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6420); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6421); - regmap_write(rt721->mbq_regmap, 0x5810000, 0xe421); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_CH_FLOAT_CTL6, 0x5561); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_REG, - RT721_GPIO_PAD_CTRL5, 0x8003); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_OT23, - RT721_SDCA_CTL_VENDOR_DEF, 0), 0x04); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_PDE23, - RT721_SDCA_CTL_FU_MUTE, CH_01), 0x00); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_PDE23, - RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, - RT721_SDCA_CTL_FU_MUTE, CH_01), 0x00); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, - RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); - regmap_write(rt721->regmap, 0x2f5d, 0x1); + unsigned int amp_func_status; + struct device *dev = &rt721->slave->dev; + + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + &_func_status); + dev_dbg(dev, "%s amp func_status=0x%x\n", __func__, amp_func_status); + + if ((amp_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt721->first_hw_init)) { + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF1_HV_CTRL1, 0xe000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8007); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x6420); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x6421); + regmap_write(rt721->mbq_regmap, 0x5810000, 0xe421); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL6, 0x5561); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_REG, + RT721_GPIO_PAD_CTRL5, 0x8003); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_OT23, + RT721_SDCA_CTL_VENDOR_DEF, 0), 0x04); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_PDE23, + RT721_SDCA_CTL_FU_MUTE, CH_01), 0x00); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_PDE23, + RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, + RT721_SDCA_CTL_FU_MUTE, CH_01), 0x00); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, + RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); + regmap_write(rt721->regmap, 0x2f5d, 0x1); + /* clear flag */ + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + FUNCTION_NEEDS_INITIALIZATION); + } } static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) { - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_VREF1_HV_CTRL1, 0xe000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8007); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_GE_REL_CTRL1, 0x8011); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_UMP_HID_CTRL3, 0xcf00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_UMP_HID_CTRL4, 0x000f); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_UMP_HID_CTRL1, 0x1100); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_UMP_HID_CTRL5, 0x0c12); - rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, - RT721_JD_1PIN_GAT_CTRL2, 0xc002); - rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, - RT721_RC_CALIB_CTRL0, 0x0b00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, - RT721_RC_CALIB_CTRL0, 0x0b40); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3333); - regmap_write(rt721->mbq_regmap, 0x5810035, 0x0036); - regmap_write(rt721->mbq_regmap, 0x5810030, 0xee00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL1, 0x0140); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x0021); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x8021); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL18, 0x5522); - regmap_write(rt721->mbq_regmap, 0x5b10007, 0x2000); - regmap_write(rt721->mbq_regmap, 0x5B10017, 0x1b0f); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, - RT721_CBJ_A0_GAT_CTRL1, 0x2205); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL4, 0xa105); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3b33); - regmap_write(rt721->mbq_regmap, 0x310400, 0x3043); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3f33); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON13, 0x6048); - regmap_write(rt721->mbq_regmap, 0x310401, 0x3000); - regmap_write(rt721->mbq_regmap, 0x310402, 0x1b00); - regmap_write(rt721->mbq_regmap, 0x310300, 0x000f); - regmap_write(rt721->mbq_regmap, 0x310301, 0x3000); - regmap_write(rt721->mbq_regmap, 0x310302, 0x1b00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON17, 0x0008); - rt_sdca_index_write(rt721->mbq_regmap, RT721_DAC_CTRL, - RT721_DAC_2CH_CTRL3, 0x55ff); - rt_sdca_index_write(rt721->mbq_regmap, RT721_DAC_CTRL, - RT721_DAC_2CH_CTRL4, 0xcc00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_MBIAS_LV_CTRL2, 0x6677); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_VREF2_LV_CTRL1, 0x7600); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL2, 0x1234); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL3, 0x3512); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL1, 0x4040); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL4, 0x1201); - rt_sdca_index_write(rt721->mbq_regmap, RT721_BOOST_CTRL, - RT721_BST_4CH_TOP_GATING_CTRL1, 0x002a); - regmap_write(rt721->regmap, 0x2f58, 0x07); + unsigned int jack_func_status; + struct device *dev = &rt721->slave->dev; - regmap_write(rt721->regmap, 0x2f51, 0x00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_MISC_CTL, 0x0004); + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + &jack_func_status); + dev_dbg(dev, "%s jack func_status=0x%x\n", __func__, jack_func_status); + + if ((jack_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt721->first_hw_init)) { + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF1_HV_CTRL1, 0xe000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8007); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_GE_REL_CTRL1, 0x8011); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL3, 0xcf00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL4, 0x000f); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL1, 0x1100); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL5, 0x0c12); + rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_CTRL2, 0xc002); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b40); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3333); + regmap_write(rt721->mbq_regmap, 0x5810035, 0x0036); + regmap_write(rt721->mbq_regmap, 0x5810030, 0xee00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL1, 0x0140); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x0021); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x8021); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL18, 0x5522); + regmap_write(rt721->mbq_regmap, 0x5b10007, 0x2000); + regmap_write(rt721->mbq_regmap, 0x5B10017, 0x1b0f); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x2205); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL4, 0xa105); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3b33); + regmap_write(rt721->mbq_regmap, 0x310400, 0x3043); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3f33); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6048); + regmap_write(rt721->mbq_regmap, 0x310401, 0x3000); + regmap_write(rt721->mbq_regmap, 0x310402, 0x1b00); + regmap_write(rt721->mbq_regmap, 0x310300, 0x000f); + regmap_write(rt721->mbq_regmap, 0x310301, 0x3000); + regmap_write(rt721->mbq_regmap, 0x310302, 0x1b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON17, 0x0008); + rt_sdca_index_write(rt721->mbq_regmap, RT721_DAC_CTRL, + RT721_DAC_2CH_CTRL3, 0x55ff); + rt_sdca_index_write(rt721->mbq_regmap, RT721_DAC_CTRL, + RT721_DAC_2CH_CTRL4, 0xcc00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_MBIAS_LV_CTRL2, 0x6677); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF2_LV_CTRL1, 0x7600); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL2, 0x1234); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL3, 0x3512); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL1, 0x4040); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL4, 0x1201); + rt_sdca_index_write(rt721->mbq_regmap, RT721_BOOST_CTRL, + RT721_BST_4CH_TOP_GATING_CTRL1, 0x002a); + regmap_write(rt721->regmap, 0x2f58, 0x07); + regmap_write(rt721->regmap, 0x2f51, 0x00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_MISC_CTL, 0x0004); + /* clear flag */ + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0), FUNCTION_NEEDS_INITIALIZATION); + } } static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) diff --git a/sound/soc/codecs/rt721-sdca.h b/sound/soc/codecs/rt721-sdca.h index 24ce188562ba..b8873b18a648 100644 --- a/sound/soc/codecs/rt721-sdca.h +++ b/sound/soc/codecs/rt721-sdca.h @@ -214,6 +214,7 @@ struct rt721_sdca_dmic_kctrl_priv { #define RT721_SDCA_ENT_XU03 0x03 #define RT721_SDCA_ENT_XU0D 0x0d #define RT721_SDCA_ENT_FU55 0x55 +#define RT721_SDCA_ENT0 0x00 /* RT721 SDCA control */ #define RT721_SDCA_CTL_SAMPLE_FREQ_INDEX 0x10 @@ -229,6 +230,7 @@ struct rt721_sdca_dmic_kctrl_priv { #define RT721_SDCA_CTL_VENDOR_DEF 0x30 #define RT721_SDCA_CTL_XUV 0x34 #define RT721_SDCA_CTL_FU_CH_GAIN 0x0b +#define RT721_SDCA_CTL_FUNC_STATUS 0x10 /* RT721 SDCA channel */ #define CH_L 0x01 @@ -261,6 +263,9 @@ struct rt721_sdca_dmic_kctrl_priv { /* RT721 HID ID */ #define RT721_SDCA_HID_ID 0x11 +/* Function_Status */ +#define FUNCTION_NEEDS_INITIALIZATION BIT(5) + enum { RT721_AIF1, /* For headset mic and headphone */ RT721_AIF2, /* For speaker */ From c7420dd9213de36a25e03c06e6d3367735970f31 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:24:59 +0900 Subject: [PATCH 211/924] ASoC: simple-card-utils: Add simple_util_remove_jack() simple_util_init_jack() acquires a GPIO descriptor with gpiod_get_optional() and hands it to snd_soc_jack_add_gpios(), which additionally installs an IRQ handler, a PM notifier, a delayed work and a sysfs export for it. simple-card-utils has no counterpart that releases any of this again. Add simple_util_remove_jack(), the counterpart of simple_util_init_jack(). It releases the jack GPIO and everything snd_soc_jack_add_gpios() installed for it via snd_soc_jack_free_gpios(), and clears sjack->gpio.desc so the descriptor is not left dangling after it has been put. This gives the generic card drivers a card->remove() path to release the jack when the card goes away. The devres cleanup alone is not enough: it is tied to the platform device, so it does not run when the card is unregistered and re-registered while the platform device stays bound - for example when a codec component is unbound and bound again. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260913102502.2805912-2-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- include/sound/simple_card_utils.h | 1 + sound/soc/generic/simple-card-utils.c | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index bd8c3a033577..b0e00555092e 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -219,6 +219,7 @@ static inline int simple_util_parse_aux_devs(struct simple_util_priv *priv, char int simple_util_init_jack(struct snd_soc_card *card, struct simple_util_jack *sjack, int is_hp, char *prefix, char *pin); +void simple_util_remove_jack(struct simple_util_jack *sjack); int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix); int simple_util_init_priv(struct simple_util_priv *priv, struct link_info *li); diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 5f3423129b13..50842627434e 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -825,6 +825,16 @@ int simple_util_init_jack(struct snd_soc_card *card, } EXPORT_SYMBOL_GPL(simple_util_init_jack); +void simple_util_remove_jack(struct simple_util_jack *sjack) +{ + if (!sjack->gpio.desc) + return; + + snd_soc_jack_free_gpios(&sjack->jack, 1, &sjack->gpio); + sjack->gpio.desc = NULL; +} +EXPORT_SYMBOL_GPL(simple_util_remove_jack); + int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix) { struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); From b59a20808727e25bc5c86586955b1901ccc10bd5 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:25:00 +0900 Subject: [PATCH 212/924] ASoC: simple-card: Free jack GPIOs on card remove simple_soc_probe() sets up the headphone and microphone jacks, which takes a reference on the jack detection GPIO and registers an IRQ, a PM notifier, a delayed work and a sysfs export for it. simple-card has no card->remove() callback, so none of this is released when the card is torn down. This goes unnoticed while the card is only torn down together with its platform device, because the devres cleanup registered by snd_soc_jack_add_gpios() is tied to that platform device. It becomes a problem once the card alone is unregistered and registered again while the platform device stays bound - for example when a codec component is unbound and bound again. card->probe() then runs a second time and gpiod_get_optional() fails with -EBUSY, because the descriptor is still held by the previous bind. The card is not registered again and audio stays broken until the platform device itself is unbound. Add simple_soc_remove() as the counterpart of simple_soc_probe() and release both jacks from there. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260913102502.2805912-3-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/generic/simple-card.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index e7ad7af04714..e2334bc10c3d 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -666,6 +666,16 @@ static int simple_soc_probe(struct snd_soc_card *card) return simple_ret(priv, ret); } +static int simple_soc_remove(struct snd_soc_card *card) +{ + struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + + simple_util_remove_jack(&priv->hp_jack); + simple_util_remove_jack(&priv->mic_jack); + + return 0; +} + static int simple_parse_of(struct simple_util_priv *priv) { struct snd_soc_card *card = simple_priv_to_card(priv); @@ -754,6 +764,7 @@ static int simple_probe(struct platform_device *pdev) card->owner = THIS_MODULE; card->dev = dev; card->probe = simple_soc_probe; + card->remove = simple_soc_remove; card->driver_name = "simple-card"; return simple_parse_of(priv); From 698fdd31522c42b486979054b195647d07cd71e8 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:25:01 +0900 Subject: [PATCH 213/924] ASoC: audio-graph-card: Free jack GPIOs on card remove graph_util_card_probe() sets up the headphone and microphone jacks, which takes a reference on the jack detection GPIO and registers an IRQ, a PM notifier, a delayed work and a sysfs export for it. audio-graph-card has no card->remove() callback, so none of this is released when the card is torn down. This goes unnoticed while the card is only torn down together with its platform device, because the devres cleanup registered by snd_soc_jack_add_gpios() is tied to that platform device. It becomes a problem once the card alone is unregistered and registered again while the platform device stays bound - for example when a codec component is unbound and bound again. card->probe() then runs a second time and gpiod_get_optional() fails with -EBUSY, because the descriptor is still held by the previous bind. The card is not registered again and audio stays broken until the platform device itself is unbound. Add graph_util_card_remove() as the counterpart of graph_util_card_probe() and hook it to card->remove. It lives in simple-card-utils.c so that audio-graph-card2 can use it as well. Signed-off-by: Chancel Liu Acked-by: Kuninori Morimoto Link: https://patch.msgid.link/20260913102502.2805912-4-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- include/sound/simple_card_utils.h | 1 + sound/soc/generic/audio-graph-card.c | 1 + sound/soc/generic/simple-card-utils.c | 11 +++++++++++ 3 files changed, 13 insertions(+) diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index b0e00555092e..68faec56970e 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -226,6 +226,7 @@ int simple_util_init_priv(struct simple_util_priv *priv, void simple_util_remove(struct platform_device *pdev); int graph_util_card_probe(struct snd_soc_card *card); +int graph_util_card_remove(struct snd_soc_card *card); int graph_util_is_ports0(struct device_node *port); int graph_util_parse_dai(struct simple_util_priv *priv, struct device_node *ep, struct snd_soc_dai_link_component *dlc, int *is_single_link); diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c index 0a8a6d891d1f..127c2e6b308e 100644 --- a/sound/soc/generic/audio-graph-card.c +++ b/sound/soc/generic/audio-graph-card.c @@ -629,6 +629,7 @@ static int graph_probe(struct platform_device *pdev) card->dapm_widgets = graph_dapm_widgets; card->num_dapm_widgets = ARRAY_SIZE(graph_dapm_widgets); card->probe = graph_util_card_probe; + card->remove = graph_util_card_remove; if (of_device_get_match_data(dev)) priv->dpcm_selectable = 1; diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 50842627434e..98e1b419105b 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -1028,6 +1028,17 @@ int graph_util_card_probe(struct snd_soc_card *card) } EXPORT_SYMBOL_GPL(graph_util_card_probe); +int graph_util_card_remove(struct snd_soc_card *card) +{ + struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + + simple_util_remove_jack(&priv->hp_jack); + simple_util_remove_jack(&priv->mic_jack); + + return 0; +} +EXPORT_SYMBOL_GPL(graph_util_card_remove); + int graph_util_is_ports0(struct device_node *np) { struct device_node *parent __free(device_node) = of_get_parent(np); From df0afa439044d7099055d5dc27189a083d8c9899 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:25:02 +0900 Subject: [PATCH 214/924] ASoC: audio-graph-card2: Free jack GPIOs on card remove audio-graph-card2 uses graph_util_card_probe() to set up the headphone and microphone jacks, but never released them again, so the jack GPIO, its IRQ, the PM notifier, the delayed work and the sysfs export stayed claimed when the card was unbound. Re-binding the card alone - for instance after a codec component is unbound and bound again - then made graph_util_card_probe() fail with -EBUSY and the card was never registered again. Hook up the graph_util_card_remove() counterpart added for audio-graph-card. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260913102502.2805912-5-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/generic/audio-graph-card2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c index 9fb3d3df5cf6..e11bc8135cb0 100644 --- a/sound/soc/generic/audio-graph-card2.c +++ b/sound/soc/generic/audio-graph-card2.c @@ -1310,6 +1310,7 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, goto end; card->probe = graph_util_card_probe; + card->remove = graph_util_card_remove; card->owner = THIS_MODULE; card->dev = dev; From c1d6580f70951de5952cf9142829c0d2d291aa43 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 16:40:14 +0700 Subject: [PATCH 215/924] ALSA: pcm: Propagate snd_pcm_stop() errors in snd_pcm_drop() snd_pcm_drop() currently ignores the return value of snd_pcm_stop() and returns a pre-initialized zero instead. Store the return value of snd_pcm_stop() in 'result' so that any error is propagated to the caller. Found by manual code inspection. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916094014.33609-1-phucduc.bui@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 000e926a1e6a..74054b9ca712 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2267,7 +2267,7 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, static int snd_pcm_drop(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime; - int result = 0; + int result; if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; @@ -2282,7 +2282,7 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream) if (runtime->state == SNDRV_PCM_STATE_PAUSED) snd_pcm_pause(substream, false); - snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + result = snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ return result; From ed7d8ed9a915633f90b0cf528b096abc80e250c9 Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Tue, 15 Sep 2026 09:49:40 +0800 Subject: [PATCH 216/924] ASoC: codecs: nau8360: Fix misleading return in nau8360_message_to_dsp() Fix a smatch static analysis warning: "missing error code? 'ret'". When there are no message parameters (!cmd_info->msg_param), 'ret' is guaranteed to be 0. Return 0 directly. Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202609081933.K4tVJnyh-lkp@intel.com/ Signed-off-by: Neo Chang Link: https://patch.msgid.link/20260915014940.317403-1-YLCHANG2@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8360-dsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/nau8360-dsp.c b/sound/soc/codecs/nau8360-dsp.c index 2e5b61bfc303..65ffec8b3a39 100644 --- a/sound/soc/codecs/nau8360-dsp.c +++ b/sound/soc/codecs/nau8360-dsp.c @@ -211,7 +211,7 @@ static int nau8360_message_to_dsp(struct snd_soc_component *cp, snd_soc_component_write(cp, dsp_addr, payload); if (!cmd_info->msg_param) - return ret; + return 0; /* sending payload + padding */ payload = nau8360_pack_param(param_offset, param_size); From 6d5cd3882ce57f7a55c1ba47e64f946496d51264 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Tue, 15 Sep 2026 10:30:05 +0800 Subject: [PATCH 217/924] ASoC: codecs: ES8326: Add private members about HPF Add private members related to HPF. Add Kcontrol for HPF Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260915023006.22940-2-zhangyi@everest-semi.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8326.c | 124 +++++++++++++++++++++++++++++++++++++- sound/soc/codecs/es8326.h | 4 ++ 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c index b6eadc2e9659..47d4efea4921 100644 --- a/sound/soc/codecs/es8326.c +++ b/sound/soc/codecs/es8326.c @@ -33,11 +33,16 @@ struct es8326_priv { * while enabling or disabling or during an irq. */ struct mutex lock; + struct mutex hpf_lock; u8 jack_pol; u8 interrupt_src; u8 interrupt_clk; u8 hpl_vol; u8 hpr_vol; + u8 hpfl; + u8 hpfr; + u32 hpf_freq; + u32 capture_rate; bool jd_inverted; unsigned int sysclk; @@ -47,6 +52,48 @@ struct es8326_priv { int jack_remove_retry; }; +static const u32 hpf_table[10][10] = { + {1020, 754, 624, 559, 527, 511, 502, 498, 497, 496}, + {754, 495, 368, 306, 274, 259, 251, 247, 246, 244}, + {624, 368, 243, 182, 151, 136, 128, 124, 123, 121}, + {559, 306, 182, 120, 90, 75, 68, 63, 62, 60}, + {527, 274, 151, 90, 60, 45, 38, 33, 32, 31}, + {511, 259, 136, 75, 45, 30, 23, 19, 18, 17}, + {502, 251, 128, 68, 38, 23, 16, 13, 11, 11}, + {498, 247, 124, 63, 33, 19, 13, 10, 8, 8}, + {497, 246, 123, 62, 32, 18, 11, 8, 8, 0}, + {496, 244, 121, 60, 31, 17, 11, 8, 0, 0} +}; + +static bool find_best_hpf_freq(u32 target_hz, u8 *hpf1, u8 *hpf2) +{ + int best_row = -1, best_col = -1; + u32 min_diff = U32_MAX; + u32 f, diff; + int i, j; + + if (target_hz > 1020) + return false; + + for (i = 0; i < 10; i++) { + for (j = i; j < 10; j++) { + f = hpf_table[i][j]; + + diff = (target_hz > f) ? (target_hz - f) : (f - target_hz); + if (diff < min_diff) { + min_diff = diff; + best_row = i; + best_col = j; + } + } + } + + *hpf1 = best_col + ES8326_HPF_OFFSET; + *hpf2 = best_row + ES8326_HPF_OFFSET; + + return true; +} + static int es8326_crosstalk1_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -189,6 +236,48 @@ static int es8326_hprvol_set(struct snd_kcontrol *kcontrol, return 0; } +static int es8326_hpf_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = es8326->hpf_freq; + return 0; +} + +static int es8326_hpf_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + u32 freq; + bool hpf; + + if ((ucontrol->value.integer.value[0] > 1020) || (ucontrol->value.integer.value[0] < 0)) + return -EBUSY; + + if (es8326->hpf_freq == ucontrol->value.integer.value[0]) + return 0; + + if (es8326->capture_rate) { + guard(mutex)(&es8326->hpf_lock); + freq = (ucontrol->value.integer.value[0] * 48000) / es8326->capture_rate; + + hpf = find_best_hpf_freq(freq, &es8326->hpfl, &es8326->hpfr); + if (!hpf) + return -EBUSY; + + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS1, 0x0f, es8326->hpfl); + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS2, 0x0f, es8326->hpfr); + } else { + dev_dbg_ratelimited(component->dev, "PCM_STREAM_CAPTURE is not active.retain the input frequency\n"); + } + es8326->hpf_freq = ucontrol->value.integer.value[0]; + + return 1; +} + static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(dac_vol_tlv, -9550, 50, 0); static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(adc_vol_tlv, -9550, 50, 0); static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(adc_analog_pga_tlv, 0, 300, 0); @@ -269,6 +358,8 @@ static const struct snd_kcontrol_new es8326_snd_controls[] = { SOC_ENUM("ALC Capture Winsize", alc_winsize), SOC_SINGLE_TLV("ALC Capture Target Level", ES8326_ALC_LEVEL, 0, 0x0f, 0, drc_target_tlv), + SOC_SINGLE_EXT("ADC HPF Freq Select", SND_SOC_NOPM, 0, 1020, 0, + es8326_hpf_get, es8326_hpf_set), SOC_SINGLE_EXT("CROSSTALK1", SND_SOC_NOPM, 0, 31, 0, es8326_crosstalk1_get, es8326_crosstalk1_set), @@ -353,8 +444,6 @@ static bool es8326_writeable_register(struct device *dev, unsigned int reg) case ES8326_BIAS_SW2: case ES8326_BIAS_SW3: case ES8326_BIAS_SW4: - case ES8326_ADC_HPFS1: - case ES8326_ADC_HPFS2: return false; default: return true; @@ -558,6 +647,8 @@ static int es8326_pcm_hw_params(struct snd_pcm_substream *substream, struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); u8 srate = 0; int coeff, array; + u32 freq; + bool hpf; if (es8326->version == 0) { coeff_div = coeff_div_v0; @@ -612,6 +703,28 @@ static int es8326_pcm_hw_params(struct snd_pcm_substream *substream, dev_warn(component->dev, "Clock coefficients do not match"); } + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + es8326->capture_rate = params_rate(params); + freq = (es8326->hpf_freq * 48000) / params_rate(params); + hpf = find_best_hpf_freq(freq, &es8326->hpfl, &es8326->hpfr); + if (!hpf) { + dev_err(component->dev, "The HPF frequency is invalid\n"); + return -EINVAL; + } + } + + return 0; +} + +static int es8326_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + es8326->capture_rate = 0; + return 0; } @@ -636,6 +749,8 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x40); regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x30); } + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS1, 0x0f, 0x04); + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS2, 0x0f, 0x04); } } else { if (!es8326->calibrated) { @@ -728,6 +843,7 @@ static int es8326_set_bias_level(struct snd_soc_component *codec, static const struct snd_soc_dai_ops es8326_ops = { .hw_params = es8326_pcm_hw_params, + .hw_free = es8326_pcm_hw_free, .set_fmt = es8326_set_dai_fmt, .set_sysclk = es8326_set_dai_sysclk, .mute_stream = es8326_mute, @@ -781,6 +897,8 @@ static void es8326_capture_pop_handler(struct work_struct *work) struct es8326_priv *es8326 = container_of(work, struct es8326_priv, capture_pop_work.work); + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS1, 0x0f, es8326->hpfl); + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS2, 0x0f, es8326->hpfr); regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, 0x0F, 0x00); } @@ -1202,6 +1320,7 @@ static int es8326_probe(struct snd_soc_component *component) } dev_dbg(component->dev, "interrupt-clk %x", es8326->interrupt_clk); + es8326->hpf_freq = ES8326_HPF_DEFAULT; es8326_init(component); return 0; } @@ -1288,6 +1407,7 @@ static int es8326_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, es8326); es8326->i2c = i2c; mutex_init(&es8326->lock); + mutex_init(&es8326->hpf_lock); es8326->regmap = devm_regmap_init_i2c(i2c, &es8326_regmap_config); if (IS_ERR(es8326->regmap)) { ret = PTR_ERR(es8326->regmap); diff --git a/sound/soc/codecs/es8326.h b/sound/soc/codecs/es8326.h index 1c5b3ec70a1e..c78d528e7dfe 100644 --- a/sound/soc/codecs/es8326.h +++ b/sound/soc/codecs/es8326.h @@ -149,6 +149,10 @@ #define ES8326_ADC_SRC_DMIC_SDIN3 6 #define ES8326_ADC_SRC_DMIC_SDIN3_INV 7 +/* HPF Settings */ +#define ES8326_HPF_DEFAULT 8 +#define ES8326_HPF_OFFSET 4 + #define ES8326_ADC_AMIC ((ES8326_ADC_SRC_ANA_INV_SW1 << ES8326_ADC2_SHIFT) \ | (ES8326_ADC_SRC_ANA_INV_SW1 << ES8326_ADC1_SHIFT)) #define ES8326_ADC_DMIC ((ES8326_ADC_SRC_DMIC_SDIN2 << ES8326_ADC2_SHIFT) \ From b05772f71877011948b0995eb102279b01ef918a Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Tue, 15 Sep 2026 10:30:06 +0800 Subject: [PATCH 218/924] ASoC: codecs: ES8326: Adjust the standby configuration Adjust the configuration in SND_SOC_BIAS_STANDBY for better power consumption performance Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260915023006.22940-3-zhangyi@everest-semi.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8326.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c index 47d4efea4921..918164005037 100644 --- a/sound/soc/codecs/es8326.c +++ b/sound/soc/codecs/es8326.c @@ -820,7 +820,8 @@ static int es8326_set_bias_level(struct snd_soc_component *codec, case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: - regmap_write(es8326->regmap, ES8326_ANA_PDN, 0x3b); + regmap_write(es8326->regmap, ES8326_PGA_PDN, 0x58); + regmap_write(es8326->regmap, ES8326_ANA_PDN, 0x13); regmap_update_bits(es8326->regmap, ES8326_CLK_CTL, 0x20, 0x00); regmap_write(es8326->regmap, ES8326_SDINOUT1_IO, ES8326_IO_INPUT); if (es8326->version > ES8326_VERSION_B) { @@ -983,7 +984,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) struct es8326_priv *es8326 = container_of(work, struct es8326_priv, jack_detect_work.work); struct snd_soc_component *comp = es8326->component; - unsigned int iface; + unsigned int iface, pga_status; guard(mutex)(&es8326->lock); iface = snd_soc_component_read(comp, ES8326_HPDET_STA); @@ -1079,12 +1080,19 @@ static void es8326_jack_detect_handler(struct work_struct *work) dev_dbg(comp->dev, "Headset detected\n"); snd_soc_jack_report(es8326->jack, SND_JACK_HEADSET, SND_JACK_HEADSET); - regmap_update_bits(es8326->regmap, ES8326_PGA_PDN, - 0x08, 0x08); - regmap_write(es8326->regmap, ES8326_ADC1_SRC, 0x00); - regmap_write(es8326->regmap, ES8326_ADC2_SRC, 0x00); - regmap_update_bits(es8326->regmap, ES8326_PGA_PDN, - 0x08, 0x00); + pga_status = snd_soc_component_read(comp, ES8326_PGA_PDN); + + if ((pga_status & 0x08) >> 3) { + regmap_write(es8326->regmap, ES8326_ADC1_SRC, 0x00); + regmap_write(es8326->regmap, ES8326_ADC2_SRC, 0x00); + } else { + regmap_update_bits(es8326->regmap, ES8326_PGA_PDN, + 0x08, 0x08); + regmap_write(es8326->regmap, ES8326_ADC1_SRC, 0x00); + regmap_write(es8326->regmap, ES8326_ADC2_SRC, 0x00); + regmap_update_bits(es8326->regmap, ES8326_PGA_PDN, + 0x08, 0x00); + } usleep_range(10000, 15000); } } From 480a5528cb6268d626632d45d4a1f29b4c0c11df Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 07:27:37 +0000 Subject: [PATCH 219/924] ASoC: wm8994: Fix runtime PM imbalance in wm8994_component_probe() In wm8994_component_probe(), pm_runtime_get_sync() is invoked before reading the GPIO configuration registers. If either regmap_read() fails, the function jumps to err_irq without calling pm_runtime_put(), leaving the runtime PM usage count incremented and causing a leak. Add pm_runtime_put() on both GPIO read failure paths before jumping to err_irq. Fixes: f5a2cda4f1db ("ASoC: wm8994: Ensure the device is resumed in wm89xx_mic_detect functions") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260916072737.1970990-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/wm8994.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 97d8df3815ac..96d3aae36862 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -4360,6 +4360,7 @@ static int wm8994_component_probe(struct snd_soc_component *component) ret = regmap_read(control->regmap, WM8994_GPIO_1, ®); if (ret < 0) { dev_err(component->dev, "Failed to read GPIO1 state: %d\n", ret); + pm_runtime_put(component->dev); goto err_irq; } if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { @@ -4372,6 +4373,7 @@ static int wm8994_component_probe(struct snd_soc_component *component) ret = regmap_read(control->regmap, WM8994_GPIO_6, ®); if (ret < 0) { dev_err(component->dev, "Failed to read GPIO6 state: %d\n", ret); + pm_runtime_put(component->dev); goto err_irq; } if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { From e04dcb895b4622d67164e218a1179785e2c75999 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 07:29:00 +0000 Subject: [PATCH 220/924] ASoC: arizona-jack: Fix runtime PM imbalance in arizona_start_hpdet_acc_id() In arizona_start_hpdet_acc_id(), pm_runtime_get_sync() is called to keep the device powered during headphone detection. If setting the HPDET mode or starting the measurement fails and the function jumps to err, pm_runtime_put_autosuspend() was not called while info->hpdet_active was set to false, resulting in a runtime PM usage count leak. Add pm_runtime_put_autosuspend() in the err error path to keep runtime PM balanced. Fixes: 9dd5e53d9d2f ("extcon: arizona: Retry HPDET identification for high impedance") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260916072900.1971595-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/arizona-jack.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/arizona-jack.c b/sound/soc/codecs/arizona-jack.c index 819d080b1188..6587d46a1867 100644 --- a/sound/soc/codecs/arizona-jack.c +++ b/sound/soc/codecs/arizona-jack.c @@ -699,6 +699,7 @@ static void arizona_start_hpdet_acc_id(struct arizona_priv *info) snd_soc_jack_report(info->jack, SND_JACK_HEADPHONE, SND_JACK_LINEOUT | SND_JACK_HEADPHONE); + pm_runtime_put_autosuspend(arizona->dev); info->hpdet_active = false; } From 98fe8aab04cec6fcadeb10a757235225d550be36 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 22:21:18 +0800 Subject: [PATCH 221/924] ASoC: rt1015: make control calibration failures observable The bypass-boost control runs a hardware calibration while bypassing the register cache. It ignores every register access and cache restore failure, then publishes both the requested mode and calibration completion. Return failures from the complete calibration sequence, always restore normal cache operation and unlock DAPM, and only publish the control state after the calibration and final mode write both succeed. The mixer control callback is an effective error consumer, independent of deferred component resume. Fixes: da145172b236 ("ASoC: rt1015: Fix DC calibration on bypass boost mode") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830142118.36668-1-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1015.c | 81 +++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/sound/soc/codecs/rt1015.c b/sound/soc/codecs/rt1015.c index c6d59c286064..5d6be7cc67b4 100644 --- a/sound/soc/codecs/rt1015.c +++ b/sound/soc/codecs/rt1015.c @@ -488,35 +488,72 @@ static int rt1015_bypass_boost_get(struct snd_kcontrol *kcontrol, return 0; } -static void rt1015_calibrate(struct rt1015_priv *rt1015) +static int rt1015_calibrate(struct rt1015_priv *rt1015) { struct snd_soc_component *component = rt1015->component; struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct regmap *regmap = rt1015->regmap; + int ret, sync_ret; snd_soc_dapm_mutex_lock(dapm); regcache_cache_bypass(regmap, true); - regmap_write(regmap, RT1015_CLK_DET, 0x0000); - regmap_write(regmap, RT1015_PWR4, 0x00B2); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x0009); + ret = regmap_write(regmap, RT1015_CLK_DET, 0x0000); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_PWR4, 0x00B2); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x0009); + if (ret) + goto restore_cache; + msleep(100); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000A); + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000A); + if (ret) + goto restore_cache; + msleep(100); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000C); + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000C); + if (ret) + goto restore_cache; + msleep(100); - regmap_write(regmap, RT1015_CLSD_INTERNAL8, 0x2028); - regmap_write(regmap, RT1015_CLSD_INTERNAL9, 0x0140); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000D); + ret = regmap_write(regmap, RT1015_CLSD_INTERNAL8, 0x2028); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_CLSD_INTERNAL9, 0x0140); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000D); + if (ret) + goto restore_cache; + msleep(300); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x0008); - regmap_write(regmap, RT1015_SYS_RST1, 0x05F5); - regmap_write(regmap, RT1015_CLK_DET, 0x8000); + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x0008); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_SYS_RST1, 0x05F5); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_CLK_DET, 0x8000); + +restore_cache: regcache_cache_bypass(regmap, false); regcache_mark_dirty(regmap); - regcache_sync(regmap); + sync_ret = regcache_sync(regmap); + if (!ret) + ret = sync_ret; snd_soc_dapm_mutex_unlock(dapm); + + return ret; } static int rt1015_bypass_boost_put(struct snd_kcontrol *kcontrol, @@ -525,6 +562,8 @@ static int rt1015_bypass_boost_put(struct snd_kcontrol *kcontrol, struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct rt1015_priv *rt1015 = snd_soc_component_get_drvdata(component); + int bypass_boost = rt1015->bypass_boost; + int ret; if (rt1015->dac_is_used) { dev_err(component->dev, "DAC is being used!\n"); @@ -534,13 +573,23 @@ static int rt1015_bypass_boost_put(struct snd_kcontrol *kcontrol, rt1015->bypass_boost = ucontrol->value.integer.value[0]; if (rt1015->bypass_boost == RT1015_Bypass_Boost && !rt1015->cali_done) { - rt1015_calibrate(rt1015); - rt1015->cali_done = 1; + ret = rt1015_calibrate(rt1015); + if (ret) + goto restore_boost; - regmap_write(rt1015->regmap, RT1015_MONO_DYNA_CTRL, 0x0010); + ret = regmap_write(rt1015->regmap, RT1015_MONO_DYNA_CTRL, + 0x0010); + if (ret) + goto restore_boost; + + rt1015->cali_done = 1; } return 0; + +restore_boost: + rt1015->bypass_boost = bypass_boost; + return ret; } static const char * const rt1015_dac_output_vol_select[] = { From 48ed992bfbe53537ad1cbc8ad68f7031e7ac0af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan=20Ghe=C8=9Bu?= Date: Tue, 8 Sep 2026 20:07:40 +0300 Subject: [PATCH 222/924] ASoC: SOF: imx: Prevent stack OOB read in DSP panic dump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 58bb5081cba1 ("ASoC: SOF: Xtensa: dump ar registers to restore call stack") added a shared Xtensa helper that iterates over a flexible array of AR registers (`ar[]`) controlled by `plat_hdr.numaregs`. While Intel IPC4 allocates dynamic storage for the AR block, the i.MX IPC3 path reads the oops message into a stack-allocated struct without backing storage for `ar[]`, while leaving `numaregs` unvalidated. This causes a stack out-of-bounds read when printing a DSP panic. Clear `numaregs` to 0 on i.MX since the AR block is not fetched or supported on this platform, preventing unsafe out-of-bounds memory accesses in the shared Xtensa helper. Fixes: 58bb5081cba1 ("ASoC: SOF: Xtensa: dump ar registers to restore call stack") Signed-off-by: Ștefan GheÈ›u Link: https://patch.msgid.link/20260908170740.276800-1-stefanghetu9@gmail.com Signed-off-by: Mark Brown --- sound/soc/sof/imx/imx-common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sof/imx/imx-common.c b/sound/soc/sof/imx/imx-common.c index 7a03c8cc5dd4..436fe49246ba 100644 --- a/sound/soc/sof/imx/imx-common.c +++ b/sound/soc/sof/imx/imx-common.c @@ -34,6 +34,7 @@ void imx8_get_registers(struct snd_sof_dev *sdev, /* first read registers */ sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops)); + xoops->plat_hdr.numaregs = 0; /* then get panic info */ if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) { From b2c5435300c815373a2112522eb38857313b5b96 Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Sat, 12 Sep 2026 19:29:50 +0300 Subject: [PATCH 223/924] ASoC: tas2783-sdw: stop describing the Latency and XU ID/Version Controls The read-only Control list and tas2783_sdca_mbq_size() still describe the Latency Control of every Entity - twenty-one registers in all - and the XU ID and XU Version of the Extension Unit. The driver never reads any of them, and the platform firmware seen so far describes exactly one of the Latencies (FU21, as a DisCo constant) and neither of the two XU Controls. XU ID answers COMMAND_IGNORED on the bus in every power state. Since commit f4ffa3820949 ("ASoC: tas2783-sdw: do not cache read-only Controls") they have no defaults and are marked volatile, so every access goes to the bus. Remove them from both tables: the driver then neither declares them readable nor sizes them, and the read-only list is left with the readings the device actually reports - Clock Valid, the actual power state, the protection status and the algorithm ready flags. No functional change for the driver itself, which does not read any of the removed registers. Link: https://lore.kernel.org/all/70a91202-e801-4008-bec8-883b229f9f0f@linux.dev/ Signed-off-by: Andrey Golovko Link: https://patch.msgid.link/20260912163500.8412-1-andrey.golovko@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2783-sdw.c | 53 +++------------------------------- 1 file changed, 4 insertions(+), 49 deletions(-) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index 04e10473c705..85ab3fd83c7b 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -333,7 +333,6 @@ static int tas2783_sdca_mbq_size(struct device *dev, u32 reg) case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU26, 0x11, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_TG23, 0x10, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x01, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x08, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x0a, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x10, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x14, 0): @@ -419,7 +418,6 @@ static int tas2783_sdca_mbq_size(struct device *dev, u32 reg) case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x0b, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x0b, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x0b, 1): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x07, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x09, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x12, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x12, 0): @@ -431,27 +429,8 @@ static int tas2783_sdca_mbq_size(struct device *dev, u32 reg) case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT21, 0x11, 0): return 2; - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU23, 0x10, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT21, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT26, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT28, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT29, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT23, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT24, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT25, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT28, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x06, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x10, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x10, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x06, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x12, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x13, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_SAPU29, 0x05, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x06, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU26, 0x06, 0): return 4; default: @@ -465,47 +444,23 @@ static bool tas2783_readable_register(struct device *dev, unsigned int reg) } /* - * The read-only SDCA Controls the driver describes: the Latency of every - * Entity, the Clock Valid of every Clock Source, the actual power state of - * the Power Domain Entity, the protection status, the algorithm ready flag - * and the Extension Unit id, version and firmware download status. None of - * them is a setting; every one is a reading of device state. + * The read-only SDCA Controls the driver describes: the Clock Valid of every + * Clock Source, the actual power state of the Power Domain Entity, the + * protection status and the algorithm ready flags. None of them is a + * setting; every one is a reading of device state. */ static bool tas2783_read_only_control(unsigned int reg) { switch (reg) { - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21, 0x10, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU23, 0x10, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU26, 0x10, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x07, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x08, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS24, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS21, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS25, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS26, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS28, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE23, 0x10, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_UDMPU23, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x05, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x11, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU21, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU26, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT21, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT29, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT26, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT28, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT24, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT23, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT25, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT28, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MU26, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT127, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU127, 0x10, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS127, 0x02, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x08, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x04, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x04, 0): return true; From bb345be9d428a62cc2038cf0db3b5f01784839eb Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 13:36:01 +0700 Subject: [PATCH 224/924] ASoC: codecs: pcm6240: Return 0 explicitly on success The return value of pcmdev_dev_read() is already checked, and errors are returned immediately. Therefore, the remaining paths are successful paths and should return 0 explicitly instead of returning ret. Reported-by: Dan Carpenter Link: https://lore.kernel.org/all/aqfHiX3pE-0BiCt9@stanley.mountain/ Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916063601.23625-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/pcm6240.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c index db85ae2f8aed..4b5569a5b062 100644 --- a/sound/soc/codecs/pcm6240.c +++ b/sound/soc/codecs/pcm6240.c @@ -620,12 +620,12 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, /* Set to wide-range mode, before using vol ctrl. */ if (!val && vol_ctrl_type == PCMDEV_PCM1690_VOL_CTRL) { ucontrol->value.integer.value[0] = -25500; - return ret; + return 0; } /* Set to fine mode, before using fine vol ctrl. */ if (val && vol_ctrl_type == PCMDEV_PCM1690_FINE_VOL_CTRL) { ucontrol->value.integer.value[0] = -12750; - return ret; + return 0; } } @@ -641,7 +641,7 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, val = mc->invert ? max - val : val; ucontrol->value.integer.value[0] = val; - return ret; + return 0; } static int pcmdevice_get_volsw(struct snd_kcontrol *kcontrol, From eeff736ebf5d6c6c605808d0e411a214efadadb9 Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Mon, 14 Sep 2026 09:56:47 +0800 Subject: [PATCH 225/924] ASoC: codecs: nau8360: Block DSP path selection when firmware load fails Block the DSP path selection if the firmware fails to load to prevent invalid routing states. If the firmware is not ready, nau8360_dac_mux_put_enum() will print a rate-limited warning and return -EBUSY. This rejects the invalid userspace request and maintains the original hardware state without causing audio wrong status. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-sound/aqK_oOpMAXVhxkHJ@stanley.mountain/ Suggested-by: Mark Brown Signed-off-by: Neo Chang Link: https://patch.msgid.link/20260914015648.311986-2-YLCHANG2@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8360.c | 9 +++++++-- sound/soc/codecs/nau8360.h | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/nau8360.c b/sound/soc/codecs/nau8360.c index 89b8ee80d6c8..f5dee12127cd 100644 --- a/sound/soc/codecs/nau8360.c +++ b/sound/soc/codecs/nau8360.c @@ -715,8 +715,13 @@ static int nau8360_dac_mux_put_enum(struct snd_kcontrol *kcontrol, int ret = 0; if (snd_soc_dapm_get_bias_level(dapm) > SND_SOC_BIAS_STANDBY) { - dev_warn(nau8360->dev, "changing path is not allowed during playback"); - return ret; + dev_warn_ratelimited(nau8360->dev, "changing path is not allowed during playback"); + return -EBUSY; + } + + if (item[0] == NAU8360_DAC_SRC_DSP && !nau8360->load_fw_done) { + dev_warn_ratelimited(nau8360->dev, "Cannot enable DSP: Firmware not ready or disabled\n"); + return -EBUSY; } mutex_lock(&nau8360->lock); diff --git a/sound/soc/codecs/nau8360.h b/sound/soc/codecs/nau8360.h index cc640ba8c838..71396747c0fa 100644 --- a/sound/soc/codecs/nau8360.h +++ b/sound/soc/codecs/nau8360.h @@ -870,6 +870,12 @@ enum { NAU8360_TDM_TXN, }; +/* DAC Source Path*/ +enum { + NAU8360_DAC_SRC_HW1 = 0, + NAU8360_DAC_SRC_DSP, +}; + /* PLL Source */ enum { NAU8360_PLL_MCLK, From 4862e73eaf088c54ac79a1c849b3194d93e2e3a4 Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Mon, 14 Sep 2026 09:56:48 +0800 Subject: [PATCH 226/924] ASoC: codecs: nau8360: Fix AB-BA deadlock in mux update nau8360_dac_mux_put_enum() acquires nau8360->lock before the DAPM mutex, causing an AB-BA lock inversion. This deadlocks with concurrent stream startups or mixer updates, which acquire locks in the reverse order. Fix this by replacing nau8360->lock with the DAPM mutex to protect pre-checks, releasing it before calling snd_soc_dapm_put_enum_double(). Reported-by: Sashiko AI Link: https://sashiko.dev/#/patchset/20260908030342.222655-1-YLCHANG2@nuvoton.com?part=1 Signed-off-by: Neo Chang Link: https://patch.msgid.link/20260914015648.311986-3-YLCHANG2@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8360.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/nau8360.c b/sound/soc/codecs/nau8360.c index f5dee12127cd..f0942739d7ab 100644 --- a/sound/soc/codecs/nau8360.c +++ b/sound/soc/codecs/nau8360.c @@ -689,6 +689,8 @@ static void nau8360_dsp_switch(struct snd_soc_component *component, bool enable) struct regmap *regmap = nau8360->regmap; int value = NAU8360_PEQ_BAND_8; + mutex_lock(&nau8360->lock); + /* If DSP is enabled, unstall HW3 engine and DSP, loading DSP firmware, * and configure PEQ after dsp reset. */ @@ -702,6 +704,7 @@ static void nau8360_dsp_switch(struct snd_soc_component *component, bool enable) regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_BAND_MASK, value << NAU8360_PEQ_BAND_SFT); + mutex_unlock(&nau8360->lock); } static int nau8360_dac_mux_put_enum(struct snd_kcontrol *kcontrol, @@ -714,27 +717,26 @@ static int nau8360_dac_mux_put_enum(struct snd_kcontrol *kcontrol, unsigned int *item = ucontrol->value.enumerated.item; int ret = 0; + snd_soc_dapm_mutex_lock(dapm); if (snd_soc_dapm_get_bias_level(dapm) > SND_SOC_BIAS_STANDBY) { dev_warn_ratelimited(nau8360->dev, "changing path is not allowed during playback"); + snd_soc_dapm_mutex_unlock(dapm); return -EBUSY; } if (item[0] == NAU8360_DAC_SRC_DSP && !nau8360->load_fw_done) { dev_warn_ratelimited(nau8360->dev, "Cannot enable DSP: Firmware not ready or disabled\n"); + snd_soc_dapm_mutex_unlock(dapm); return -EBUSY; } - - mutex_lock(&nau8360->lock); + snd_soc_dapm_mutex_unlock(dapm); ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); if (ret <= 0) - goto unlock; + return ret; nau8360_dsp_switch(component, snd_soc_enum_item_to_val(e, item[0])); -unlock: - mutex_unlock(&nau8360->lock); - return ret; } From e419e9a6cbfe1d0947dd5ab8dabe9a5ed4b4f572 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:01 +0530 Subject: [PATCH 227/924] ASoC: SOF: amd: return -EINVAL for unknown PCI revision in acp7x suspend amd_sof_acp7x_suspend() is registered only for ACP7.B and ACP7.F platforms. If an unexpected PCI revision reaches the switch statement the default case previously fell through, leaving ACP_CONTROL with a stale value (enable=false) before writing ZSC_DSP_CTRL=1. Return -EINVAL instead to surface the programming error immediately. Fixes: 1c9646f3180e ("ASoC: SOF: amd: add system and runtime PM ops for ACP7x") Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-2-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index f89ad86260b4..688df7acd2e2 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1197,7 +1197,8 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) enable = true; break; default: - break; + dev_err(sdev->dev, "Unexpected PCI revision: 0x%x\n", acp_data->pci_rev); + return -EINVAL; } snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); From db2167f2e3f72db64d01356cc9f2924d32fd5a51 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:02 +0530 Subject: [PATCH 228/924] ASoC: SOF: amd: fix amd_sof_acp_remove() teardown ordering Call free_irq() before amd_sof_sdw_exit() in amd_sof_acp_remove(). amd_sof_sdw_exit() sets adata->sdw to NULL; if an IRQ fires between that point and the subsequent free_irq(), acp_irq_handler() may dereference the now-NULL pdev[] pointer, causing a use-after-free. Fixes: d948218424bf ("ASoC: SOF: amd: add code for invoking soundwire manager helper functions") Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-3-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 688df7acd2e2..e4cdb74c075d 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1048,12 +1048,12 @@ void amd_sof_acp_remove(struct snd_sof_dev *sdev) { struct acp_dev_data *adata = sdev->pdata->hw_pdata; - if (adata->sdw) - amd_sof_sdw_exit(sdev); - if (sdev->ipc_irq) free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); + if (adata->dmic_dev) platform_device_unregister(adata->dmic_dev); From b6aaef8ca8c305170de83e621c7e16d56731312a Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:03 +0530 Subject: [PATCH 229/924] ASoC: SOF: amd: fix amd_sof_acp_probe() error unwind ordering Call amd_sof_sdw_exit() after free_irq() in the free_ipc_irq error label of amd_sof_acp_probe(). Without this, an IRQ that fires between sdw context teardown and free_irq() can dereference freed SoundWire resources. Fixes: d948218424bf ("ASoC: SOF: amd: add code for invoking soundwire manager helper functions") Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-4-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index e4cdb74c075d..5570f3d1348d 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1038,6 +1038,8 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) free_ipc_irq: free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); unregister_dev: platform_device_unregister(adata->dmic_dev); return ret; From 203ea76c4b0a84f0c70e0ab27503a29f77e0a471 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:04 +0530 Subject: [PATCH 230/924] ASoC: SOF: amd: add ACP7.B/7.F PDM controller scan and pdata propagation Add acp_sof_scan_pdm_devices() to read the acp-audio-ep-port ACPI _DSD property from the PDM child device on ACP7.B/7.F platforms. Value 4 selects PDM0 (ACP7X_PDM_DMIC0), value 5 selects PDM1 (ACP7X_PDM_DMIC1). Unrecognized values are reported via dev_warn(). The selected controller is stored in acp_dev_data.pdm_sel and propagated to the machine driver via mach->pdata in amd_sof_machine_select() so the machine driver probe can register the correct SOF DMIC DAI link. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-5-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/soc_amd_sdw_common.h | 13 +++++++++++ sound/soc/sof/amd/acp-common.c | 17 ++++++++++++++ sound/soc/sof/amd/acp.c | 31 ++++++++++++++++++++++++++ sound/soc/sof/amd/acp.h | 10 +++++++++ 4 files changed, 71 insertions(+) diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index 3930cc46fa58..17e4e97fb3d1 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -23,6 +23,19 @@ #define ACP71_PCI_REV 0x71 #define ACP72_PCI_REV 0x72 +/** + * struct amd_pdm_pdata - platform data passed via mach->pdata to machine driver + * @pdm_sel: active PDM controller (ACP7X_PDM_DMIC0 or ACP7X_PDM_DMIC1), + * non-zero when a PDM controller was identified via ACPI _DSD + * + * Carries the PDM controller selection for ACP7.B/7.F platforms, derived + * from the acp-audio-ep-port ACPI _DSD property and passed via mach->pdata + * to the machine driver. + */ +struct amd_pdm_pdata { + unsigned int pdm_sel; +}; + #define SOC_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define ASOC_SDW_FOUR_SPK BIT(4) #define ASOC_SDW_ACP_DMIC BIT(5) diff --git a/sound/soc/sof/amd/acp-common.c b/sound/soc/sof/amd/acp-common.c index df656cdc1527..33540f7c421b 100644 --- a/sound/soc/sof/amd/acp-common.c +++ b/sound/soc/sof/amd/acp-common.c @@ -16,6 +16,7 @@ #include "acp.h" #include "acp-dsp-offset.h" #include +#include "../../amd/acp/soc_amd_sdw_common.h" /** * amd_sof_ipc_dump() - This function is called when IPC tx times out. @@ -177,6 +178,7 @@ struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev) struct acp_dev_data *acp_data = sdev->pdata->hw_pdata; const struct sof_dev_desc *desc = sof_pdata->desc; struct snd_soc_acpi_mach *mach = NULL; + struct amd_pdm_pdata *pdm_pdata; if (desc->machines) mach = snd_soc_acpi_find_machine(desc->machines); @@ -188,7 +190,22 @@ struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev) } } + mach = devm_kmemdup(sdev->dev, mach, sizeof(*mach), GFP_KERNEL); + if (!mach) { + dev_err(sdev->dev, "failed to allocate machine entry copy\n"); + return NULL; + } + mach->mach_params.subsystem_rev = acp_data->pci_rev; + + if (acp_data->pdm_sel) { + pdm_pdata = devm_kzalloc(sdev->dev, sizeof(*pdm_pdata), GFP_KERNEL); + if (!pdm_pdata) + return NULL; + pdm_pdata->pdm_sel = acp_data->pdm_sel; + mach->pdata = pdm_pdata; + } + sof_pdata->tplg_filename = mach->sof_tplg_filename; sof_pdata->fw_filename = mach->fw_filename; diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 5570f3d1348d..37909f2d86a4 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -853,6 +853,31 @@ int amd_sof_acp_resume(struct snd_sof_dev *sdev) } EXPORT_SYMBOL_NS(amd_sof_acp_resume, "SND_SOC_SOF_AMD_COMMON"); +static void acp_sof_scan_pdm_devices(struct snd_sof_dev *sdev, + struct acpi_device *pdm_dev) +{ + struct acp_dev_data *acp_data = sdev->pdata->hw_pdata; + struct fwnode_handle *fwnode, *child; + u32 ep_port_val; + + fwnode = acpi_fwnode_handle(pdm_dev); + child = fwnode_get_next_child_node(fwnode, NULL); + if (!child) + return; + + if (!fwnode_property_read_u32(child, "acp-audio-ep-port", &ep_port_val)) { + if (ep_port_val == ACP_DEV_PORT_PDM) + acp_data->pdm_sel = ACP7X_PDM_DMIC0; + else if (ep_port_val == ACP_DEV_PORT_PDM2) + acp_data->pdm_sel = ACP7X_PDM_DMIC1; + else + dev_warn(sdev->dev, + "acp-audio-ep-port: unrecognized value %u\n", + ep_port_val); + } + fwnode_handle_put(child); +} + #if IS_ENABLED(CONFIG_SND_SOC_SOF_AMD_SOUNDWIRE) static int acp_sof_scan_sdw_devices(struct snd_sof_dev *sdev, u64 addr) { @@ -1070,6 +1095,7 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) const struct sof_amd_acp_desc *chip; const union acpi_object *obj; struct acpi_device *adev; + struct acpi_device *pdm_dev; unsigned int addr; unsigned int irqflags; int ret; @@ -1123,6 +1149,11 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) } if (adev) { + /* DMIC ACPI child address is 2 on ACP7x platforms */ + pdm_dev = acpi_find_child_device(adev, ACP7X_DMIC_ADDR, 0); + if (pdm_dev) + acp_sof_scan_pdm_devices(sdev, pdm_dev); + if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ACPI_TYPE_INTEGER, &obj)) adata->acp_sof_signed_firmware_image = obj->integer.value; diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 16d66b2eaa70..2326b9d2e4c6 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -125,6 +125,14 @@ #define ACP_SRAM_PAGE_COUNT 128 #define ACP6X_SDW_MAX_MANAGER_COUNT 2 #define ACP70_SDW_MAX_MANAGER_COUNT ACP6X_SDW_MAX_MANAGER_COUNT +/* ACPI _DSD acp-audio-ep-port values for PDM controller selection */ +#define ACP_DEV_PORT_PDM 4 +#define ACP_DEV_PORT_PDM2 5 +/* ACPI child device address for the ACP7x PDM/DMIC device */ +#define ACP7X_DMIC_ADDR 2 +/* ACP7X PDM controller selection values for acp_dev_data.pdm_sel; 0 = not set */ +#define ACP7X_PDM_DMIC0 1 +#define ACP7X_PDM_DMIC1 2 #define ACP_DSP_MSG_SET 1 #define ACP_DSP_ACK_SET 1 @@ -279,6 +287,8 @@ struct acp_dev_data { bool acp70_sdw0_wake_event; /* acp70_sdw1_wake_event flag set to true when wake irq asserted for SW1 instance */ bool acp70_sdw1_wake_event; + /* PDM controller index selected from ACPI acp-audio-ep-port; passed to machine driver */ + unsigned int pdm_sel; unsigned int pci_rev; int acp_sof_signed_firmware_image; }; From 59c03f40a53e697d9f29359fbf39834a7e15cc5b Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:05 +0530 Subject: [PATCH 231/924] ASoC: SOF: amd: update SoundWire specific acp descriptor fields for ACP7.B/7.F Populate the SoundWire descriptor fields in acp7x_chip_info so the SOF core can locate and enumerate the four SoundWire managers on ACP7.B/7.F platforms. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-6-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.h | 3 +++ sound/soc/sof/amd/pci-acp7x.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 2326b9d2e4c6..47e475de8b4d 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -128,6 +128,9 @@ /* ACPI _DSD acp-audio-ep-port values for PDM controller selection */ #define ACP_DEV_PORT_PDM 4 #define ACP_DEV_PORT_PDM2 5 +#define SDW_ACPI_ADDR_ACP7X SDW_ACPI_ADDR_ACP63 +#define ACP7X_SDW_MAX_MANAGER_COUNT 4 + /* ACPI child device address for the ACP7x PDM/DMIC device */ #define ACP7X_DMIC_ADDR 2 /* ACP7X PDM controller selection values for acp_dev_data.pdm_sel; 0 = not set */ diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 532e15313795..0594c1f3deb6 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -40,6 +40,8 @@ static const struct sof_amd_acp_desc acp7x_chip_info = { .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, .reg_start_addr = ACP7X_REG_START, + .sdw_max_link_count = ACP7X_SDW_MAX_MANAGER_COUNT, + .sdw_acpi_dev_addr = SDW_ACPI_ADDR_ACP7X, .reg_end_addr = ACP7X_REG_END, }; From 612684a9d2afd1a49017282b2666fb919dbc4f4c Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:06 +0530 Subject: [PATCH 232/924] ASoC: SOF: amd: enable SoundWire build for ACP7.B/7.F Select SND_SOC_SOF_AMD_SOUNDWIRE_LINK_BASELINE in Kconfig for SND_SOC_SOF_AMD_ACP7X so that the SoundWire stack is built when ACP7.B/7.F SOF support is enabled. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-7-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sof/amd/Kconfig b/sound/soc/sof/amd/Kconfig index 903e7ec3b3ba..d64f682cfbea 100644 --- a/sound/soc/sof/amd/Kconfig +++ b/sound/soc/sof/amd/Kconfig @@ -109,6 +109,7 @@ config SND_SOC_SOF_AMD_ACP7X depends on SND_SOC_SOF_PCI depends on AMD_NODE select SND_SOC_SOF_AMD_COMMON + select SND_SOC_SOF_AMD_SOUNDWIRE_LINK_BASELINE help Select this option for SOF support on AMD ACP7.B and ACP7.F PCI revision based platforms. From 750176135630f1eda6e946ecb4155c2b7afa199b Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:07 +0530 Subject: [PATCH 233/924] ASoC: SOF: amd: wire SoundWire probe and remove into ACP7.B/7.F paths In amd_sof_acp7x_probe(), scan DSDT for SoundWire peripherals via acp_sof_scan_sdw_devices() and call amd_sof_sdw_probe() on success. In the error unwind, call free_irq() before amd_sof_sdw_exit() so the IRQ cannot fire after the SoundWire context is freed. In amd_sof_acp7x_remove(), call amd_sof_sdw_exit() after free_irq() to prevent a use-after-free: the IRQ handler dereferences pdev[] entries freed by amd_sof_sdw_exit(). Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-8-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 37909f2d86a4..a900dbc1de20 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1148,6 +1148,18 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) goto unregister_dev; } + /* scan SoundWire capabilities exposed by DSDT */ + ret = acp_sof_scan_sdw_devices(sdev, chip->sdw_acpi_dev_addr); + if (ret < 0) { + dev_dbg(sdev->dev, "skipping SoundWire, not detected with ACPI scan\n"); + goto skip_soundwire; + } + ret = amd_sof_sdw_probe(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: SoundWire probe error\n"); + goto free_ipc_irq; + } +skip_soundwire: if (adev) { /* DMIC ACPI child address is 2 on ACP7x platforms */ pdm_dev = acpi_find_child_device(adev, ACP7X_DMIC_ADDR, 0); @@ -1191,6 +1203,8 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) free_ipc_irq: free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); unregister_dev: platform_device_unregister(adata->dmic_dev); return ret; @@ -1204,6 +1218,9 @@ void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) if (sdev->ipc_irq) free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); + if (adata->dmic_dev) platform_device_unregister(adata->dmic_dev); From db294afbd61aabd857fce39372a1bd1c8b173441 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:08 +0530 Subject: [PATCH 234/924] ASoC: SOF: amd: add ACP7.B/7.F clock-stop detection in check_acp_sdw_enable_status Extend check_acp_sdw_enable_status() with a pci_rev switch so that ACP7.B/7.F, which has 4 SoundWire manager instances, reads all four SW_EN registers to determine whether any manager is in clock-stop mode. The existing two-manager read (ACP_SW0_EN, ACP_SW1_EN) is preserved for ACP63/ACP70/ACP71/ACP72 platforms. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-9-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp-dsp-offset.h | 2 ++ sound/soc/sof/amd/acp.c | 23 +++++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h index bea1bd3afa70..74abbc834d71 100644 --- a/sound/soc/sof/amd/acp-dsp-offset.h +++ b/sound/soc/sof/amd/acp-dsp-offset.h @@ -156,5 +156,7 @@ #define ACP7X_IDMA_ERROR_MASK 0x1FF9FF #define ACP7X_ZSC_DSP_CTRL 0x001014 #define ACP7X_PME_EN ACP70_PME_EN +/* SW enable base for SDW0; manager N uses ACP7X_SW_EN + (N * 0x2000) */ +#define ACP7X_SW_EN 0x5200 #endif diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index a900dbc1de20..a4ca4f78e106 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -779,15 +779,30 @@ static int acp_init(struct snd_sof_dev *sdev) static bool check_acp_sdw_enable_status(struct snd_sof_dev *sdev) { struct acp_dev_data *acp_data; - u32 sdw0_en, sdw1_en; + u32 sdw0_en, sdw1_en, sdw2_en, sdw3_en; acp_data = sdev->pdata->hw_pdata; if (!acp_data->sdw) return false; - sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW0_EN); - sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW1_EN); - acp_data->sdw_en_stat = sdw0_en || sdw1_en; + switch (acp_data->pci_rev) { + case ACP63_PCI_ID: + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: + sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW0_EN); + sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW1_EN); + acp_data->sdw_en_stat = sdw0_en || sdw1_en; + break; + case ACP7B_PCI_ID: + case ACP7F_PCI_ID: + sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (0 * 0x2000)); + sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (1 * 0x2000)); + sdw2_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (2 * 0x2000)); + sdw3_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (3 * 0x2000)); + acp_data->sdw_en_stat = sdw0_en || sdw1_en || sdw2_en || sdw3_en; + break; + } return acp_data->sdw_en_stat; } From c9b000c028792c313861b123c508b3dc7f7114ab Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:09 +0530 Subject: [PATCH 235/924] ASoC: SOF: amd: add ACP7.B/7.F SoundWire IO IRQ handling ACP7.B/7.F exposes four SoundWire managers (SDW0-SDW3), each with dedicated interrupt, wake-enable, PME status and error registers. Add acp7x_irq_handler() to dispatch: - Per-manager data IRQs via sof_acp7x_handle_sdw_manager_irq() - Host-wake and PME events via sof_amd_check_and_handle_acp7x_sdw_wake_irq() which checks both sources independently per manager so simultaneous host-wake and PME events are not missed - Error conditions via sof_amd_acp7x_clear_sdw_err_regs() which writes back the updated error status to clear only the affected manager bit Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-10-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp-dsp-offset.h | 15 +++ sound/soc/sof/amd/acp.c | 187 ++++++++++++++++++++++++++++- sound/soc/sof/amd/acp.h | 2 + sound/soc/sof/amd/acp7x.h | 37 ++++++ 4 files changed, 235 insertions(+), 6 deletions(-) create mode 100644 sound/soc/sof/amd/acp7x.h diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h index 74abbc834d71..3984cd627db6 100644 --- a/sound/soc/sof/amd/acp-dsp-offset.h +++ b/sound/soc/sof/amd/acp-dsp-offset.h @@ -159,4 +159,19 @@ /* SW enable base for SDW0; manager N uses ACP7X_SW_EN + (N * 0x2000) */ #define ACP7X_SW_EN 0x5200 +/* ACP7X SoundWire IO registers (non-gsync, up to 4 managers SW0-SW3) */ +#define ACP7X_EXTERNAL_SW_INTR_STAT 0x001A24 +#define ACP7X_SW_WAKE_EN 0x001458 +#define ACP7X_SW_PME_STS 0x001474 +#define ACP7X_SDW_STAT BIT(23) +#define ACP7X_ERROR_IRQ BIT(29) +#define ACP7X_SW_WAKE_EN_MASK BIT(0) +#define ACP7X_SW_HOST_WAKE_MASK BIT(22) +#define ACP7X_SDW_HOST_WAKE_STAT BIT(24) +#define ACP7X_SW_FIFO_ERROR_REASON 0x50C4 +#define ACP7X_SW_ERROR_REASON1 0x50CC +#define ACP7X_SW_ERROR_REASON2 0x50D4 +#define ACP7X_EXTERNAL_SDW_STAT BIT(16) +#define ACP7X_SW_ERR_STAT_MASK BIT(11) + #endif diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index a4ca4f78e106..eff86b1cee3c 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -22,11 +22,20 @@ #include "../ops.h" #include "acp.h" #include "acp-dsp-offset.h" +#include "../../amd/acp/soc_amd_sdw_common.h" +#include "acp7x.h" static bool enable_fw_debug; module_param(enable_fw_debug, bool, 0444); MODULE_PARM_DESC(enable_fw_debug, "Enable Firmware debug"); +static const u32 acp7x_sof_sdw_ext_stat[ACP7X_SDW_MAX_MANAGER_COUNT] = { + ACP7X_SDW_STAT << 0, + ACP7X_SDW_STAT << 1, + ACP7X_SDW_STAT << 2, + ACP7X_SDW_STAT << 3, +}; + static struct acp_quirk_entry quirk_valve_galileo = { .signed_fw_image = true, .skip_iram_dram_size_mod = true, @@ -581,30 +590,196 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) return IRQ_NONE; } +/* ACP7X SoundWire IO data tables */ + +const struct sof_amd_acp7x_sdw_err_regs acp7x_sdw_err_regs[ACP7X_SDW_MAX_MANAGER_COUNT] = { + { ACP7X_SW_ERR_STAT_MASK << 0, + ACP7X_SW_FIFO_ERROR_REASON + (0 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (0 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (0 * 0x2000) }, + { ACP7X_SW_ERR_STAT_MASK << 1, + ACP7X_SW_FIFO_ERROR_REASON + (1 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (1 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (1 * 0x2000) }, + { ACP7X_SW_ERR_STAT_MASK << 2, + ACP7X_SW_FIFO_ERROR_REASON + (2 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (2 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (2 * 0x2000) }, + { ACP7X_SW_ERR_STAT_MASK << 3, + ACP7X_SW_FIFO_ERROR_REASON + (3 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (3 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (3 * 0x2000) }, +}; + +const struct acp7x_sdw_wake_src acp7x_sdw_wake_sources[] = { + { ACP7X_SDW_HOST_WAKE_STAT << 0, ACP7X_SW_PME_STS + (0 * 4), + ACP7X_SW_WAKE_EN_MASK << 0, 0 }, + { ACP7X_SDW_HOST_WAKE_STAT << 1, ACP7X_SW_PME_STS + (1 * 4), + ACP7X_SW_WAKE_EN_MASK << 1, 1 }, + { ACP7X_SDW_HOST_WAKE_STAT << 2, ACP7X_SW_PME_STS + (2 * 4), + ACP7X_SW_WAKE_EN_MASK << 2, 2 }, + { ACP7X_SDW_HOST_WAKE_STAT << 3, ACP7X_SW_PME_STS + (3 * 4), + ACP7X_SW_WAKE_EN_MASK << 3, 3 }, +}; + +/* ACP7X SoundWire IO interrupt and wake helpers */ + +static void sof_amd_acp7x_handle_one_sdw_err(struct snd_sof_dev *sdev, u32 *err_stat, + u32 acp_error_stat_reg, + const struct sof_amd_acp7x_sdw_err_regs *regs) +{ + if (!(*err_stat & regs->err_stat_mask)) + return; + + *err_stat &= ~regs->err_stat_mask; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, acp_error_stat_reg, *err_stat); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, regs->fifo_err_reason, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, regs->err_reason1, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, regs->err_reason2, 0); +} + +/* Clears SoundWire error registers for all managers; runs in hard IRQ context. */ +static void sof_amd_acp7x_clear_sdw_err_regs(struct snd_sof_dev *sdev) +{ + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u32 err_stat; + unsigned int instance; + + err_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->acp_error_stat); + + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) + sof_amd_acp7x_handle_one_sdw_err(sdev, &err_stat, desc->acp_error_stat, + &acp7x_sdw_err_regs[instance]); +} + +static void sof_amd_handle_acp7x_sdw_wake_event(struct acp_dev_data *adata) +{ + struct amd_sdw_manager *amd_manager; + unsigned int instance; + + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) { + if (!adata->acp7x_sdw_wake_event[instance]) + continue; + if (!adata->sdw->pdev[instance]) + continue; + + amd_manager = dev_get_drvdata(&adata->sdw->pdev[instance]->dev); + if (amd_manager) + pm_request_resume(amd_manager->dev); + adata->acp7x_sdw_wake_event[instance] = false; + } +} + +static int sof_amd_check_and_handle_acp7x_sdw_wake_irq(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + unsigned int ext_intr_stat1; + unsigned int sdw_pme_stat, sdw_wake_en; + unsigned int i; + bool sdw_wake_irq = false; + + ext_intr_stat1 = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat1); + + for (i = 0; i < ARRAY_SIZE(acp7x_sdw_wake_sources); i++) { + const struct acp7x_sdw_wake_src *src = &acp7x_sdw_wake_sources[i]; + + bool woke = false; + + if (ext_intr_stat1 & src->host_stat_mask) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat1, + src->host_stat_mask); + woke = true; + } + + sdw_pme_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, src->pme_sts_reg); + if (sdw_pme_stat) { + sdw_wake_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN); + sdw_wake_en &= ~src->wake_en_mask; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN, sdw_wake_en); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, src->pme_sts_reg, sdw_pme_stat); + woke = true; + } + + if (woke) { + adata->acp7x_sdw_wake_event[src->instance] = true; + sdw_wake_irq = true; + } + } + + if (sdw_wake_irq) { + sof_amd_handle_acp7x_sdw_wake_event(adata); + return WAKE_IRQ_HANDLED; + } + return WAKE_IRQ_NONE; +} + +static void sof_acp7x_handle_sdw_manager_irq(struct snd_sof_dev *sdev, + struct acp_dev_data *adata, + const struct sof_amd_acp_desc *desc, + unsigned int instance, + u32 ext_stat_mask) +{ + struct amd_sdw_manager *amd_manager; + unsigned int sdw_intr_stat; + u32 sw_intr_reg = ACP7X_EXTERNAL_SW_INTR_STAT + (instance * 4); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ext_stat_mask); + sdw_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, sw_intr_reg); + if (sdw_intr_stat & ACP7X_EXTERNAL_SDW_STAT) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, sw_intr_reg, ACP7X_EXTERNAL_SDW_STAT); + if (!adata->sdw->pdev[instance]) + return; + amd_manager = dev_get_drvdata(&adata->sdw->pdev[instance]->dev); + if (amd_manager) + schedule_work(&amd_manager->amd_sdw_irq_thread); + } +} + static irqreturn_t acp7x_irq_handler(int irq, void *dev_id) { struct snd_sof_dev *sdev = dev_id; + struct acp_dev_data *adata = sdev->pdata->hw_pdata; const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); unsigned int base = desc->dsp_intr_base; unsigned int val; unsigned int ext_intr_stat; - int irq_flag = 0; + unsigned int instance; + int irq_flag = 0, wake_irq_flag = 0; + bool dsp_irq = false; val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET); if (val & ACP_DSP_TO_HOST_IRQ) { snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET, ACP_DSP_TO_HOST_IRQ); - return IRQ_WAKE_THREAD; + dsp_irq = true; } ext_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat); - if (ext_intr_stat & ACP_ERROR_IRQ_MASK) { - snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); - snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); + + if (adata->sdw) { + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) { + if (ext_intr_stat & acp7x_sof_sdw_ext_stat[instance]) { + sof_acp7x_handle_sdw_manager_irq(sdev, adata, desc, instance, + acp7x_sof_sdw_ext_stat[instance]); + irq_flag = 1; + } + } + } + + if (adata->sdw) + wake_irq_flag = sof_amd_check_and_handle_acp7x_sdw_wake_irq(sdev); + + if (ext_intr_stat & ACP7X_ERROR_IRQ) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP7X_ERROR_IRQ); + sof_amd_acp7x_clear_sdw_err_regs(sdev); irq_flag = 1; } - if (irq_flag) + if (dsp_irq) + return IRQ_WAKE_THREAD; + + if (irq_flag || wake_irq_flag) return IRQ_HANDLED; return IRQ_NONE; diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 47e475de8b4d..e3c85b5cb20c 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -290,6 +290,8 @@ struct acp_dev_data { bool acp70_sdw0_wake_event; /* acp70_sdw1_wake_event flag set to true when wake irq asserted for SW1 instance */ bool acp70_sdw1_wake_event; + /* per-manager wake event flags; indexed by SoundWire manager instance (0-3) */ + bool acp7x_sdw_wake_event[ACP7X_SDW_MAX_MANAGER_COUNT]; /* PDM controller index selected from ACPI acp-audio-ep-port; passed to machine driver */ unsigned int pdm_sel; unsigned int pci_rev; diff --git a/sound/soc/sof/amd/acp7x.h b/sound/soc/sof/amd/acp7x.h new file mode 100644 index 000000000000..5151d8757c12 --- /dev/null +++ b/sound/soc/sof/amd/acp7x.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2026 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Vijendar Mukunda + */ + +#ifndef __SOF_AMD_ACP7X_H +#define __SOF_AMD_ACP7X_H + +/* Return values for sof_amd_check_and_handle_acp7x_sdw_wake_irq() */ +#define WAKE_IRQ_HANDLED 1 +#define WAKE_IRQ_NONE 0 + +/* ACP7X SoundWire IO structures */ + +struct sof_amd_acp7x_sdw_err_regs { + u32 err_stat_mask; + u32 fifo_err_reason; + u32 err_reason1; + u32 err_reason2; +}; + +struct acp7x_sdw_wake_src { + u32 host_stat_mask; + u32 pme_sts_reg; + u32 wake_en_mask; + u8 instance; +}; + +extern const struct sof_amd_acp7x_sdw_err_regs acp7x_sdw_err_regs[ACP7X_SDW_MAX_MANAGER_COUNT]; +extern const struct acp7x_sdw_wake_src acp7x_sdw_wake_sources[]; + +#endif /* __SOF_AMD_ACP7X_H */ From b99242b8d4b34a95f844e5b9e6e02a98fcaddc0d Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:10 +0530 Subject: [PATCH 236/924] ASoC: SOF: amd: enable SoundWire host wake interrupt in acp_init During acp_init() for ACP7.B/7.F, read ACP7X_SW_WAKE_EN and enable the corresponding host-wake interrupt mask in ACP7X_EXTERNAL_INTR_CNTL1 only for managers that have wake-enable set (SW_WAKE_EN bit i enables ACP7X_SW_HOST_WAKE_MASK << i in INTR_CNTL1). This ensures only the active managers arm their host-wake interrupt when ACP enters D0. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-11-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index eff86b1cee3c..da987a51491c 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -903,6 +903,8 @@ static int acp_init(struct snd_sof_dev *sdev) const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); struct acp_dev_data *acp_data; unsigned int sdw0_wake_en, sdw1_wake_en; + u32 sdw_wake_en, intr_mask; + unsigned int i; int ret; /* power on */ @@ -946,6 +948,20 @@ static int acp_init(struct snd_sof_dev *sdev) snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_DSP0_IDMA_ERROR_MASK, ACP7X_IDMA_ERROR_MASK); + /* + * Enable host-wake interrupt per manager based on SW_WAKE_EN: + * SW_WAKE_EN bit i enables ACP7X_SW_HOST_WAKE_MASK << i in INTR_CNTL1. + */ + sdw_wake_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN); + intr_mask = 0; + for (i = 0; i < ACP7X_SDW_MAX_MANAGER_COUNT; i++) { + if (sdw_wake_en & BIT(i)) + intr_mask |= ACP7X_SW_HOST_WAKE_MASK << i; + } + if (intr_mask) + snd_sof_dsp_update_bits(sdev, ACP_DSP_BAR, + ACP7X_EXTERNAL_INTR_CNTL1, + intr_mask, intr_mask); break; } return 0; From be2823854e00558f46f5bbb124bb3f3a3bb9a94a Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:11 +0530 Subject: [PATCH 237/924] ASoC: SOF: amd: add SoundWire PM ops for ACP7.B/7.F Add handle_amd_sof_acp7x_sdw_pme_event() to iterate over all four SoundWire managers on runtime PM resume, clear PME status and wake-enable bits (under acp_lock to prevent races with the IRQ path), and request runtime resume for each active manager. Extend amd_sof_acp7x_suspend() with a clock-stop fast path: when SoundWire managers are in clock-stop mode, write ZSC_DSP_CTRL=1 and call acp_dsp_reset() instead of the full acp_reset() to preserve SoundWire bus state. Extend amd_sof_acp7x_resume() with a matching clock-stop resume path that restores ZSC_DSP_CTRL and re-arms PME_EN. Introduce amd_sof_acp7x_suspend_runtime() and amd_sof_acp7x_resume_runtime() as separate runtime PM callbacks. The runtime resume path calls handle_amd_sof_acp7x_sdw_pme_event() after re-initialising hardware to clear any PME state that arrived during the suspend window. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-12-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 106 +++++++++++++++++++++++++++++++++------- sound/soc/sof/amd/acp.h | 1 + 2 files changed, 88 insertions(+), 19 deletions(-) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index da987a51491c..b059039c9e0d 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1365,6 +1365,9 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) dev_err(sdev->dev, "error: SoundWire probe error\n"); goto free_ipc_irq; } + if (adata->info.link_mask) + adata->is_sdw_dev = true; + skip_soundwire: if (adev) { /* DMIC ACPI child address is 2 on ACP7x platforms */ @@ -1434,6 +1437,44 @@ void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) } EXPORT_SYMBOL_NS(amd_sof_acp7x_remove, "SND_SOC_SOF_AMD_COMMON"); +static void handle_amd_sof_acp7x_sdw_pme_event(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata; + struct amd_sdw_manager *amd_manager; + u32 sdw_pme_stat; + u32 sdw_wake_en; + u32 pme_reg; + u32 wake_mask; + unsigned int instance; + + adata = sdev->pdata->hw_pdata; + if (!adata->sdw) + return; + + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) { + pme_reg = ACP7X_SW_PME_STS + (instance * 4); + wake_mask = ACP7X_SW_WAKE_EN_MASK << instance; + + sdw_pme_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, pme_reg); + if (!sdw_pme_stat) + continue; + + mutex_lock(&adata->acp_lock); + sdw_wake_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN); + sdw_wake_en &= ~wake_mask; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN, sdw_wake_en); + mutex_unlock(&adata->acp_lock); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pme_reg, sdw_pme_stat); + + if (!adata->sdw->pdev[instance]) + continue; + + amd_manager = dev_get_drvdata(&adata->sdw->pdev[instance]->dev); + if (amd_manager) + pm_request_resume(amd_manager->dev); + } +} + int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) { struct acp_dev_data *acp_data; @@ -1442,6 +1483,11 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) acp_data = sdev->pdata->hw_pdata; + if (acp_data->is_sdw_dev && check_acp_sdw_enable_status(sdev)) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); + return acp_dsp_reset(sdev); + } + ret = acp_reset(sdev); if (ret) { dev_err(sdev->dev, "ACP Reset failed\n"); @@ -1463,13 +1509,25 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) } EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend, "SND_SOC_SOF_AMD_COMMON"); -int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) +int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) +{ + return amd_sof_acp7x_suspend(sdev, 0); +} +EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); + +int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) { struct acp_dev_data *acp_data; int ret; acp_data = sdev->pdata->hw_pdata; + if (acp_data->sdw_en_stat) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); + return acp_dsp_reset(sdev); + } + ret = acp_init(sdev); if (ret) { dev_err(sdev->dev, "ACP Init failed\n"); @@ -1481,31 +1539,41 @@ int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) return ret; } - switch (acp_data->pci_rev) { - case ACP7B_PCI_ID: - case ACP7F_PCI_ID: + if (acp_data->is_sdw_dev) + handle_amd_sof_acp7x_sdw_pme_event(sdev); + + return 0; +} +EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); + +int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *acp_data; + int ret; + + acp_data = sdev->pdata->hw_pdata; + + if (acp_data->sdw_en_stat) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); - break; - default: - break; + return acp_dsp_reset(sdev); + } + + ret = acp_init(sdev); + if (ret) { + dev_err(sdev->dev, "ACP Init failed\n"); + return ret; + } + ret = acp_memory_init(sdev); + if (ret) { + dev_err(sdev->dev, "ACP Memory init failed\n"); + return ret; } return 0; } EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); -int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) -{ - return amd_sof_acp7x_suspend(sdev, 0); -} -EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); - -int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) -{ - return amd_sof_acp7x_resume(sdev); -} -EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); - MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("AMD ACP sof driver"); MODULE_IMPORT_NS("SOUNDWIRE_AMD_INIT"); diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index e3c85b5cb20c..1bc6b572448d 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -294,6 +294,7 @@ struct acp_dev_data { bool acp7x_sdw_wake_event[ACP7X_SDW_MAX_MANAGER_COUNT]; /* PDM controller index selected from ACPI acp-audio-ep-port; passed to machine driver */ unsigned int pdm_sel; + bool is_sdw_dev; unsigned int pci_rev; int acp_sof_signed_firmware_image; }; From 5a3c23d4aa89cdb7a3f2a42465c2cabcb71ebcdd Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:08 +0700 Subject: [PATCH 238/924] ASoC: mediatek: mt8189: Propagate APLL enable errors mt8189_apll1_enable() and mt8189_apll2_enable() currently ignore errors from regmap_update_bits() and do not clean up resources when clock enable operations fail. Propagate these errors and roll back the clocks and tuner state on errors. Fixes: dc637ffeed6c ("ASoC: mediatek: mt8189: support audio clock control") Signed-off-by: bui duc phuc Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260916050020.14575-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-clk.c | 78 ++++++++++++++++------ 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c index fc7a7a73b0cf..f088ccf80448 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c @@ -454,30 +454,47 @@ int mt8189_apll1_enable(struct mtk_base_afe *afe) ret = mt8189_afe_enable_top_cg(afe, MT8189_CG_APLL1_CK); if (ret) - return ret; + goto err_clear_mux_setting; ret = mt8189_afe_enable_top_cg(afe, MT8189_PDN_APLL_TUNER1); if (ret) - return ret; + goto err_disable_apll1_ck; /* sel 44.1kHz:1, apll_div:7, upper bound:3 */ - regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, - XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | - UPPER_BOUND_MASK_SFT, - (0x1 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) | - (3 << UPPER_BOUND_SFT)); + ret = regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, + XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | + UPPER_BOUND_MASK_SFT, + (0x1 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) | + (3 << UPPER_BOUND_SFT)); + if (ret) + goto err_disable_apll_tuner1; /* apll1 freq tuner enable */ - regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, - FREQ_TUNER_EN_MASK_SFT, - 0x1 << FREQ_TUNER_EN_SFT); + ret = regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x1 << FREQ_TUNER_EN_SFT); + if (ret) + goto err_disable_apll_tuner1; /* audio apll1 on */ ret = mt8189_afe_enable_top_cg(afe, MT8189_AUDIO_APLL1_EN_ON); if (ret) - return ret; + goto err_clear_freq_tuner_en; return 0; + +err_clear_freq_tuner_en: + regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x0); +err_disable_apll_tuner1: + mt8189_afe_disable_top_cg(afe, MT8189_PDN_APLL_TUNER1); +err_disable_apll1_ck: + mt8189_afe_disable_top_cg(afe, MT8189_CG_APLL1_CK); +err_clear_mux_setting: + apll1_mux_setting(afe, false); + + return ret; } void mt8189_apll1_disable(struct mtk_base_afe *afe) @@ -506,30 +523,47 @@ int mt8189_apll2_enable(struct mtk_base_afe *afe) ret = mt8189_afe_enable_top_cg(afe, MT8189_CG_APLL2_CK); if (ret) - return ret; + goto err_clear_mux_setting; ret = mt8189_afe_enable_top_cg(afe, MT8189_PDN_APLL_TUNER2); if (ret) - return ret; + goto err_disable_apll2_ck; /* sel 48kHz: 2, apll_div: 7, upper bound: 3*/ - regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, - XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | - UPPER_BOUND_MASK_SFT, - (0x2 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) | - (3 << UPPER_BOUND_SFT)); + ret = regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, + XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | + UPPER_BOUND_MASK_SFT, + (0x2 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) | + (3 << UPPER_BOUND_SFT)); + if (ret) + goto err_disable_apll_tuner2; /* apll2 freq tuner enable */ - regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, - FREQ_TUNER_EN_MASK_SFT, - 0x1 << FREQ_TUNER_EN_SFT); + ret = regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x1 << FREQ_TUNER_EN_SFT); + if (ret) + goto err_disable_apll_tuner2; /* audio apll2 on */ ret = mt8189_afe_enable_top_cg(afe, MT8189_AUDIO_APLL2_EN_ON); if (ret) - return ret; + goto err_clear_freq_tuner_en; return 0; + +err_clear_freq_tuner_en: + regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x0); +err_disable_apll_tuner2: + mt8189_afe_disable_top_cg(afe, MT8189_PDN_APLL_TUNER2); +err_disable_apll2_ck: + mt8189_afe_disable_top_cg(afe, MT8189_CG_APLL2_CK); +err_clear_mux_setting: + apll2_mux_setting(afe, false); + + return ret; } void mt8189_apll2_disable(struct mtk_base_afe *afe) From bd9a8d9def207a8d7eeaa4dda1c53c5b4fca4008 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:09 +0700 Subject: [PATCH 239/924] ASoC: mediatek: mt8189: Propagate MCK enable errors mt8189_mck_enable() currently returns without restoring the clock state when setting the clock parent, enabling the divider, or setting the divider rate fails. Propagate the error and disable clocks enabled by the function. Fixes: dc637ffeed6c ("ASoC: mediatek: mt8189: support audio clock control") Signed-off-by: bui duc phuc Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260916050020.14575-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-clk.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c index f088ccf80448..56362c4756c9 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c @@ -639,7 +639,7 @@ int mt8189_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) ret = mt8189_afe_set_clk_parent(afe, afe_priv->clk[m_sel_id], afe_priv->clk[apll_clk_id]); if (ret) - return ret; + goto err_disable_m_sel_clk; } /* enable div, set rate */ @@ -650,13 +650,21 @@ int mt8189_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) ret = mt8189_afe_enable_clk(afe, afe_priv->clk[div_clk_id]); if (ret) - return ret; + goto err_disable_m_sel_clk; ret = mt8189_afe_set_clk_rate(afe, afe_priv->clk[div_clk_id], rate); if (ret) - return ret; + goto err_disable_div_clk; return 0; + +err_disable_div_clk: + mt8189_afe_disable_clk(afe, afe_priv->clk[div_clk_id]); +err_disable_m_sel_clk: + if (m_sel_id >= 0) + mt8189_afe_disable_clk(afe, afe_priv->clk[m_sel_id]); + + return ret; } int mt8189_mck_disable(struct mtk_base_afe *afe, int mck_id) From b6b47040e897f18df1ed53c658ab5159b035b5d1 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:10 +0700 Subject: [PATCH 240/924] ASoC: mediatek: mt8189: Validate MCK ID mt8189_mck_disable() only checks for negative MCK IDs. Reject IDs outside the valid MCK range as well. Fixes: dc637ffeed6c ("ASoC: mediatek: mt8189: support audio clock control") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-clk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c index 56362c4756c9..7fe9d01006d5 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c @@ -675,8 +675,8 @@ int mt8189_mck_disable(struct mtk_base_afe *afe, int mck_id) dev_dbg(afe->dev, "mck_id: %d.\n", mck_id); - if (mck_id < 0) { - dev_err(afe->dev, "mck_id = %d < 0\n", mck_id); + if (mck_id >= MT8189_MCK_NUM || mck_id < 0) { + dev_err(afe->dev, "mck_id = %d\n", mck_id); return -EINVAL; } From 29de6666879997c9dc375344fd046363d0c3bcdb Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:11 +0700 Subject: [PATCH 241/924] ASoC: mediatek: mt8189: Propagate reg_rw clock errors mt8189_afe_enable_reg_rw_clk() currently ignores errors from clock enable and parent operations. Propagate these errors and clean up the clocks before returning the error. Fixes: dc637ffeed6c ("ASoC: mediatek: mt8189: support audio clock control") Signed-off-by: bui duc phuc Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260916050020.14575-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-clk.c | 31 +++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c index 7fe9d01006d5..1132c924158d 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c @@ -700,17 +700,36 @@ int mt8189_mck_disable(struct mtk_base_afe *afe, int mck_id) int mt8189_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) { struct mt8189_afe_private *afe_priv = afe->platform_priv; + int ret; /* bus clock for AFE internal access, like AFE SRAM */ - mt8189_afe_enable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS]); - mt8189_afe_set_clk_parent(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS], - afe_priv->clk[MT8189_CLK_TOP_CLK26M]); + ret = mt8189_afe_enable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS]); + if (ret) + return ret; + + ret = mt8189_afe_set_clk_parent(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS], + afe_priv->clk[MT8189_CLK_TOP_CLK26M]); + if (ret) + goto err_disable_audiointbus_clk; + /* enable audio clock source */ - mt8189_afe_enable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H]); - mt8189_afe_set_clk_parent(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H], - afe_priv->clk[MT8189_CLK_TOP_CLK26M]); + ret = mt8189_afe_enable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H]); + if (ret) + goto err_disable_audiointbus_clk; + + ret = mt8189_afe_set_clk_parent(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H], + afe_priv->clk[MT8189_CLK_TOP_CLK26M]); + if (ret) + goto err_disable_audio_h_clk; return 0; + +err_disable_audio_h_clk: + mt8189_afe_disable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H]); +err_disable_audiointbus_clk: + mt8189_afe_disable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS]); + + return ret; } int mt8189_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) From ea8c96a05da7b7ba6234a225209ac0c2dcc57e41 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:12 +0700 Subject: [PATCH 242/924] ASoC: mediatek: mt8189: Use dev_err_probe() for clock errors Use dev_err_probe() when obtaining clocks to avoid redundant error messages, particularly for probe deferral. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-clk.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c index 1132c924158d..594c214fa697 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c @@ -793,10 +793,9 @@ int mt8189_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT8189_CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "devm_clk_get %s fail\n", aud_clks[i]); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } ret = mt8189_afe_disable_apll(afe); From 483e827b005f122755785a3b6094e2bc19eb7685 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:13 +0700 Subject: [PATCH 243/924] ASoC: mediatek: mt8189: Propagate runtime resume errors mt8189_afe_runtime_resume() currently ignores errors from regmap operations and mt8189_afe_enable_main_clock(). Propagate these errors and clean up the state before returning the error. Fixes: 7eb153585598 ("ASoC: mediatek: mt8189: add platform driver") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-pcm.c | 32 ++++++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c index 77cf2b604f6c..39cc2804ad4c 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c @@ -2332,20 +2332,40 @@ static int mt8189_afe_runtime_resume(struct device *dev) } regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) + goto err_set_cache_only; /* set audio 26M request */ - regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x1); - regmap_update_bits(afe->regmap, AFE_CBIP_CFG0, 0x1, 0x1); + ret = regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x1); + if (ret) + goto err_set_cache_only; + + ret = regmap_update_bits(afe->regmap, AFE_CBIP_CFG0, 0x1, 0x1); + if (ret) + goto err_clear_26m_req; /* force cpu use 8_24 format when writing 32bit data */ - regmap_update_bits(afe->regmap, AFE_MEMIF_CON0, - CPU_HD_ALIGN_MASK_SFT, 0 << CPU_HD_ALIGN_SFT); + ret = regmap_update_bits(afe->regmap, AFE_MEMIF_CON0, + CPU_HD_ALIGN_MASK_SFT, 0 << CPU_HD_ALIGN_SFT); + if (ret) + goto err_clear_26m_req; /* enable AFE */ - mt8189_afe_enable_main_clock(afe); + ret = mt8189_afe_enable_main_clock(afe); + if (ret) + goto err_clear_26m_req; return 0; + +err_clear_26m_req: + regmap_update_bits(afe->regmap, + AFE_SPM_CONTROL_REQ, 0x1, 0x0); +err_set_cache_only: + regcache_cache_only(afe->regmap, true); + mt8189_afe_disable_reg_rw_clk(afe); + + return ret; } static int mt8189_afe_component_probe(struct snd_soc_component *component) From e9e9e514eeef7a5c6121ece355f8ea2701e1613f Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:14 +0700 Subject: [PATCH 244/924] ASoC: mediatek: mt8189: Remove redundant error message The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() and dev_err_probe() call is redundant and can be removed. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-pcm.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c index 39cc2804ad4c..11dba86c3e81 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c @@ -2486,13 +2486,12 @@ static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) afe->base_addr = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(afe->base_addr)) - return dev_err_probe(dev, PTR_ERR(afe->base_addr), - "AFE base_addr not found\n"); + return PTR_ERR(afe->base_addr); /* init audio related clock */ ret = mt8189_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "init clock error.\n"); + return ret; /* init memif */ /* IPM2.0 no need banding */ @@ -2526,13 +2525,13 @@ static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); if (irq_id < 0) - return dev_err_probe(dev, irq_id, "no irq found"); + return irq_id; ret = devm_request_irq(dev, irq_id, mt8189_afe_irq_handler, IRQF_TRIGGER_NONE, "Afe_ISR_Handle", afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); @@ -2601,10 +2600,8 @@ static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) &mt8189_afe_component, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_err(dev, "afe component err: %d\n", ret); + if (ret) return ret; - } return 0; From 6347f276da8c6c3e6e8262b30b9cd14dfbc48248 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:15 +0700 Subject: [PATCH 245/924] ASoC: mediatek: mt8189: Propagate APLL errors mtk_apll_event() currently ignores errors returned by the APLL enable functions. Propagate these errors. Fixes: 7eb153585598 ("ASoC: mediatek: mt8189: add platform driver") Signed-off-by: bui duc phuc Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260916050020.14575-9-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-dai-i2s.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c index 94c49a662e2d..4826ee250d70 100644 --- a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c +++ b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c @@ -485,6 +485,7 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", __func__, w->name, event); @@ -492,9 +493,11 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: if (strcmp(w->name, APLL1_W_NAME) == 0) - mt8189_apll1_enable(afe); + ret = mt8189_apll1_enable(afe); else - mt8189_apll2_enable(afe); + ret = mt8189_apll2_enable(afe); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: if (strcmp(w->name, APLL1_W_NAME) == 0) From 65b9d4a783cc266d057a64c1f04f690e9abb1bd3 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:16 +0700 Subject: [PATCH 246/924] ASoC: mediatek: mt8189: Propagate MCLK errors mtk_mclk_en_event() currently ignores errors returned by the MCK enable and disable functions. Propagate these errors. Fixes: 7eb153585598 ("ASoC: mediatek: mt8189: add platform driver") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-10-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-dai-i2s.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c index 4826ee250d70..8cd5ddf62d46 100644 --- a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c +++ b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c @@ -519,6 +519,7 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mtk_afe_i2s_priv *i2s_priv; + int ret; dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", __func__, w->name, event); @@ -529,17 +530,18 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8189_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); + ret = mt8189_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); break; case SND_SOC_DAPM_POST_PMD: i2s_priv->mclk_rate = 0; - mt8189_mck_disable(afe, i2s_priv->mclk_id); + ret = mt8189_mck_disable(afe, i2s_priv->mclk_id); break; default: + ret = 0; break; } - return 0; + return ret; } static const struct snd_soc_dapm_widget mtk_dai_i2s_widgets[] = { From 9c73b5a43c99f905ebeab4744a8ba0b28232b189 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:17 +0700 Subject: [PATCH 247/924] ASoC: mediatek: mt8189: Validate sysclk frequency A zero frequency causes a modulo-by-zero error when validating the I2S clock frequency. Reject it before performing the validation. Fixes: 7eb153585598 ("ASoC: mediatek: mt8189: add platform driver") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-11-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-dai-i2s.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c index 8cd5ddf62d46..c9e80ef42f60 100644 --- a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c +++ b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c @@ -1287,6 +1287,9 @@ static int mtk_dai_i2s_set_sysclk(struct snd_soc_dai *dai, dir != SND_SOC_CLOCK_OUT) return -EINVAL; + if (!freq) + return -EINVAL; + i2s_priv = afe_priv->dai_priv[dai->id]; if (!i2s_priv) return -EINVAL; From 242d0280eabba05c085a51ad0a9c9a7a3afa7b03 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:18 +0700 Subject: [PATCH 248/924] ASoC: mediatek: mt8189: Propagate TDM clock errors mtk_tdm_bck_en_event() and mtk_tdm_mck_en_event() currently ignore errors returned by the MCK enable and disable functions. Propagate these errors. Fixes: 9f202872ba04 ("ASoC: mediatek: mt8189: support TDM in platform driver") Signed-off-by: bui duc phuc Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260916050020.14575-12-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-dai-tdm.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c b/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c index 5d68a55ccc45..9aebd0320614 100644 --- a/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c +++ b/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c @@ -261,22 +261,24 @@ static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, struct mt8189_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d, bck: %d\n", w->name, event, dai_id, tdm_priv->bck_rate); switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8189_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); + ret = mt8189_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); break; case SND_SOC_DAPM_POST_PMD: - mt8189_mck_disable(afe, tdm_priv->bck_id); + ret = mt8189_mck_disable(afe, tdm_priv->bck_id); break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, @@ -288,23 +290,25 @@ static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, struct mt8189_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d, mclk %d\n", w->name, event, dai_id, tdm_priv->mclk_rate); switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8189_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); + ret = mt8189_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); break; case SND_SOC_DAPM_POST_PMD: tdm_priv->mclk_rate = 0; - mt8189_mck_disable(afe, tdm_priv->mclk_id); + ret = mt8189_mck_disable(afe, tdm_priv->mclk_id); break; default: + ret = 0; break; } - return 0; + return ret; } static const struct snd_soc_dapm_widget mtk_dai_tdm_widgets[] = { From 197ec30e1b608ade5d2eb9271d01c59480a6f9a1 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:19 +0700 Subject: [PATCH 249/924] ASoC: mediatek: mt8189: Validate TDM MCLK frequency mtk_dai_tdm_cal_mclk() accepts zero or negative frequencies. Reject invalid frequencies before configuring the TDM clock. Fixes: 9f202872ba04 ("ASoC: mediatek: mt8189: support TDM in platform driver") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-13-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-dai-tdm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c b/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c index 9aebd0320614..e113c52fe06a 100644 --- a/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c +++ b/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c @@ -383,6 +383,9 @@ static int mtk_dai_tdm_cal_mclk(struct mtk_base_afe *afe, int apll; int apll_rate; + if (freq <= 0) + return -EINVAL; + apll = mt8189_get_apll_by_rate(afe, freq); apll_rate = mt8189_get_apll_rate(afe, apll); From 4d4bc656580bcc448f8e6d6b3b3a903120eb29bb Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 14 Sep 2026 12:58:06 -0700 Subject: [PATCH 250/924] docs: sound: hdspm: clean up driver doc. Do some overall document cleanups: - add driver name for clarification - fix some punctuation issues - correct some capitalization - correct some grammar - convert a few words from German to English - eliminate double use of "note:" - unindent Precise Pointer bullets so that they are not part of the Hint - drop precise_ptr documentation since it has been modified to be "always on" Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260914195806.1544938-1-rdunlap@infradead.org Signed-off-by: Takashi Iwai --- Documentation/sound/cards/hdspm.rst | 109 ++++++++++------------------ 1 file changed, 40 insertions(+), 69 deletions(-) diff --git a/Documentation/sound/cards/hdspm.rst b/Documentation/sound/cards/hdspm.rst index 5373e51ed076..deba92267942 100644 --- a/Documentation/sound/cards/hdspm.rst +++ b/Documentation/sound/cards/hdspm.rst @@ -6,9 +6,11 @@ Software Interface ALSA-DSP MADI Driver 2004 - winfried ritsch +driver name - snd-hdspm + Full functionality has been added to the driver. Since some of -the Controls and startup-options are ALSA-Standard and only the +the Controls and startup options are ALSA-Standard, only the special Controls are described and discussed below. @@ -18,7 +20,7 @@ Hardware functionality Audio transmission ------------------ -* number of channels -- depends on transmission mode +* number of channels -- depends on transmission mode The number of channels chosen is from 1..Nmax. The reason to use for a lower number of channels is only resource allocation, @@ -29,29 +31,29 @@ Audio transmission * Single Speed -- 1..64 channels .. note:: - (Note: Choosing the 56channel mode for transmission or as + Choosing the 56-channel mode for transmission or as receiver, only 56 are transmitted/received over the MADI, but all 64 channels are available for the mixer, so channel count - for the driver) + for the driver. * Double Speed -- 1..32 channels .. note:: - Note: Choosing the 56-channel mode for - transmission/receive-mode , only 28 are transmitted/received + Choosing the 56-channel mode for + transmission/receive-mode, only 28 are transmitted/received over the MADI, but all 32 channels are available for the mixer, - so channel count for the driver + so channel count for the driver. * Quad Speed -- 1..16 channels .. note:: Choosing the 56-channel mode for - transmission/receive-mode , only 14 are transmitted/received + transmission/receive-mode, only 14 are transmitted/received over the MADI, but all 16 channels are available for the mixer, - so channel count for the driver + so channel count for the driver. -* Format -- signed 32 Bit Little Endian (SNDRV_PCM_FMTBIT_S32_LE) +* Format -- signed 32-bit Little Endian (SNDRV_PCM_FMTBIT_S32_LE) * Sample Rates -- @@ -67,30 +69,6 @@ Audio transmission * fragments -- 2 -* Hardware-pointer -- 2 Modi - - - The Card supports the readout of the actual Buffer-pointer, - where DMA reads/writes. Since of the bulk mode of PCI it is only - 64 Byte accurate. SO it is not really usable for the - ALSA-mid-level functions (here the buffer-ID gives a better - result), but if MMAP is used by the application. Therefore it - can be configured at load-time with the parameter - precise-pointer. - - -.. hint:: - (Hint: Experimenting I found that the pointer is maximum 64 to - large never to small. So if you subtract 64 you always have a - safe pointer for writing, which is used on this mode inside - ALSA. In theory now you can get now a latency as low as 16 - Samples, which is a quarter of the interrupt possibilities.) - - * Precise Pointer -- off - interrupt used for pointer-calculation - - * Precise Pointer -- on - hardware pointer used. Controller ---------- @@ -104,7 +82,7 @@ hwdep-interface. Also all 128+256 Peak and RMS-Meter can be accessed via the hwdep-interface. Since it could be a performance problem always copying and converting Peak and RMS-Levels even if you just need -one, I decided to export the hardware structure, so that of +one, I decided to export the hardware structure, so that if needed some driver-guru can implement a memory-mapping of mixer or peak-meters over ioctl, or also to do only copying and no conversion. A test-application shows the usage of the controller. @@ -112,9 +90,9 @@ conversion. A test-application shows the usage of the controller. * Latency Controls --- not implemented !!! .. note:: - Note: Within the windows-driver the latency is accessible of a - control-panel, but buffer-sizes are controlled with ALSA from - hwparams-calls and should not be changed in run-state, I did not + Within the Windows driver the latency is accessible via + Control Panel, but buffer sizes are controlled with ALSA from + hwparams calls and should not be changed in run-state. I did not implement it here. @@ -127,12 +105,12 @@ conversion. A test-application shows the usage of the controller. * Values -- "Master" "Slave" .. note:: - !!!! This is a hardware-function but is in conflict with the - Clock-source controller, which is a kind of ALSA-standard. I + !!!! This is a hardware function but is in conflict with the + Clock-source controller, which is a kind of ALSA-standard. It makes sense to set the card to a special mode (master at some - frequency or slave), since even not using an Audio-application + frequency or slave), since even not using an Audio application a studio should have working synchronisations setup. So use - Clock-source-controller instead !!!! + Clock-source controller instead !!!! * Clock Source @@ -145,11 +123,11 @@ conversion. A test-application shows the usage of the controller. "Internal 96.0 kHz" Choose between Master at a specific Frequency and so also the - Speed-mode or Slave (Autosync). Also see "Preferred Sync Ref" + Speed-mode or Slave (Autosync). Also see "Preferred Sync Ref". .. warning:: - !!!! This is no pure hardware function but was implemented by - ALSA by some ALSA-drivers before, so I use it also. !!! + !!!! This is not a pure hardware function but was implemented by + ALSA by some ALSA drivers before, so I use it also. !!! * Preferred Sync Ref @@ -165,7 +143,7 @@ conversion. A test-application shows the usage of the controller. chosen. If it is not available another is used if possible. .. note:: - Note: Since MADI has a much higher bit-rate than word-clock, the + Since MADI has a much higher bit-rate than word-clock, the card should synchronise better in MADI Mode. But since the RME-PLL is very good, there are almost no problems with word-clock too. I never found a difference. @@ -179,12 +157,12 @@ conversion. A test-application shows the usage of the controller. * Values -- 0 1 - Using 64-channel-modus (1) or 56-channel-modus for + Using 64-channel mode (1) or 56-channel mode for MADI-transmission (0). .. note:: - Note: This control is for output only. Input-mode is detected + This control is for output only. Input mode is detected automatically from hardware sending MADI. @@ -200,7 +178,7 @@ conversion. A test-application shows the usage of the controller. Don't use to lower 5 Audio-bits on AES as additional Bits. -* Safe Mode oder Auto Input +* Safe Mode or Auto Input * Name -- "Safe Mode" @@ -222,7 +200,7 @@ conversion. A test-application shows the usage of the controller. * Values -- optical coaxial - Choosing the Input, optical or coaxial. If Safe-mode is active, + Choosing the Input, optical or coaxial. If Safe mode is active, this is the preferred Input. Mixer @@ -240,7 +218,7 @@ Mixer Here as a first value the channel-index is taken to get/set the corresponding mixer channel, where 0-63 are the input to output fader and 64-127 the playback to outputs fader. Value 0 - is channel muted 0 and 32768 an amplification of 1. + is channel muted 0 and 32768 an amplification of 1. * Chn 1-64 @@ -250,14 +228,14 @@ Mixer * Line Out - * Name -- "Line Out" + * Name -- "Line Out" * Access -- Read Write * Values -- 0 1 Switching on and off the analog out, which has nothing to do - with mixing or routing. the analog outs reflects channel 63,64. + with mixing or routing. The analog outs reflects channel 63,64. Information (only read access) @@ -280,7 +258,7 @@ Information (only read access) Should be "Autosync Rate", but Name used is - ALSA-Scheme. External Sample frequency liked used on Autosync is + ALSA-Scheme. External Sample frequency like used on Autosync is reported. @@ -315,19 +293,19 @@ Information (only read access) Sync-Reference is either "WordClock", "MADI" or none. -* RX 64ch --- noch nicht implementiert +* RX 64ch --- not yet implemented - MADI-Receiver is in 64 channel mode oder 56 channel mode. + MADI-Receiver is in 64-channel mode or 56-channel mode. -* AB_inp --- not tested +* AB_inp --- not tested Used input for Auto-Input. * actual Buffer Position --- not implemented - !!! this is a ALSA internal function, so no control is used !!! + !!! This is an ALSA internal function, so no control is used !!! @@ -352,22 +330,15 @@ Calling Parameter note: ALSA-standard -* precise_ptr int array (min = 1, max = 8) - - Enable precise pointer, or disable. - -.. note:: - note: Use only when the application supports this (which is a special case). - * line_outs_monitor int array (min = 1, max = 8) Send playback streams to analog outs by default. .. note:: - note: each playback channel is mixed to the same numbered output + Each playback channel is mixed to the same numbered output channel (routed). This is against the ALSA-convention, where all channels have to be muted on after loading the driver, but was - used before on other cards, so i historically use it again) + used before on other cards, so I historically use it again. @@ -375,5 +346,5 @@ Calling Parameter Enable Analog Out on Channel 63/64 by default. -.. note :: - note: here the analog output is enabled (but not routed). +.. note:: + Here the analog output is enabled (but not routed). From d5212d2d50f36a132568534ca637b56ae50f3f02 Mon Sep 17 00:00:00 2001 From: Khanh Le Date: Thu, 17 Sep 2026 03:02:42 +0000 Subject: [PATCH 251/924] ASoC: ak4619: Add suspend and resume callbacks After resuming from S2R, the register cache is out of sync with the hardware. Implement suspend and resume callbacks to properly restore the configuration of the codec on resume. Signed-off-by: Khanh Le Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87a4pgzijx.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/ak4619.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/sound/soc/codecs/ak4619.c b/sound/soc/codecs/ak4619.c index d9c9f6b20028..5252b30669d4 100644 --- a/sound/soc/codecs/ak4619.c +++ b/sound/soc/codecs/ak4619.c @@ -799,7 +799,26 @@ static const struct snd_soc_dai_ops ak4619_dai_ops = { .num_auto_selectable_formats = ARRAY_SIZE(ak4619_dai_formats), }; +static int ak4619_suspend(struct snd_soc_component *component) +{ + struct regmap *regmap = dev_get_regmap(component->dev, NULL); + + regcache_cache_only(regmap, true); + regcache_mark_dirty(regmap); + return 0; +} + +static int ak4619_resume(struct snd_soc_component *component) +{ + struct regmap *regmap = dev_get_regmap(component->dev, NULL); + + regcache_cache_only(regmap, false); + return regcache_sync(regmap); +} + static const struct snd_soc_component_driver soc_component_dev_ak4619 = { + .suspend = ak4619_suspend, + .resume = ak4619_resume, .set_bias_level = ak4619_set_bias_level, .controls = ak4619_snd_controls, .num_controls = ARRAY_SIZE(ak4619_snd_controls), From a603fe4e000366d187849667c9afb38da5259632 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Thu, 17 Sep 2026 11:10:01 +0530 Subject: [PATCH 252/924] ASoC: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Signed-off-by: Hemanth Selam Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260917054001.15728-1-hemanth.selam@gmail.com Signed-off-by: Mark Brown --- sound/soc/atmel/atmel-pcm-dma.c | 2 +- sound/soc/bcm/cygnus-ssp.h | 2 +- sound/soc/codecs/adau1701.c | 4 ++-- sound/soc/codecs/cs35l33.c | 2 +- sound/soc/codecs/cs35l34.c | 2 +- sound/soc/codecs/cs42l42.c | 2 +- sound/soc/codecs/cs42l73.c | 4 ++-- sound/soc/codecs/cx20442.c | 2 +- sound/soc/codecs/da7210.c | 2 +- sound/soc/codecs/hdac_hda.c | 2 +- sound/soc/codecs/hdac_hdmi.c | 2 +- sound/soc/codecs/max98927.c | 4 ++-- sound/soc/codecs/mt6359.c | 8 ++++---- sound/soc/codecs/nau8825.c | 10 +++++----- sound/soc/codecs/rt1320-sdw.c | 2 +- sound/soc/codecs/rt5631.h | 8 ++++---- sound/soc/codecs/rt5640.c | 2 +- sound/soc/codecs/rt5645.c | 2 +- sound/soc/codecs/rt5651.c | 2 +- sound/soc/codecs/rt5670.c | 2 +- sound/soc/codecs/tlv320dac33.c | 2 +- sound/soc/intel/atom/sst-mfld-dsp.h | 2 +- sound/soc/intel/atom/sst/sst.h | 2 +- sound/soc/intel/boards/sof_cirrus_common.c | 2 +- sound/soc/qcom/qdsp6/q6asm-dai.c | 2 +- sound/soc/ti/davinci-i2s.c | 2 +- sound/soc/ti/davinci-mcasp.c | 2 +- sound/soc/ti/omap-mcbsp.c | 2 +- sound/soc/uniphier/aio.h | 2 +- sound/soc/ux500/ux500_msp_i2s.c | 2 +- 30 files changed, 43 insertions(+), 43 deletions(-) diff --git a/sound/soc/atmel/atmel-pcm-dma.c b/sound/soc/atmel/atmel-pcm-dma.c index 7306e04da513..e4b73ae3df4f 100644 --- a/sound/soc/atmel/atmel-pcm-dma.c +++ b/sound/soc/atmel/atmel-pcm-dma.c @@ -47,7 +47,7 @@ static const struct snd_pcm_hardware atmel_pcm_dma_hardware = { * atmel_pcm_dma_irq: SSC interrupt handler for DMAENGINE enabled SSC * * We use DMAENGINE to send/receive data to/from SSC so this ISR is only to - * check if any overrun occured. + * check if any overrun occurred. */ static void atmel_pcm_dma_irq(u32 ssc_sr, struct snd_pcm_substream *substream) diff --git a/sound/soc/bcm/cygnus-ssp.h b/sound/soc/bcm/cygnus-ssp.h index 4925e03c3c30..7848e15efbe1 100644 --- a/sound/soc/bcm/cygnus-ssp.h +++ b/sound/soc/bcm/cygnus-ssp.h @@ -27,7 +27,7 @@ struct ringbuf_regs { unsigned wraddr; unsigned baseaddr; unsigned endaddr; - unsigned fmark; /* freemark for play, fullmark for caputure */ + unsigned fmark; /* freemark for play, fullmark for capture */ unsigned period_bytes; unsigned buf_size; }; diff --git a/sound/soc/codecs/adau1701.c b/sound/soc/codecs/adau1701.c index 505d52c63627..4afbf46ffaa3 100644 --- a/sound/soc/codecs/adau1701.c +++ b/sound/soc/codecs/adau1701.c @@ -4,7 +4,7 @@ * * Copyright 2011 Analog Devices Inc. * Author: Lars-Peter Clausen - * based on an inital version by Cliff Cai + * based on an initial version by Cliff Cai */ #include @@ -699,7 +699,7 @@ static int adau1701_probe(struct snd_soc_component *component) */ adau1701->pll_clkdiv = ADAU1707_CLKDIV_UNSET; - /* initalize with pre-configured pll mode settings */ + /* initialize with pre-configured pll mode settings */ ret = adau1701_reset(component, adau1701->pll_clkdiv, 0); if (ret < 0) goto exit_regulators_disable; diff --git a/sound/soc/codecs/cs35l33.c b/sound/soc/codecs/cs35l33.c index d0f9231f5e44..b8c19f175701 100644 --- a/sound/soc/codecs/cs35l33.c +++ b/sound/soc/codecs/cs35l33.c @@ -578,7 +578,7 @@ static int cs35l33_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, | CS35L33_X_LOC); } - /* disconnect {vp,vbst}_mon routes: eanble later if set in tx_mask*/ + /* disconnect {vp,vbst}_mon routes: enable later if set in tx_mask*/ snd_soc_dapm_del_routes(dapm, cs35l33_vp_vbst_mon_route, ARRAY_SIZE(cs35l33_vp_vbst_mon_route)); diff --git a/sound/soc/codecs/cs35l34.c b/sound/soc/codecs/cs35l34.c index 3f265bd760f7..a72be629a31e 100644 --- a/sound/soc/codecs/cs35l34.c +++ b/sound/soc/codecs/cs35l34.c @@ -297,7 +297,7 @@ static int cs35l34_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, CS35L34_X_STATE | CS35L34_X_LOC, CS35L34_X_STATE | CS35L34_X_LOC); - /* disconnect {vp,vbst}_mon routes: eanble later if set in tx_mask*/ + /* disconnect {vp,vbst}_mon routes: enable later if set in tx_mask*/ while (slot >= 0) { /* configure VMON_TX_LOC */ if (slot_num == 0) diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index 9e40c03ada4e..b5d69d5516a6 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -1682,7 +1682,7 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) if (cs42l42->suspended || !cs42l42->init_done) return IRQ_NONE; - /* Read sticky registers to clear interurpt */ + /* Read sticky registers to clear interrupt */ for (i = 0; i < ARRAY_SIZE(stickies); i++) { regmap_read(cs42l42->regmap, irq_params_table[i].status_addr, &(stickies[i])); diff --git a/sound/soc/codecs/cs42l73.c b/sound/soc/codecs/cs42l73.c index 72076ccf4570..72c4213ed74b 100644 --- a/sound/soc/codecs/cs42l73.c +++ b/sound/soc/codecs/cs42l73.c @@ -761,7 +761,7 @@ static const struct snd_soc_dapm_route cs42l73_audio_map[] = { {"ASPOUTL", "ASP-IP Volume", "ASPL Output Mixer"}, {"ASPOUTR", "ASP-IP Volume", "ASPR Output Mixer"}, - /* Auxillary Capture */ + /* Auxiliary Capture */ {"XSPL Output Mixer", NULL, "Input Left Capture"}, {"XSPR Output Mixer", NULL, "Input Right Capture"}, @@ -1126,7 +1126,7 @@ static int cs42l73_set_bias_level(struct snd_soc_component *component, mdelay(cs42l73->shutdwn_delay); cs42l73->shutdwn_delay = 0; } else { - mdelay(15); /* Min amount of time requred to power + mdelay(15); /* Min amount of time required to power * down. */ } diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index 0dea84cd8ef2..d3c7a9370220 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -200,7 +200,7 @@ static int cx20442_write(struct snd_soc_component *component, unsigned int reg, * Line discpline related code * * Any of the callback functions below can be used in two ways: - * 1) registerd by a machine driver as one of line discipline operations, + * 1) registered by a machine driver as one of line discipline operations, * 2) called from a machine's provided line discipline callback function * in case when extra machine specific code must be run as well. */ diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c index 597510de561a..40d759938edc 100644 --- a/sound/soc/codecs/da7210.c +++ b/sound/soc/codecs/da7210.c @@ -1158,7 +1158,7 @@ static int da7210_probe(struct snd_soc_component *component) snd_soc_component_write(component, DA7210_PLL_DIV3, DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP); - /* Diable PLL and bypass it */ + /* Disable PLL and bypass it */ snd_soc_component_write(component, DA7210_PLL, DA7210_PLL_FS_48000); /* Activate all enabled subsystem */ diff --git a/sound/soc/codecs/hdac_hda.c b/sound/soc/codecs/hdac_hda.c index 1c06fdbf0e71..7414be08d3d5 100644 --- a/sound/soc/codecs/hdac_hda.c +++ b/sound/soc/codecs/hdac_hda.c @@ -520,7 +520,7 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) /* * hdac_device core already sets the state to active and calls * get_noresume. So enable runtime and set the device to suspend. - * pm_runtime_enable is also called during codec registeration + * pm_runtime_enable is also called during codec registration */ pm_runtime_put(&hdev->dev); pm_runtime_suspend(&hdev->dev); diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c index d9e6fa6d3e99..b3f677e7ddc9 100644 --- a/sound/soc/codecs/hdac_hdmi.c +++ b/sound/soc/codecs/hdac_hdmi.c @@ -1753,7 +1753,7 @@ static int hdmi_codec_resume(struct device *dev) /* * As the ELD notify callback request is not entertained while the * device is in suspend state. Need to manually check detection of - * all pins here. pin capablity change is not support, so use the + * all pins here. pin capability change is not support, so use the * already set pin caps. * * NOTE: this is safe to call even if the codec doesn't actually resume. diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c index d8dfaefdca7e..285c9d1ea721 100644 --- a/sound/soc/codecs/max98927.c +++ b/sound/soc/codecs/max98927.c @@ -672,7 +672,7 @@ static int max98927_probe(struct snd_soc_component *component) 0x80); regmap_write(max98927->regmap, MAX98927_R0026_PCM_TO_SPK_MONOMIX_B, 0x1); - /* Set inital volume (+13dB) */ + /* Set initial volume (+13dB) */ regmap_write(max98927->regmap, MAX98927_R0036_AMP_VOL_CTRL, 0x38); regmap_write(max98927->regmap, MAX98927_R003C_SPK_GAIN, 0x05); /* Enable DC blocker */ @@ -870,7 +870,7 @@ static int max98927_i2c_probe(struct i2c_client *i2c) /* voltage/current slot configuration */ max98927_slot_config(i2c, max98927); - /* codec registeration */ + /* codec registration */ ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_max98927, max98927_dai, ARRAY_SIZE(max98927_dai)); diff --git a/sound/soc/codecs/mt6359.c b/sound/soc/codecs/mt6359.c index b15bdb15dbb0..c51eae602ca0 100644 --- a/sound/soc/codecs/mt6359.c +++ b/sound/soc/codecs/mt6359.c @@ -82,7 +82,7 @@ static void mt6359_reset_capture_gpio(struct mt6359_priv *priv) 0x3 << 0, 0x0); } -/* use only when doing mtkaif calibraiton at the boot time */ +/* use only when doing mtkaif calibration at the boot time */ static void mt6359_set_dcxo(struct mt6359_priv *priv, bool enable) { regmap_update_bits(priv->regmap, MT6359_DCXO_CW12, @@ -90,7 +90,7 @@ static void mt6359_set_dcxo(struct mt6359_priv *priv, bool enable) (enable ? 1 : 0) << RG_XO_AUDIO_EN_M_SFT); } -/* use only when doing mtkaif calibraiton at the boot time */ +/* use only when doing mtkaif calibration at the boot time */ static void mt6359_set_clksq(struct mt6359_priv *priv, bool enable) { /* Enable/disable CLKSQ 26MHz */ @@ -99,7 +99,7 @@ static void mt6359_set_clksq(struct mt6359_priv *priv, bool enable) (enable ? 1 : 0) << RG_CLKSQ_EN_SFT); } -/* use only when doing mtkaif calibraiton at the boot time */ +/* use only when doing mtkaif calibration at the boot time */ static void mt6359_set_aud_global_bias(struct mt6359_priv *priv, bool enable) { regmap_update_bits(priv->regmap, MT6359_AUDDEC_ANA_CON13, @@ -107,7 +107,7 @@ static void mt6359_set_aud_global_bias(struct mt6359_priv *priv, bool enable) (enable ? 0 : 1) << RG_AUDGLB_PWRDN_VA32_SFT); } -/* use only when doing mtkaif calibraiton at the boot time */ +/* use only when doing mtkaif calibration at the boot time */ static void mt6359_set_topck(struct mt6359_priv *priv, bool enable) { regmap_update_bits(priv->regmap, MT6359_AUD_TOP_CKPDN_CON0, diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index 5165aeba4b67..2eb0efc983a0 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -345,10 +345,10 @@ static u32 nau8825_intlog10_dec3(u32 value) /** * nau8825_xtalk_sidetone - computes cross talk suppression sidetone gain. * - * @sig_org: orignal signal level + * @sig_org: original signal level * @sig_cros: cross talk signal level * - * The orignal and cross talk signal vlues need to be characterized. + * The original and cross talk signal vlues need to be characterized. * Once these values have been characterized, this sidetone value * can be converted to decibel with the equation below. * sidetone = 20 * log (original signal level / crosstalk signal level) @@ -671,11 +671,11 @@ static void nau8825_xtalk_imm_stop(struct nau8825 *nau8825) * Thus, the measurement function has four states to complete whole sequence. * 1. Prepare state : Prepare the resource for detection and transfer to HPR * IMM stat to make JKR1(HPR) impedance measure. - * 2. HPR IMM state : Read out orignal signal level of JKR1(HPR) and transfer + * 2. HPR IMM state : Read out original signal level of JKR1(HPR) and transfer * to HPL IMM state to make JKTIP(HPL) impedance measure. * 3. HPL IMM state : Read out cross talk signal level of JKTIP(HPL) and * transfer to IMM state to determine suppression sidetone gain. - * 4. IMM state : Computes cross talk suppression sidetone gain with orignal + * 4. IMM state : Computes cross talk suppression sidetone gain with original * and cross talk signal level. Apply this gain and then restore codec * configuration. Then transfer to Done state for ending. */ @@ -724,7 +724,7 @@ static void nau8825_xtalk_measure(struct nau8825 *nau8825) nau8825->xtalk_state = NAU8825_XTALK_IMM; break; case NAU8825_XTALK_IMM: - /* In impedance measure state, the orignal and cross talk + /* In impedance measure state, the original and cross talk * signal level vlues are ready. The side tone gain is deter- * mined with these signal level. After all, restore codec * configuration. diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index 444a91755be9..b13622e54ae0 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -2084,7 +2084,7 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x80); /* RAE run */ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x01); - /* Phase sync eanble */ + /* Phase sync enable */ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); break; } diff --git a/sound/soc/codecs/rt5631.h b/sound/soc/codecs/rt5631.h index 8a6b99a48c7c..00734207ac9f 100644 --- a/sound/soc/codecs/rt5631.h +++ b/sound/soc/codecs/rt5631.h @@ -415,7 +415,7 @@ #define RT5631_ADDA_FILTER_CLK_SEL_256FS (0 << 7) /* 256FS */ #define RT5631_ADDA_FILTER_CLK_SEL_384FS (1 << 7) /* 384FS */ -/* Power managment addition 1 (0x3A) */ +/* Power management addition 1 (0x3A) */ #define RT5631_PWR_MAIN_I2S_EN (0x1 << 15) #define RT5631_PWR_MAIN_I2S_BIT 15 #define RT5631_PWR_CLASS_D (0x1 << 12) @@ -435,7 +435,7 @@ #define RT5631_PWR_DAC_R_TO_MIXER (0x1 << 5) #define RT5631_PWR_DAC_R_TO_MIXER_BIT 5 -/* Power managment addition 2 (0x3B) */ +/* Power management addition 2 (0x3B) */ #define RT5631_PWR_OUTMIXER_L (0x1 << 15) #define RT5631_PWR_OUTMIXER_L_BIT 15 #define RT5631_PWR_OUTMIXER_R (0x1 << 14) @@ -461,7 +461,7 @@ #define RT5631_PWR_PLL2 (0x1 << 0) #define RT5631_PWR_PLL2_BIT 0 -/* Power managment addition 3(0x3C) */ +/* Power management addition 3(0x3C) */ #define RT5631_PWR_VREF (0x1 << 15) #define RT5631_PWR_VREF_BIT 15 #define RT5631_PWR_FAST_VREF_CTRL (0x1 << 14) @@ -489,7 +489,7 @@ #define RT5631_PWR_HP_AMP_DRIVING (0x1 << 0) #define RT5631_PWR_HP_AMP_DRIVING_BIT 0 -/* Power managment addition 4(0x3E) */ +/* Power management addition 4(0x3E) */ #define RT5631_PWR_SPK_L_VOL (0x1 << 15) #define RT5631_PWR_SPK_L_VOL_BIT 15 #define RT5631_PWR_SPK_R_VOL (0x1 << 14) diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c index 4fa1eee8e87f..03d0ac3359f5 100644 --- a/sound/soc/codecs/rt5640.c +++ b/sound/soc/codecs/rt5640.c @@ -1216,7 +1216,7 @@ static const struct snd_soc_dapm_widget rt5640_dapm_widgets[] = { 0, rt5640_spk_l_mix, ARRAY_SIZE(rt5640_spk_l_mix)), SND_SOC_DAPM_MIXER("SPK MIXR", RT5640_PWR_MIXER, RT5640_PWR_SM_R_BIT, 0, rt5640_spk_r_mix, ARRAY_SIZE(rt5640_spk_r_mix)), - /* Ouput Volume */ + /* Output Volume */ SND_SOC_DAPM_PGA("SPKVOL L", RT5640_PWR_VOL, RT5640_PWR_SV_L_BIT, 0, NULL, 0), SND_SOC_DAPM_PGA("SPKVOL R", RT5640_PWR_VOL, diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index bf9f278c3de9..bb448254275f 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -2209,7 +2209,7 @@ static const struct snd_soc_dapm_widget rt5645_dapm_widgets[] = { 0, rt5645_out_l_mix, ARRAY_SIZE(rt5645_out_l_mix)), SND_SOC_DAPM_MIXER("OUT MIXR", RT5645_PWR_MIXER, RT5645_PWR_OM_R_BIT, 0, rt5645_out_r_mix, ARRAY_SIZE(rt5645_out_r_mix)), - /* Ouput Volume */ + /* Output Volume */ SND_SOC_DAPM_SWITCH("SPKVOL L", RT5645_PWR_VOL, RT5645_PWR_SV_L_BIT, 0, &spk_l_vol_control), SND_SOC_DAPM_SWITCH("SPKVOL R", RT5645_PWR_VOL, RT5645_PWR_SV_R_BIT, 0, diff --git a/sound/soc/codecs/rt5651.c b/sound/soc/codecs/rt5651.c index 9723b4c7540c..b911757376f5 100644 --- a/sound/soc/codecs/rt5651.c +++ b/sound/soc/codecs/rt5651.c @@ -1024,7 +1024,7 @@ static const struct snd_soc_dapm_widget rt5651_dapm_widgets[] = { 0, rt5651_out_l_mix, ARRAY_SIZE(rt5651_out_l_mix)), SND_SOC_DAPM_MIXER("OUT MIXR", RT5651_PWR_MIXER, RT5651_PWR_OM_R_BIT, 0, rt5651_out_r_mix, ARRAY_SIZE(rt5651_out_r_mix)), - /* Ouput Volume */ + /* Output Volume */ SND_SOC_DAPM_SWITCH("OUTVOL L", RT5651_PWR_VOL, RT5651_PWR_OV_L_BIT, 0, &outvol_l_control), SND_SOC_DAPM_SWITCH("OUTVOL R", RT5651_PWR_VOL, diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index f772f093479a..62d4e7cea5af 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -1901,7 +1901,7 @@ static const struct snd_soc_dapm_widget rt5670_dapm_widgets[] = { 0, rt5670_out_l_mix, ARRAY_SIZE(rt5670_out_l_mix)), SND_SOC_DAPM_MIXER("OUT MIXR", RT5670_PWR_MIXER, RT5670_PWR_OM_R_BIT, 0, rt5670_out_r_mix, ARRAY_SIZE(rt5670_out_r_mix)), - /* Ouput Volume */ + /* Output Volume */ SND_SOC_DAPM_MIXER("HPOVOL MIXL", RT5670_PWR_VOL, RT5670_PWR_HV_L_BIT, 0, rt5670_hpvoll_mix, ARRAY_SIZE(rt5670_hpvoll_mix)), diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 55a95b62a650..b91ab42882d1 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -914,7 +914,7 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, /* OSC calibration time */ dac33_write(component, DAC33_CALIB_TIME, 96); - /* adjustment treshold & step */ + /* adjustment threshold & step */ dac33_write(component, DAC33_INT_OSC_CTRL_B, DAC33_ADJTHRSHLD(2) | DAC33_ADJSTEP(1)); diff --git a/sound/soc/intel/atom/sst-mfld-dsp.h b/sound/soc/intel/atom/sst-mfld-dsp.h index c8f0816edb53..59606259a06e 100644 --- a/sound/soc/intel/atom/sst-mfld-dsp.h +++ b/sound/soc/intel/atom/sst-mfld-dsp.h @@ -189,7 +189,7 @@ struct ipc_dsp_hdr { union ipc_header_high { struct { u32 msg_id:8; /* Message ID - Max 256 Message Types */ - u32 task_id:4; /* Task ID associated with this comand */ + u32 task_id:4; /* Task ID associated with this command */ u32 drv_id:4; /* Identifier for the driver to track*/ u32 rsvd1:8; /* Reserved */ u32 result:4; /* Reserved */ diff --git a/sound/soc/intel/atom/sst/sst.h b/sound/soc/intel/atom/sst/sst.h index c43946c5ecee..e24d9dad7ef6 100644 --- a/sound/soc/intel/atom/sst/sst.h +++ b/sound/soc/intel/atom/sst/sst.h @@ -182,7 +182,7 @@ struct sst_block { * @pcm_substream : PCM substream * @period_elapsed : PCM period elapsed callback * @sfreq : stream sampling freq - * @cumm_bytes : cummulative bytes decoded + * @cumm_bytes : cumulative bytes decoded */ struct stream_info { unsigned int status; diff --git a/sound/soc/intel/boards/sof_cirrus_common.c b/sound/soc/intel/boards/sof_cirrus_common.c index 88fc6cb2bfd4..ec4bda40ae00 100644 --- a/sound/soc/intel/boards/sof_cirrus_common.c +++ b/sound/soc/intel/boards/sof_cirrus_common.c @@ -151,7 +151,7 @@ static const char * const cs35l41_name_prefixes[] = { "WL", "WR", "TL", "TR" }; * UID 0x2 -> TL * UID 0x3 -> TR * Note: If there are less than 4 Amps, UIDs still map to WL/WR/TL/TR. Dynamic code will only create - * dai links for UIDs which exist, and ignore non-existant ones. Only 2 or 4 amps are expected. + * dai links for UIDs which exist, and ignore non-existent ones. Only 2 or 4 amps are expected. * Return number of codecs found. */ static int cs35l41_compute_codec_conf(void) diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c index 4f09fdd40905..cd4f82bb8cc7 100644 --- a/sound/soc/qcom/qdsp6/q6asm-dai.c +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -551,7 +551,7 @@ static void compress_event_handler(uint32_t opcode, uint32_t token, prtd->stream_id, CMD_CLOSE); /* - * vaild stream ids start from 1, So we are + * valid stream ids start from 1, So we are * toggling this between 1 and 2. */ prtd->stream_id = (prtd->stream_id == 1 ? 2 : 1); diff --git a/sound/soc/ti/davinci-i2s.c b/sound/soc/ti/davinci-i2s.c index ec7eb2f19b6f..3614f614117d 100644 --- a/sound/soc/ti/davinci-i2s.c +++ b/sound/soc/ti/davinci-i2s.c @@ -49,7 +49,7 @@ * incompatible with ASP and with either McBSP. * * In short: this uses ASP to implement I2S, not McBSP. - * And it won't be the only DaVinci implemention of I2S. + * And it won't be the only DaVinci implementation of I2S. */ #define DAVINCI_MCBSP_DRR_REG 0x00 #define DAVINCI_MCBSP_DXR_REG 0x04 diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index ad9e194a3d0d..d461ab419655 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -1818,7 +1818,7 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, !mcasp->async_mode) max_channels = mcasp->channels; /* - * But we can always allow channels upto the amount of + * But we can always allow channels up to the amount of * the available tdm_slots. */ if (max_channels < tdm_slots) diff --git a/sound/soc/ti/omap-mcbsp.c b/sound/soc/ti/omap-mcbsp.c index 5734388a4e4b..889e357b64d0 100644 --- a/sound/soc/ti/omap-mcbsp.c +++ b/sound/soc/ti/omap-mcbsp.c @@ -1237,7 +1237,7 @@ static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, break; case OMAP_MCBSP_SYSCLK_CLKR_EXT: regs->pcr0 |= SCLKME; - /* Disable ouput on CLKR pin in master mode */ + /* Disable output on CLKR pin in master mode */ regs->pcr0 &= ~CLKRM; break; default: diff --git a/sound/soc/uniphier/aio.h b/sound/soc/uniphier/aio.h index 1900ea01ce3d..87b27256cf80 100644 --- a/sound/soc/uniphier/aio.h +++ b/sound/soc/uniphier/aio.h @@ -183,7 +183,7 @@ struct uniphier_aio_selector { * * These are examples for sound data paths: * - * For caputure device: + * For capture device: * (outer of AIO) -> iport -> iif -> ch -> rb -> (CPU) * For playback device: * (CPU) -> rb -> ch -> oif -> oport -> (outer of AIO) diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index 683b485fb570..bd2cfc618290 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -19,7 +19,7 @@ #include "ux500_msp_i2s.h" - /* Protocol desciptors */ + /* Protocol descriptors */ static const struct msp_protdesc prot_descs[] = { { /* I2S */ MSP_SINGLE_PHASE, From ebdc596c42626d1a6dcbc7cd7ad3f842b2cb2c70 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 16 Sep 2026 15:04:53 -0700 Subject: [PATCH 253/924] ASoC: fsl: mpc5200: gate AC97 driver on PPC_MPC52xx The MPC5200 PSC AC97 driver only applies to the MPC52xx family of SoCs, but only depended on PPC_BESTCOMM. Restrict the driver to MPC52xx platforms so it is not offered (and does not fail to build) on other bestcomm-based machines. Fixes the following linking error: ERROR: modpost: sound/soc/fsl/mpc5200_psc_ac97.ko: symbol 'mpc5200_psc_ac97_gpio_reset' undefined! Assisted-by: LLM Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260916220453.260472-1-rosenp@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 04940879dfd8..101bc952aa14 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -234,7 +234,7 @@ config SND_SOC_MPC5200_I2S config SND_SOC_MPC5200_AC97 tristate "Freescale MPC5200 PSC in AC97 mode driver" - depends on PPC_BESTCOMM + depends on PPC_BESTCOMM && PPC_MPC52xx select SND_SOC_AC97_BUS select SND_MPC52xx_DMA select PPC_BESTCOMM_GEN_BD From 8633243a494bb78ed92cdd0339bb67ed3d08c020 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:14:04 -0700 Subject: [PATCH 254/924] ASoC: rockchip: rk3399_gru_sound: Allocate dai_link with ARRAY_SIZE() In preparation for making the devm_kmalloc family of allocators type aware, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (Before, the allocator would always return "void *", which can be implicitly cast to any pointer type.) This is allocating a copy of rockchip_dais, which is an array of struct snd_soc_dai_link, but the size was taken from the whole array, which would make the allocation type a pointer to the array rather than the "struct snd_soc_dai_link *" being assigned. Allocate ARRAY_SIZE-many entries instead. The resulting allocation size is the same. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: sound/soc/rockchip/rk3399_gru_sound.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Link: https://patch.msgid.link/20260917211403.i.984-kees@kernel.org Signed-off-by: Mark Brown --- sound/soc/rockchip/rk3399_gru_sound.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/rockchip/rk3399_gru_sound.c b/sound/soc/rockchip/rk3399_gru_sound.c index b80acb221d24..04e7bed8275d 100644 --- a/sound/soc/rockchip/rk3399_gru_sound.c +++ b/sound/soc/rockchip/rk3399_gru_sound.c @@ -505,8 +505,8 @@ static int rockchip_sound_of_parse_dais(struct device *dev, int i, index; int num_routes; - card->dai_link = devm_kzalloc(dev, sizeof(rockchip_dais), - GFP_KERNEL); + card->dai_link = devm_kcalloc(dev, ARRAY_SIZE(rockchip_dais), + sizeof(*card->dai_link), GFP_KERNEL); if (!card->dai_link) return -ENOMEM; From 13b4ab33642b145a74ee55dc64110900e077f6be Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:10:43 -0700 Subject: [PATCH 255/924] ASoC: codecs: wm8904: Add const to drc_texts allocation type In preparation for converting the kmalloc family of allocators to the type-aware kmalloc_obj family, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (The kmalloc family returns "void *", which can be implicitly cast to any pointer type.) The assigned type is "const char **", but the converted allocation type would be "char **", which is the same type without the const qualifier. As there is no general way to safely add const qualifiers, take the size from the assignment target instead. No change in allocation size results. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: sound/soc/codecs/wm8904.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260917211042.i.810-kees@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/wm8904.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index 153ce2c1a0f6..96d2e54a2c6a 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -2204,7 +2204,7 @@ static void wm8904_handle_pdata(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8904->drc_texts = kmalloc_array(pdata->num_drc_cfgs, - sizeof(char *), + sizeof(*wm8904->drc_texts), GFP_KERNEL); if (!wm8904->drc_texts) return; From d0d2b9576f447ca842132e94f56c488f9fd85d33 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:11:03 -0700 Subject: [PATCH 256/924] ASoC: codecs: wm8958: Add const to enum texts allocation types In preparation for converting the kmalloc family of allocators to the type-aware kmalloc_obj family, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (The kmalloc family returns "void *", which can be implicitly cast to any pointer type.) The assigned types of mbc_texts, vss_texts, vss_hpf_texts, and enh_eq_texts are "const char **", but the converted allocation types would be "char **", which is the same type without the const qualifier. As there is no general way to safely add const qualifiers, take the sizes from the assignment targets instead. No change in allocation size results. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: sound/soc/codecs/wm8958-dsp2.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Reviewed-by: Cezary Rojewski Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260917211102.i.029-kees@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/wm8958-dsp2.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c index f75a6dc9d2bb..b26c36ca8249 100644 --- a/sound/soc/codecs/wm8958-dsp2.c +++ b/sound/soc/codecs/wm8958-dsp2.c @@ -928,7 +928,7 @@ void wm8958_dsp2_init(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8994->mbc_texts = kmalloc_array(pdata->num_mbc_cfgs, - sizeof(char *), + sizeof(*wm8994->mbc_texts), GFP_KERNEL); if (!wm8994->mbc_texts) return; @@ -954,7 +954,7 @@ void wm8958_dsp2_init(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8994->vss_texts = kmalloc_array(pdata->num_vss_cfgs, - sizeof(char *), + sizeof(*wm8994->vss_texts), GFP_KERNEL); if (!wm8994->vss_texts) return; @@ -981,7 +981,7 @@ void wm8958_dsp2_init(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8994->vss_hpf_texts = kmalloc_array(pdata->num_vss_hpf_cfgs, - sizeof(char *), + sizeof(*wm8994->vss_hpf_texts), GFP_KERNEL); if (!wm8994->vss_hpf_texts) return; @@ -1009,7 +1009,7 @@ void wm8958_dsp2_init(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8994->enh_eq_texts = kmalloc_array(pdata->num_enh_eq_cfgs, - sizeof(char *), + sizeof(*wm8994->enh_eq_texts), GFP_KERNEL); if (!wm8994->enh_eq_texts) return; From cbcb58ad7a194149c019b37d01ffaa8a65055edc Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:11:18 -0700 Subject: [PATCH 257/924] ASoC: SOF: topology: Add const to tplg_files allocation type In preparation for converting the kmalloc family of allocators to the type-aware kmalloc_obj family, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (The kmalloc family returns "void *", which can be implicitly cast to any pointer type.) The assigned type is "const char **", but the converted allocation type would be "char **", which is the same type without the const qualifier. As there is no general way to safely add const qualifiers, take the size from the assignment target instead. No change in allocation size results. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: sound/soc/sof/topology.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Link: https://patch.msgid.link/20260917211117.i.991-kees@kernel.org Signed-off-by: Mark Brown --- sound/soc/sof/topology.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 8338133899ab..145bad472118 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -2517,7 +2517,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) int i; const char **tplg_files __free(kfree) = - kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); + kcalloc(scomp->card->num_links, sizeof(*tplg_files), + GFP_KERNEL); if (!tplg_files) return -ENOMEM; From d0814ef719032e34ff1ca5fb1be44c15bcfd54a0 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:15:30 +0200 Subject: [PATCH 258/924] ASoC: codecs: rt5514: Fix the pm_ops constant name To improve readability. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260918111530.3589280-1-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5514.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c index 8dad8ba0fe0f..753143e2d11f 100644 --- a/sound/soc/codecs/rt5514.c +++ b/sound/soc/codecs/rt5514.c @@ -1332,7 +1332,7 @@ static int rt5514_i2c_probe(struct i2c_client *i2c) rt5514_dai, ARRAY_SIZE(rt5514_dai)); } -static const struct dev_pm_ops rt5514_i2_pm_ops = { +static const struct dev_pm_ops rt5514_i2c_pm_ops = { SYSTEM_SLEEP_PM_OPS(NULL, rt5514_i2c_resume) }; @@ -1341,7 +1341,7 @@ static struct i2c_driver rt5514_i2c_driver = { .name = "rt5514", .acpi_match_table = ACPI_PTR(rt5514_acpi_match), .of_match_table = of_match_ptr(rt5514_of_match), - .pm = pm_ptr(&rt5514_i2_pm_ops), + .pm = pm_ptr(&rt5514_i2c_pm_ops), }, .probe = rt5514_i2c_probe, .id_table = rt5514_i2c_id, From 5ea495bccfd746b063bc6702064f167467717b41 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 16:58:51 +0000 Subject: [PATCH 259/924] ASoC: audio-graph-card2: Fix use-after-free in audio_graph2_link_c2c() of_graph_get_next_port() consumes the reference of its prev argument. port0 is only borrowed from the caller's iterator, so the call drops a reference the function does not own, and port0 is dereferenced again afterwards. Pass an extra reference, as graph_count_c2c() does, so the reference the callee consumes is the one taken here. Fixes: 5f281c3e82b1 ("ASoC: audio-graph-card2: use __free(device_node) for device node") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Acked-by: Kuninori Morimoto Link: https://patch.msgid.link/20260917165851.2163548-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/generic/audio-graph-card2.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c index e11bc8135cb0..503170578cff 100644 --- a/sound/soc/generic/audio-graph-card2.c +++ b/sound/soc/generic/audio-graph-card2.c @@ -969,7 +969,8 @@ int audio_graph2_link_c2c(struct simple_util_priv *priv, struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); struct device_node *port0 = lnk; struct device_node *ports __free(device_node) = port_to_ports(port0); - struct device_node *port1 __free(device_node) = of_graph_get_next_port(ports, port0); + struct device_node *port1 __free(device_node) = + of_graph_get_next_port(ports, of_node_get(port0)); u32 val = 0; int ret = -EINVAL; From b2c0e5bf750e33b0e1dcdfbb2df8103a06c7caab Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 16:51:30 +0000 Subject: [PATCH 260/924] ASoC: codecs: wm8994: Fix runtime PM leak in wm8994_mic_detect() wm8994_mic_detect() resumes the component before configuring the MICBIAS pins, but the default case of the micbias switch returns -EINVAL right after taking that reference, skipping the pm_runtime_put() on the success path. This leaks a runtime PM reference on every call with an invalid micbias. Release the reference before returning -EINVAL. Fixes: f5a2cda4f1db ("ASoC: wm8994: Ensure the device is resumed in wm89xx_mic_detect functions") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260917165130.2163278-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/wm8994.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 96d3aae36862..b8881b260a4b 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -3552,6 +3552,7 @@ int wm8994_mic_detect(struct snd_soc_component *component, struct snd_soc_jack * break; default: dev_warn(component->dev, "Invalid MICBIAS %d\n", micbias); + pm_runtime_put(component->dev); return -EINVAL; } From cc47a544d4f7e4d5603af2e0d5c18fbdac5fe257 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 18 Sep 2026 00:50:59 +0200 Subject: [PATCH 261/924] ASoC: dt-bindings: sound: Prepare Ux500 audio graph links Audio graph card drivers parse TDM slot data and continuous clock selection from endpoint nodes. The audio graph endpoint schema describes slot count and width directly but does not cover sparse slot masks or continuous clocking. Reference the common TDM slot schema so TX and RX masks are documented, and add the continuous-clock flag. Replace the incomplete Ux500 MSP text binding with a DT schema that describes the clocks, reset, DMA channels and power supply consumed by the driver. Define the MSP as a zero-cell sound DAI provider and allow it to expose an audio graph port. Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260918-ux500-simple-sound-v2-1-88d29416e3c3@kernel.org Signed-off-by: Mark Brown --- .../bindings/sound/audio-graph-port.yaml | 10 +- .../sound/stericsson,ux500-msp-i2s.yaml | 96 +++++++++++++++++++ .../devicetree/bindings/sound/ux500-msp.txt | 42 -------- 3 files changed, 100 insertions(+), 48 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/stericsson,ux500-msp-i2s.yaml delete mode 100644 Documentation/devicetree/bindings/sound/ux500-msp.txt diff --git a/Documentation/devicetree/bindings/sound/audio-graph-port.yaml b/Documentation/devicetree/bindings/sound/audio-graph-port.yaml index d1cbfc5edd3a..565d723d0398 100644 --- a/Documentation/devicetree/bindings/sound/audio-graph-port.yaml +++ b/Documentation/devicetree/bindings/sound/audio-graph-port.yaml @@ -39,6 +39,7 @@ definitions: allOf: - $ref: /schemas/graph.yaml#/$defs/endpoint-base - $ref: /schemas/sound/dai-params.yaml# + - $ref: /schemas/sound/tdm-slot.yaml# properties: mclk-fs: $ref: simple-card.yaml#/definitions/mclk-fs @@ -48,6 +49,9 @@ definitions: bitclock-inversion: description: dai-link uses bit clock inversion $ref: /schemas/types.yaml#/definitions/flag + continuous-clock: + description: dai-link bit clock runs continuously rather than gated + $ref: /schemas/types.yaml#/definitions/flag frame-master: description: Indicates dai-link frame master. oneOf: @@ -82,12 +86,6 @@ definitions: - msb - lsb - dai-tdm-slot-num: - description: Number of slots in use. - $ref: /schemas/types.yaml#/definitions/uint32 - dai-tdm-slot-width: - description: Width in bits for each slot. - $ref: /schemas/types.yaml#/definitions/uint32 dai-tdm-slot-width-map: description: Mapping of sample widths to slot widths. For hardware that cannot support a fixed slot width or a slot width always diff --git a/Documentation/devicetree/bindings/sound/stericsson,ux500-msp-i2s.yaml b/Documentation/devicetree/bindings/sound/stericsson,ux500-msp-i2s.yaml new file mode 100644 index 000000000000..bdfcd5376cfe --- /dev/null +++ b/Documentation/devicetree/bindings/sound/stericsson,ux500-msp-i2s.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/stericsson,ux500-msp-i2s.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ST-Ericsson Ux500 Multichannel Serial Port + +maintainers: + - Linus Walleij + +description: + The Ux500 Multichannel Serial Port (MSP) is a synchronous serial audio + interface supporting I2S and PCM/TDM protocols. Data is transferred to and + from memory using the Ux500 DMA controller. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: stericsson,ux500-msp-i2s + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + v-ape-supply: + description: Analog power engine supply + + dmas: + minItems: 1 + maxItems: 2 + + dma-names: + minItems: 1 + maxItems: 2 + items: + enum: [ rx, tx ] + + clocks: + items: + - description: MSP functional clock + - description: MSP APB bus clock + + clock-names: + items: + - const: msp + - const: apb_pclk + + resets: + maxItems: 1 + + '#sound-dai-cells': + const: 0 + + port: + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + - v-ape-supply + - dmas + - dma-names + - clocks + - clock-names + - resets + - '#sound-dai-cells' + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + msp@80123000 { + compatible = "stericsson,ux500-msp-i2s"; + reg = <0x80123000 0x1000>; + interrupts = ; + v-ape-supply = <&db8500_vape_reg>; + dmas = <&dma 31 0 0x12>, <&dma 31 0 0x10>; + dma-names = "rx", "tx"; + clocks = <&prcc_kclk 1 3>, <&prcc_pclk 1 3>; + clock-names = "msp", "apb_pclk"; + resets = <&prcc_reset DB8500_PRCC_1 DB8500_PRCC_1_RESET_MSP0>; + #sound-dai-cells = <0>; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/ux500-msp.txt b/Documentation/devicetree/bindings/sound/ux500-msp.txt deleted file mode 100644 index 7dd1b96160f5..000000000000 --- a/Documentation/devicetree/bindings/sound/ux500-msp.txt +++ /dev/null @@ -1,42 +0,0 @@ -* ux500 MSP (CPU-side Digital Audio Interface) - -Required properties: - - compatible :"stericsson,ux500-msp-i2s" - - reg : Physical base address and length of the device's registers. - -Optional properties: - - interrupts : The interrupt output from the device. - - -supply : Phandle to the regulator supply - -Example: - - sound { - compatible = "stericsson,snd-soc-mop500"; - - stericsson,platform-pcm-dma = <&pcm>; - stericsson,cpu-dai = <&msp1 &msp3>; - stericsson,audio-codec = <&codec>; - }; - - pcm: ux500-pcm { - compatible = "stericsson,ux500-pcm"; - }; - - msp1: msp@80124000 { - compatible = "stericsson,ux500-msp-i2s"; - reg = <0x80124000 0x1000>; - interrupts = <0 62 0x4>; - v-ape-supply = <&db8500_vape_reg>; - }; - - msp3: msp@80125000 { - compatible = "stericsson,ux500-msp-i2s"; - reg = <0x80125000 0x1000>; - interrupts = <0 62 0x4>; - v-ape-supply = <&db8500_vape_reg>; - }; - - codec: ab8500-codec { - compatible = "stericsson,ab8500-codec"; - stericsson,earpeice-cmv = <950>; /* Units in mV. */ - }; From 661b5aa8109337509e12525ee53a30f90a7a5b01 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 18 Sep 2026 00:51:01 +0200 Subject: [PATCH 262/924] ASoC: dt-bindings: ux500: Remove MOP500 sound card binding The Ux500 device trees now use audio-graph-card2 and no longer contain the MOP500-specific compatible or properties. Remove the obsolete text binding rather than retaining an unused legacy ABI. Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260918-ux500-simple-sound-v2-3-88d29416e3c3@kernel.org Signed-off-by: Mark Brown --- .../bindings/sound/ux500-mop500.txt | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/ux500-mop500.txt diff --git a/Documentation/devicetree/bindings/sound/ux500-mop500.txt b/Documentation/devicetree/bindings/sound/ux500-mop500.txt deleted file mode 100644 index 48e071c96b46..000000000000 --- a/Documentation/devicetree/bindings/sound/ux500-mop500.txt +++ /dev/null @@ -1,39 +0,0 @@ -* MOP500 Audio Machine Driver - -This node is responsible for linking together all ux500 Audio Driver components. - -Required properties: - - compatible : "stericsson,snd-soc-mop500" - -Non-standard properties: - - stericsson,cpu-dai : Phandle to the CPU-side DAI - - stericsson,audio-codec : Phandle to the Audio CODEC - - stericsson,card-name : Over-ride default card name - -Example: - - sound { - compatible = "stericsson,snd-soc-mop500"; - - stericsson,cpu-dai = <&msp1 &msp3>; - stericsson,audio-codec = <&codec>; - }; - - msp1: msp@80124000 { - compatible = "stericsson,ux500-msp-i2s"; - reg = <0x80124000 0x1000>; - interrupts = <0 62 0x4>; - v-ape-supply = <&db8500_vape_reg>; - }; - - msp3: msp@80125000 { - compatible = "stericsson,ux500-msp-i2s"; - reg = <0x80125000 0x1000>; - interrupts = <0 62 0x4>; - v-ape-supply = <&db8500_vape_reg>; - }; - - codec: ab8500-codec { - compatible = "stericsson,ab8500-codec"; - stericsson,earpeice-cmv = <950>; /* Units in mV. */ - }; From 0d46182a1be7ef12f85433d91315d4d7091dd8c8 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 18 Sep 2026 00:51:02 +0200 Subject: [PATCH 263/924] ASoC: ab8500: Skip digital microphone support on AB8505 The AB8505 does not have the digital microphone interface, its six input pins or the VDMIC regulator found in AB8500. The shared codec driver nevertheless creates the entire AB8500 DMIC DAPM graph. When an AB8505 sound card binds, the regulator widget requests the nonexistent V-DMIC supply and falls back to a dummy regulator. Split the digital microphone widgets and routes from the common codec graph and add them only when the parent MFD is an actual AB8500. This also removes the DMIC4-only AD_OUT4 path from AB8505. Fixes: 679d7abdc754 ("ASoC: codecs: Add AB8500 codec-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260918-ux500-simple-sound-v2-4-88d29416e3c3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 144 ++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 62 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index fc204889c95b..3e3ff9bfe00d 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -299,7 +299,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_REGULATOR_SUPPLY("V-AUD", 0, 0), SND_SOC_DAPM_REGULATOR_SUPPLY("V-AMIC1", 0, 0), SND_SOC_DAPM_REGULATOR_SUPPLY("V-AMIC2", 0, 0), - SND_SOC_DAPM_REGULATOR_SUPPLY("V-DMIC", 0, 0), /* Power */ SND_SOC_DAPM_SUPPLY("Audio Power", @@ -324,7 +323,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_AIF_OUT("AD_OUT1", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AD_OUT2", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AD_OUT3", NULL, 0, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_AIF_OUT("AD_OUT4", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AD_OUT57", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AD_OUT68", NULL, 0, SND_SOC_NOPM, 0, 0), @@ -584,36 +582,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("AD5768 Enable", AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD5768, 0, NULL, 0), - /* Digital Microphone path */ - - SND_SOC_DAPM_INPUT("DMic 1"), - SND_SOC_DAPM_INPUT("DMic 2"), - SND_SOC_DAPM_INPUT("DMic 3"), - SND_SOC_DAPM_INPUT("DMic 4"), - SND_SOC_DAPM_INPUT("DMic 5"), - SND_SOC_DAPM_INPUT("DMic 6"), - - SND_SOC_DAPM_MIXER("DMIC1", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC1, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC2", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC2, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC3", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC3, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC4", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC4, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC5", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC5, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC6", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC6, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("AD4 Channel Volume", - SND_SOC_NOPM, 0, 0, - NULL, 0), /* Acoustical Noise Cancellation path */ SND_SOC_DAPM_MUX("ANC Source", @@ -648,6 +616,40 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { NULL, 0), }; +static const struct snd_soc_dapm_widget ab8500_dmic_dapm_widgets[] = { + SND_SOC_DAPM_REGULATOR_SUPPLY("V-DMIC", 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT4", NULL, 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_INPUT("DMic 1"), + SND_SOC_DAPM_INPUT("DMic 2"), + SND_SOC_DAPM_INPUT("DMic 3"), + SND_SOC_DAPM_INPUT("DMic 4"), + SND_SOC_DAPM_INPUT("DMic 5"), + SND_SOC_DAPM_INPUT("DMic 6"), + + SND_SOC_DAPM_MIXER("DMIC1", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC1, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC2", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC2, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC3", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC3, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC4", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC4, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC5", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC5, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC6", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC6, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("AD4 Channel Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), +}; + /* * DAPM-routes */ @@ -853,35 +855,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"AD_OUT68", NULL, "Main Supply"}, {"AD_OUT68", NULL, "AD6 Channel Volume"}, - /* Digital Microphone path */ - - {"DMic 1", NULL, "V-DMIC"}, - {"DMic 2", NULL, "V-DMIC"}, - {"DMic 3", NULL, "V-DMIC"}, - {"DMic 4", NULL, "V-DMIC"}, - {"DMic 5", NULL, "V-DMIC"}, - {"DMic 6", NULL, "V-DMIC"}, - - {"DMIC1", NULL, "DMic 1"}, - {"DMIC2", NULL, "DMic 2"}, - {"DMIC3", NULL, "DMic 3"}, - {"DMIC4", NULL, "DMic 4"}, - {"DMIC5", NULL, "DMic 5"}, - {"DMIC6", NULL, "DMic 6"}, - - {"AD1 Source Select", "DMic 1", "DMIC1"}, - {"AD2 Source Select", "DMic 2", "DMIC2"}, - {"AD3 Source Select", "DMic 3", "DMIC3"}, - {"AD5 Source Select", "DMic 5", "DMIC5"}, - {"AD6 Source Select", "DMic 6", "DMIC6"}, - - {"AD4 Channel Volume", NULL, "DMIC4"}, - {"AD4 Channel Volume", NULL, "AD34 Enable"}, - - {"ab8500_0c", NULL, "AD_OUT4"}, - {"AD_OUT4", NULL, "Main Supply"}, - {"AD_OUT4", NULL, "AD4 Channel Volume"}, - /* LineIn Bypass path */ {"LINL to HSL Volume", NULL, "LINL Enable"}, @@ -911,7 +884,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"Sidetone Left Source", "Headset Left", "DA_IN1"}, {"Sidetone Right Source", "LineIn Right", "AD2 Channel Volume"}, {"Sidetone Right Source", "Mic 1", "AD3 Channel Volume"}, - {"Sidetone Right Source", "DMic 4", "AD4 Channel Volume"}, {"Sidetone Right Source", "Headset Right", "DA_IN2"}, {"STFIR1 Control", NULL, "Sidetone Left Source"}, @@ -924,6 +896,37 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"DA2 Enable", NULL, "STFIR2 Volume"}, }; +static const struct snd_soc_dapm_route ab8500_dmic_dapm_routes[] = { + {"DMic 1", NULL, "V-DMIC"}, + {"DMic 2", NULL, "V-DMIC"}, + {"DMic 3", NULL, "V-DMIC"}, + {"DMic 4", NULL, "V-DMIC"}, + {"DMic 5", NULL, "V-DMIC"}, + {"DMic 6", NULL, "V-DMIC"}, + + {"DMIC1", NULL, "DMic 1"}, + {"DMIC2", NULL, "DMic 2"}, + {"DMIC3", NULL, "DMic 3"}, + {"DMIC4", NULL, "DMic 4"}, + {"DMIC5", NULL, "DMic 5"}, + {"DMIC6", NULL, "DMic 6"}, + + {"AD1 Source Select", "DMic 1", "DMIC1"}, + {"AD2 Source Select", "DMic 2", "DMIC2"}, + {"AD3 Source Select", "DMic 3", "DMIC3"}, + {"AD5 Source Select", "DMic 5", "DMIC5"}, + {"AD6 Source Select", "DMic 6", "DMIC6"}, + + {"AD4 Channel Volume", NULL, "DMIC4"}, + {"AD4 Channel Volume", NULL, "AD34 Enable"}, + + {"ab8500_0c", NULL, "AD_OUT4"}, + {"AD_OUT4", NULL, "Main Supply"}, + {"AD_OUT4", NULL, "AD4 Channel Volume"}, + + {"Sidetone Right Source", "DMic 4", "AD4 Channel Volume"}, +}; + static const struct snd_soc_dapm_route ab8500_dapm_routes_mic1a_vamicx[] = { {"MIC1A V-AMICx Enable", NULL, "V-AMIC1"}, {"MIC1A V-AMICx Enable", NULL, "V-AMIC2"}, @@ -2056,13 +2059,30 @@ static int ab8500_codec_probe(struct snd_soc_component *component) { struct device *dev = component->dev; struct device_node *np = dev->of_node; + struct ab8500 *ab8500 = dev_get_drvdata(dev->parent); struct ab8500_codec_platform_data codec_pdata; + struct snd_soc_dapm_context *dapm; int status; dev_dbg(dev, "%s: Enter.\n", __func__); ab8500_codec_of_probe(dev, np, &codec_pdata); + if (is_ab8500(ab8500)) { + dapm = snd_soc_component_to_dapm(component); + status = snd_soc_dapm_new_controls(dapm, + ab8500_dmic_dapm_widgets, + ARRAY_SIZE(ab8500_dmic_dapm_widgets)); + if (status) + return status; + + status = snd_soc_dapm_add_routes(dapm, + ab8500_dmic_dapm_routes, + ARRAY_SIZE(ab8500_dmic_dapm_routes)); + if (status) + return status; + } + status = ab8500_audio_init_audioblock(component); if (status < 0) { dev_err(dev, "%s: failed to init audio-block (%d)!\n", From f25d142baa23a94a16e3da20715f69f1166826ea Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 18 Sep 2026 00:51:03 +0200 Subject: [PATCH 264/924] ASoC: ux500: Remove the MOP500 machine driver The Ux500 boards now use audio-graph-card2, so remove the machine driver and its obsolete binding. The generic card obtains the DAI format, clock roles, TDM layout and board routing from the device tree graphs. Make the codec, MSP and DMA platform components part of the Ux500 ASoC selection. These providers were previously selected indirectly by the machine driver. Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260918-ux500-simple-sound-v2-5-88d29416e3c3@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/Kconfig | 23 +- sound/soc/ux500/Makefile | 3 - sound/soc/ux500/mop500.c | 167 ------------ sound/soc/ux500/mop500_ab8500.c | 437 -------------------------------- sound/soc/ux500/mop500_ab8500.h | 17 -- 5 files changed, 7 insertions(+), 640 deletions(-) delete mode 100644 sound/soc/ux500/mop500.c delete mode 100644 sound/soc/ux500/mop500_ab8500.c delete mode 100644 sound/soc/ux500/mop500_ab8500.h diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig index 113058060350..f3c7c216267c 100644 --- a/sound/soc/ux500/Kconfig +++ b/sound/soc/ux500/Kconfig @@ -6,28 +6,19 @@ menuconfig SND_SOC_UX500 tristate "Ux500" depends on SND_SOC depends on MFD_DB8500_PRCMU + depends on AB8500_CORE && AB8500_GPADC + select SND_SOC_AB8500_CODEC + select SND_SOC_UX500_PLAT_MSP_I2S + select SND_SOC_UX500_PLAT_DMA help - Say Y if you want to enable ASoC-support for - any of the Ux500 platforms (e.g. U8500). + Say Y if you want to enable ASoC support for Ux500 platforms + using the AB8500 or AB8505 codec. config SND_SOC_UX500_PLAT_MSP_I2S tristate depends on SND_SOC_UX500 config SND_SOC_UX500_PLAT_DMA - tristate "Platform - DB8500 (DMA)" + tristate depends on SND_SOC_UX500 select SND_SOC_GENERIC_DMAENGINE_PCM - help - Say Y if you want to enable the Ux500 platform-driver. - -config SND_SOC_UX500_MACH_MOP500 - tristate "Machine - MOP500 (Ux500 + AB8500)" - depends on AB8500_CORE && AB8500_GPADC && SND_SOC_UX500 - select SND_SOC_AB8500_CODEC - select SND_SOC_UX500_PLAT_MSP_I2S - select SND_SOC_UX500_PLAT_DMA - help - Select this to enable the MOP500 machine-driver. - This will enable platform-drivers for: Ux500 - This will enable codec-drivers for: AB8500 diff --git a/sound/soc/ux500/Makefile b/sound/soc/ux500/Makefile index a63787d9d664..83e7aeff70f8 100644 --- a/sound/soc/ux500/Makefile +++ b/sound/soc/ux500/Makefile @@ -6,6 +6,3 @@ obj-$(CONFIG_SND_SOC_UX500_PLAT_MSP_I2S) += snd-soc-ux500-plat-msp-i2s.o snd-soc-ux500-plat-dma-y := ux500_pcm.o obj-$(CONFIG_SND_SOC_UX500_PLAT_DMA) += snd-soc-ux500-plat-dma.o - -snd-soc-ux500-mach-mop500-y := mop500.o mop500_ab8500.o -obj-$(CONFIG_SND_SOC_UX500_MACH_MOP500) += snd-soc-ux500-mach-mop500.o diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c deleted file mode 100644 index 6d196b4b8802..000000000000 --- a/sound/soc/ux500/mop500.c +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Author: Ola Lilja (ola.o.lilja@stericsson.com) - * for ST-Ericsson. - */ - -#include - -#include -#include -#include -#include - -#include -#include - -#include "ux500_pcm.h" -#include "ux500_msp_dai.h" - -#include "mop500_ab8500.h" - -/* Define the whole MOP500 soundcard, linking platform to the codec-drivers */ -SND_SOC_DAILINK_DEFS(link1, - DAILINK_COMP_ARRAY(COMP_CPU("ux500-msp-i2s.1")), - DAILINK_COMP_ARRAY(COMP_CODEC("ab8500-codec.0", "ab8500-codec-dai.0")), - DAILINK_COMP_ARRAY(COMP_PLATFORM("ux500-msp-i2s.1"))); - -SND_SOC_DAILINK_DEFS(link2, - DAILINK_COMP_ARRAY(COMP_CPU("ux500-msp-i2s.3")), - DAILINK_COMP_ARRAY(COMP_CODEC("ab8500-codec.0", "ab8500-codec-dai.1")), - DAILINK_COMP_ARRAY(COMP_PLATFORM("ux500-msp-i2s.3"))); - -static struct snd_soc_dai_link mop500_dai_links[] = { - { - .name = "ab8500_0", - .stream_name = "ab8500_0", - .init = mop500_ab8500_machine_init, - .ops = mop500_ab8500_ops, - SND_SOC_DAILINK_REG(link1), - }, - { - .name = "ab8500_1", - .stream_name = "ab8500_1", - .init = NULL, - .ops = mop500_ab8500_ops, - SND_SOC_DAILINK_REG(link2), - }, -}; - -static struct snd_soc_card mop500_card = { - .name = "MOP500-card", - .owner = THIS_MODULE, - .probe = NULL, - .dai_link = mop500_dai_links, - .num_links = ARRAY_SIZE(mop500_dai_links), -}; - -static void mop500_of_node_put(void) -{ - int i; - - for (i = 0; i < 2; i++) - of_node_put(mop500_dai_links[i].cpus->of_node); - - /* Both links use the same codec, which is refcounted only once */ - of_node_put(mop500_dai_links[0].codecs->of_node); -} - -static int mop500_of_probe(struct snd_soc_card *card) -{ - struct device *dev = card->dev; - struct device_node *codec_np, *msp_np[2]; - struct device_node *np = dev->of_node; - int i; - - msp_np[0] = of_parse_phandle(np, "stericsson,cpu-dai", 0); - msp_np[1] = of_parse_phandle(np, "stericsson,cpu-dai", 1); - codec_np = of_parse_phandle(np, "stericsson,audio-codec", 0); - - if (!(msp_np[0] && msp_np[1] && codec_np)) { - dev_err(dev, "Phandle missing or invalid\n"); - for (i = 0; i < 2; i++) - of_node_put(msp_np[i]); - of_node_put(codec_np); - return -EINVAL; - } - - for (i = 0; i < 2; i++) { - mop500_dai_links[i].cpus->of_node = msp_np[i]; - mop500_dai_links[i].cpus->dai_name = NULL; - mop500_dai_links[i].platforms->of_node = msp_np[i]; - mop500_dai_links[i].platforms->name = NULL; - mop500_dai_links[i].codecs->of_node = codec_np; - mop500_dai_links[i].codecs->name = NULL; - } - - snd_soc_of_parse_card_name(card, "stericsson,card-name"); - - return 0; -} - -static int mop500_probe(struct platform_device *pdev) -{ - int ret; - - dev_dbg(&pdev->dev, "%s: Enter.\n", __func__); - - mop500_card.dev = &pdev->dev; - - ret = mop500_of_probe(&mop500_card); - if (ret) - return ret; - - dev_dbg(&pdev->dev, "%s: Card %s: Set platform drvdata.\n", - __func__, mop500_card.name); - - snd_soc_card_set_drvdata(&mop500_card, NULL); - - dev_dbg(&pdev->dev, "%s: Card %s: num_links = %d\n", - __func__, mop500_card.name, mop500_card.num_links); - dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: name = %s\n", - __func__, mop500_card.name, mop500_card.dai_link[0].name); - dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: stream_name = %s\n", - __func__, mop500_card.name, - mop500_card.dai_link[0].stream_name); - - ret = snd_soc_register_card(&mop500_card); - if (ret) - dev_err(&pdev->dev, - "Error: snd_soc_register_card failed (%d)!\n", ret); - - return ret; -} - -static void mop500_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - - pr_debug("%s: Enter.\n", __func__); - - snd_soc_unregister_card(card); - mop500_ab8500_remove(card); - mop500_of_node_put(); -} - -static const struct of_device_id snd_soc_mop500_match[] = { - { .compatible = "stericsson,snd-soc-mop500", }, - {}, -}; -MODULE_DEVICE_TABLE(of, snd_soc_mop500_match); - -static struct platform_driver snd_soc_mop500_driver = { - .driver = { - .name = "snd-soc-mop500", - .of_match_table = snd_soc_mop500_match, - }, - .probe = mop500_probe, - .remove = mop500_remove, -}; - -module_platform_driver(snd_soc_mop500_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("ASoC MOP500 board driver"); -MODULE_AUTHOR("Ola Lilja"); diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c deleted file mode 100644 index feb683c55d11..000000000000 --- a/sound/soc/ux500/mop500_ab8500.c +++ /dev/null @@ -1,437 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Author: Ola Lilja , - * Kristoffer Karlsson - * for ST-Ericsson. - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "ux500_pcm.h" -#include "ux500_msp_dai.h" -#include "mop500_ab8500.h" -#include "../codecs/ab8500-codec.h" - -#define TX_SLOT_MONO 0x0008 -#define TX_SLOT_STEREO 0x000a -#define RX_SLOT_MONO 0x0001 -#define RX_SLOT_STEREO 0x0003 -#define TX_SLOT_8CH 0x00FF -#define RX_SLOT_8CH 0x00FF - -#define DEF_TX_SLOTS TX_SLOT_STEREO -#define DEF_RX_SLOTS RX_SLOT_MONO - -#define DRIVERMODE_NORMAL 0 -#define DRIVERMODE_CODEC_ONLY 1 - -/* Slot configuration */ -static unsigned int tx_slots = DEF_TX_SLOTS; -static unsigned int rx_slots = DEF_RX_SLOTS; - -/* Configuration consistency parameters */ -static DEFINE_MUTEX(mop500_ab8500_params_lock); -static unsigned long mop500_ab8500_usage; -static int mop500_ab8500_rate; -static int mop500_ab8500_channels; - -/* Clocks */ -static const char * const enum_mclk[] = { - "SYSCLK", - "ULPCLK" -}; -enum mclk { - MCLK_SYSCLK, - MCLK_ULPCLK, -}; - -static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_mclk, enum_mclk); - -/* Private data for machine-part MOP500<->AB8500 */ -struct mop500_ab8500_drvdata { - /* Clocks */ - enum mclk mclk_sel; - struct clk *clk_ptr_intclk; - struct clk *clk_ptr_sysclk; - struct clk *clk_ptr_ulpclk; -}; - -static inline const char *get_mclk_str(enum mclk mclk_sel) -{ - switch (mclk_sel) { - case MCLK_SYSCLK: - return "SYSCLK"; - case MCLK_ULPCLK: - return "ULPCLK"; - default: - return "Unknown"; - } -} - -static int mop500_ab8500_set_mclk(struct device *dev, - struct mop500_ab8500_drvdata *drvdata) -{ - int status; - struct clk *clk_ptr; - - if (IS_ERR(drvdata->clk_ptr_intclk)) { - dev_err(dev, - "%s: ERROR: intclk not initialized!\n", __func__); - return -EIO; - } - - switch (drvdata->mclk_sel) { - case MCLK_SYSCLK: - clk_ptr = drvdata->clk_ptr_sysclk; - break; - case MCLK_ULPCLK: - clk_ptr = drvdata->clk_ptr_ulpclk; - break; - default: - return -EINVAL; - } - - if (IS_ERR(clk_ptr)) { - dev_err(dev, "%s: ERROR: %s not initialized!\n", __func__, - get_mclk_str(drvdata->mclk_sel)); - return -EIO; - } - - status = clk_set_parent(drvdata->clk_ptr_intclk, clk_ptr); - if (status) - dev_err(dev, - "%s: ERROR: Setting intclk parent to %s failed (ret = %d)!", - __func__, get_mclk_str(drvdata->mclk_sel), status); - else - dev_dbg(dev, - "%s: intclk parent changed to %s.\n", - __func__, get_mclk_str(drvdata->mclk_sel)); - - return status; -} - -/* - * Control-events - */ - -static int mclk_input_control_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); - struct mop500_ab8500_drvdata *drvdata = - snd_soc_card_get_drvdata(card); - - ucontrol->value.enumerated.item[0] = drvdata->mclk_sel; - - return 0; -} - -static int mclk_input_control_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); - struct mop500_ab8500_drvdata *drvdata = - snd_soc_card_get_drvdata(card); - unsigned int val = ucontrol->value.enumerated.item[0]; - - if (val > (unsigned int)MCLK_ULPCLK) - return -EINVAL; - if (drvdata->mclk_sel == val) - return 0; - - drvdata->mclk_sel = val; - - return 1; -} - -/* - * Controls - */ - -static struct snd_kcontrol_new mop500_ab8500_ctrls[] = { - SOC_ENUM_EXT("Master Clock Select", - soc_enum_mclk, - mclk_input_control_get, mclk_input_control_put), - SOC_DAPM_PIN_SWITCH("Headset Left"), - SOC_DAPM_PIN_SWITCH("Headset Right"), - SOC_DAPM_PIN_SWITCH("Earpiece"), - SOC_DAPM_PIN_SWITCH("Speaker Left"), - SOC_DAPM_PIN_SWITCH("Speaker Right"), - SOC_DAPM_PIN_SWITCH("LineOut Left"), - SOC_DAPM_PIN_SWITCH("LineOut Right"), - SOC_DAPM_PIN_SWITCH("Vibra 1"), - SOC_DAPM_PIN_SWITCH("Vibra 2"), - SOC_DAPM_PIN_SWITCH("Mic 1"), - SOC_DAPM_PIN_SWITCH("Mic 2"), - SOC_DAPM_PIN_SWITCH("LineIn Left"), - SOC_DAPM_PIN_SWITCH("LineIn Right"), - SOC_DAPM_PIN_SWITCH("DMic 1"), - SOC_DAPM_PIN_SWITCH("DMic 2"), - SOC_DAPM_PIN_SWITCH("DMic 3"), - SOC_DAPM_PIN_SWITCH("DMic 4"), - SOC_DAPM_PIN_SWITCH("DMic 5"), - SOC_DAPM_PIN_SWITCH("DMic 6"), -}; - -/* ASoC */ - -static int mop500_ab8500_startup(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - - /* Set audio-clock source */ - return mop500_ab8500_set_mclk(rtd->card->dev, - snd_soc_card_get_drvdata(rtd->card)); -} - -static void mop500_ab8500_shutdown(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - struct device *dev = rtd->card->dev; - - dev_dbg(dev, "%s: Enter\n", __func__); - - /* Reset slots configuration to default(s) */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - tx_slots = DEF_TX_SLOTS; - else - rx_slots = DEF_RX_SLOTS; -} - -static int mop500_ab8500_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 device *dev = rtd->card->dev; - unsigned int fmt; - int channels, ret = 0, driver_mode, slots; - unsigned int sw_codec, sw_cpu; - bool is_playback; - - dev_dbg(dev, "%s: Enter\n", __func__); - - dev_dbg(dev, "%s: substream->pcm->name = %s\n" - "substream->pcm->id = %s.\n" - "substream->name = %s.\n" - "substream->number = %d.\n", - __func__, - substream->pcm->name, - substream->pcm->id, - substream->name, - substream->number); - - /* Ensure configuration consistency between DAIs */ - scoped_guard(mutex, &mop500_ab8500_params_lock) { - if (mop500_ab8500_usage) { - if (mop500_ab8500_rate != params_rate(params) || - mop500_ab8500_channels != params_channels(params)) { - return -EBUSY; - } - } else { - mop500_ab8500_rate = params_rate(params); - mop500_ab8500_channels = params_channels(params); - } - __set_bit(cpu_dai->id, &mop500_ab8500_usage); - } - - channels = params_channels(params); - - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S32_LE: - sw_cpu = 32; - break; - - case SNDRV_PCM_FORMAT_S16_LE: - sw_cpu = 16; - break; - - default: - return -EINVAL; - } - - /* Setup codec depending on driver-mode */ - if (channels == 8) - driver_mode = DRIVERMODE_CODEC_ONLY; - else - driver_mode = DRIVERMODE_NORMAL; - dev_dbg(dev, "%s: Driver-mode: %s.\n", __func__, - (driver_mode == DRIVERMODE_NORMAL) ? "NORMAL" : "CODEC_ONLY"); - - /* Setup format */ - - if (driver_mode == DRIVERMODE_NORMAL) { - fmt = SND_SOC_DAIFMT_DSP_A | - SND_SOC_DAIFMT_CBP_CFP | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CONT; - } else { - fmt = SND_SOC_DAIFMT_DSP_A | - SND_SOC_DAIFMT_CBP_CFP | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_GATED; - } - - ret = snd_soc_runtime_set_dai_fmt(rtd, fmt); - if (ret) - return ret; - - /* Setup TDM-slots */ - - is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); - switch (channels) { - case 1: - slots = 16; - tx_slots = (is_playback) ? TX_SLOT_MONO : 0; - rx_slots = (is_playback) ? 0 : RX_SLOT_MONO; - break; - case 2: - slots = 16; - tx_slots = (is_playback) ? TX_SLOT_STEREO : 0; - rx_slots = (is_playback) ? 0 : RX_SLOT_STEREO; - break; - case 8: - slots = 16; - tx_slots = (is_playback) ? TX_SLOT_8CH : 0; - rx_slots = (is_playback) ? 0 : RX_SLOT_8CH; - break; - default: - return -EINVAL; - } - - if (driver_mode == DRIVERMODE_NORMAL) - sw_codec = sw_cpu; - else - sw_codec = 20; - - dev_dbg(dev, "%s: CPU-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__, - tx_slots, rx_slots); - ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_slots, rx_slots, slots, - sw_cpu); - if (ret) - return ret; - - dev_dbg(dev, "%s: CODEC-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__, - tx_slots, rx_slots); - ret = snd_soc_dai_set_tdm_slot(codec_dai, tx_slots, rx_slots, slots, - sw_codec); - if (ret) - return ret; - - return 0; -} - -static int mop500_ab8500_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); - - guard(mutex)(&mop500_ab8500_params_lock); - __clear_bit(cpu_dai->id, &mop500_ab8500_usage); - - return 0; -} - -const struct snd_soc_ops mop500_ab8500_ops[] = { - { - .hw_params = mop500_ab8500_hw_params, - .hw_free = mop500_ab8500_hw_free, - .startup = mop500_ab8500_startup, - .shutdown = mop500_ab8500_shutdown, - } -}; - -int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(rtd->card); - struct device *dev = rtd->card->dev; - struct mop500_ab8500_drvdata *drvdata; - int ret; - - dev_dbg(dev, "%s Enter.\n", __func__); - - /* Create driver private-data struct */ - drvdata = devm_kzalloc(dev, sizeof(struct mop500_ab8500_drvdata), - GFP_KERNEL); - - if (!drvdata) - return -ENOMEM; - - snd_soc_card_set_drvdata(rtd->card, drvdata); - - /* Setup clocks */ - - drvdata->clk_ptr_sysclk = clk_get(dev, "sysclk"); - if (IS_ERR(drvdata->clk_ptr_sysclk)) - dev_warn(dev, "%s: WARNING: clk_get failed for 'sysclk'!\n", - __func__); - drvdata->clk_ptr_ulpclk = clk_get(dev, "ulpclk"); - if (IS_ERR(drvdata->clk_ptr_ulpclk)) - dev_warn(dev, "%s: WARNING: clk_get failed for 'ulpclk'!\n", - __func__); - drvdata->clk_ptr_intclk = clk_get(dev, "intclk"); - if (IS_ERR(drvdata->clk_ptr_intclk)) - dev_warn(dev, "%s: WARNING: clk_get failed for 'intclk'!\n", - __func__); - - /* Set intclk default parent to ulpclk */ - drvdata->mclk_sel = MCLK_ULPCLK; - ret = mop500_ab8500_set_mclk(dev, drvdata); - if (ret < 0) - dev_warn(dev, "%s: WARNING: mop500_ab8500_set_mclk!\n", - __func__); - - drvdata->mclk_sel = MCLK_ULPCLK; - - /* Add controls */ - ret = snd_soc_add_card_controls(rtd->card, mop500_ab8500_ctrls, - ARRAY_SIZE(mop500_ab8500_ctrls)); - if (ret < 0) { - pr_err("%s: Failed to add machine-controls (%d)!\n", - __func__, ret); - return ret; - } - - ret = snd_soc_dapm_disable_pin(dapm, "Earpiece"); - ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Left"); - ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Right"); - ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Left"); - ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Right"); - ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 1"); - ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 2"); - ret |= snd_soc_dapm_disable_pin(dapm, "Mic 1"); - ret |= snd_soc_dapm_disable_pin(dapm, "Mic 2"); - ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Left"); - ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Right"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 1"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 2"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 3"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 4"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 5"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 6"); - - return ret; -} - -void mop500_ab8500_remove(struct snd_soc_card *card) -{ - struct mop500_ab8500_drvdata *drvdata = snd_soc_card_get_drvdata(card); - - clk_put(drvdata->clk_ptr_sysclk); - clk_put(drvdata->clk_ptr_ulpclk); - clk_put(drvdata->clk_ptr_intclk); - - snd_soc_card_set_drvdata(card, NULL); -} diff --git a/sound/soc/ux500/mop500_ab8500.h b/sound/soc/ux500/mop500_ab8500.h deleted file mode 100644 index 98de80a9cc4f..000000000000 --- a/sound/soc/ux500/mop500_ab8500.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Author: Ola Lilja - * for ST-Ericsson. - */ - -#ifndef MOP500_AB8500_H -#define MOP500_AB8500_H - -extern const struct snd_soc_ops mop500_ab8500_ops[]; - -int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd); -void mop500_ab8500_remove(struct snd_soc_card *card); - -#endif From beb56d1d3de522885b30bb97fee83825041d65c2 Mon Sep 17 00:00:00 2001 From: Karl Asseily Date: Fri, 18 Sep 2026 21:23:14 +0300 Subject: [PATCH 265/924] dt-bindings: vendor-prefixes: add ESS Technology Add the vendor prefix for ESS Technology, Inc., maker of the SABRE family of audio DACs. The prefix follows the company's domain, esstech.com. Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Asseily Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260918182317.143223-2-karl@asseily.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index ba2002969373..ff4a85f4cbd4 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -559,6 +559,8 @@ patternProperties: description: Seiko Epson Corp. "^esp,.*": description: Espressif Systems Co. Ltd. + "^esstech,.*": + description: ESS Technology, Inc. "^est,.*": description: ESTeem Wireless Modems "^eswin,.*": From ab1a02163fcdb2dbe5da603f97aceebe0c582708 Mon Sep 17 00:00:00 2001 From: Karl Asseily Date: Fri, 18 Sep 2026 21:23:15 +0300 Subject: [PATCH 266/924] ASoC: dt-bindings: add ESS Technology ES9039Q2M Add a binding for the ES9039Q2M, a 32-bit two-channel audio DAC with an asynchronous sample rate converter. The part selects between two control personalities with its MODE pin: hardware mode, strapped by HW0/HW1/HW2 with no control bus at all, and software mode over I2C or SPI. This binding describes software mode over I2C, which MODE = GND selects. The four supply inputs are described and required. DVDD is not among them: the part's 1.2V digital rail is generated on chip and the pin takes only a decoupling capacitor. The datasheet asks for the supplies in a set order - AVDD, then VCCA about 200us later, then the two output-stage references, and the reverse on the way down - which the description records, so a board with separately switchable rails has it to hand. Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Asseily Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260918182317.143223-3-karl@asseily.com Signed-off-by: Mark Brown --- .../bindings/sound/esstech,es9039q2m.yaml | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml diff --git a/Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml b/Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml new file mode 100644 index 000000000000..15db30af4b6c --- /dev/null +++ b/Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/esstech,es9039q2m.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ESS Technology ES9039Q2M audio DAC + +maintainers: + - Karl Asseily + +description: + The ES9039Q2M is a 32-bit two-channel audio DAC supporting PCM to 768 kHz, + DSD64 to DSD1024, DoP and S/PDIF. It is controlled over I2C or SPI when the + MODE pin selects software mode; this binding covers the I2C interface. The + part contains an asynchronous sample rate converter, so the system clock need + not be synchronous with the audio bit or frame clocks. + + The four supplies are all nominally 3.3V and the datasheet requires a set + order - AVDD first, then VCCA about 200us later, then AVCC_DAC1 and + AVCC_DAC2, and the reverse on the way down. DVDD, the part's 1.2V rail, is + on-chip and needs only its decoupling capacitor - it is not a supply input + and has no property here. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: esstech,es9039q2m + + reg: + description: + I2C address, selected by the ADDR0 and ADDR1 pins - 0x48, 0x49, 0x4a or + 0x4b for GND/GND, GND/AVDD, AVDD/GND and AVDD/AVDD respectively. + enum: [ 0x48, 0x49, 0x4a, 0x4b ] + + clocks: + maxItems: 1 + + clock-names: + const: mclk + + avdd-supply: + description: + 3.3V supply for the part's internal digital regulator, pin AVDD. It is + the first rail up and the last down, per the ordering described above. + + vcca-supply: + description: 3.3V analogue supply, pin VCCA. + + avcc-dac1-supply: + description: + 3.3V reference supply for the channel 1 analogue output stage, pin + AVCC_DAC1. The datasheet allows this rail to move by up to 15% where a + calibrating regulator tracks the DAC's output impedance. + + avcc-dac2-supply: + description: + 3.3V reference supply for the channel 2 analogue output stage, pin + AVCC_DAC2, with the same 15% allowance as channel 1. + + "#sound-dai-cells": + const: 0 + +required: + - compatible + - reg + - avdd-supply + - vcca-supply + - avcc-dac1-supply + - avcc-dac2-supply + - "#sound-dai-cells" + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + audio-codec@48 { + compatible = "esstech,es9039q2m"; + reg = <0x48>; + #sound-dai-cells = <0>; + clocks = <&dac_mclk>; + clock-names = "mclk"; + avdd-supply = <&dac_3v3>; + vcca-supply = <&dac_3v3>; + avcc-dac1-supply = <&dac_avcc1>; + avcc-dac2-supply = <&dac_avcc2>; + }; + }; From 004e260a54758de2cf813cb7fc63e45fe251c7fd Mon Sep 17 00:00:00 2001 From: Karl Asseily Date: Fri, 18 Sep 2026 21:23:16 +0300 Subject: [PATCH 267/924] ASoC: es9039q2m: add ESS Technology ES9039Q2M codec driver The ES9039Q2M is a 32-bit two-channel audio DAC with an asynchronous sample rate converter. It selects between two control personalities with its MODE pin: hardware mode, strapped by HW0/HW1/HW2 with no control bus at all, and software mode over I2C or SPI. This driver implements software mode over I2C, which MODE = GND selects. Three properties of the part shape the driver: - The ASRC in front of the DAC means MCLK need not be synchronous with BCLK or LRCK, so a board can feed it a fixed oscillator and never touch the clock again. The driver derives which case it is in rather than assuming one: the part runs synchronously when it drives the bus clocks itself, or when the MCLK it was given can be set to a multiple of the sample rate, and asynchronously only when a fixed oscillator feeds it as a clock consumer. The datasheet's MCLK floor differs between the two - 128 x Fs synchronous, 130 x Fs asynchronous - and where the clock is fixed a startup constraint derives the maximum rate from the MCLK actually present rather than advertising rates the hardware cannot honour. - Selecting an input format takes two registers, not one. Register 57 INPUT_SEL chooses which port to listen to; register 1 SYS MODE CONFIG enables the corresponding decoder, and at reset only ENABLE_TDM_DECODE is set. Selecting DoP without also enabling ENABLE_DOP_DECODE leaves the part hunting for a marker with the marker decoder switched off, so it finds no valid DoP and mutes. The driver sets both. - Several registers have non-zero reserved defaults - register 88 reads 0xb8 at reset - so every write is read-modify-write. The part has four supply inputs and the datasheet asks for them in a set order: AVDD, then VCCA about 200us later, then the two output-stage references AVCC_DAC1 and AVCC_DAC2, with power-down exactly reversed. The driver enables them one at a time for that reason rather than calling regulator_bulk_enable(), which makes no ordering guarantee; the bulk API still acquires them, where order does not matter. DVDD is not among them: the part's 1.2V digital rail is generated on chip and that pin takes only a decoupling capacitor. No settling time is inserted before the first register access because the datasheet specifies none. The datapath is off at reset and the driver turns it on at component probe. SYSTEM_CONFIG[1] is clear when the part comes out of reset, so in software mode nothing enables it at power-up and the analogue output stays dead until the driver writes that bit. It is written after the mute and never before it: bringing the datapath up into an unmuted part whose volume registers still sit at their 0 dB reset value is the thump the mute is there to prevent. Note when testing that a warm reboot does not reset this part - registers and coefficient RAM both survive one, and only removing power clears them - so the previous boot's value can hide whether the write happened at all. DoP is detected by the part rather than announced by the host: AUTO_INPUT_SEL is enabled and both the TDM and DoP decoders run, so a player can simply send DoP-encoded PCM. That is the only way DoP works in practice, because no player can select a DAC mode through a kcontrol first, and material sent to a part that is not looking for the marker renders as the near-silent hiss the DoP design deliberately degrades to. The programmable FIR coefficient controls are write-only by design. The part has a PROG_COEFF_OUT register described as "Programmable FIR coefficient readback", but it is not a RAM read port: it returns the last coefficient written, whatever address is selected in PROG_COEFF_ADDR. Measured by writing two different coefficients to addresses 0 and 1 and reading them back five ways - plain, with a settle delay, with PROG_COEFF_EN set, with the address written twice, and with a write-enable pulse after the address. All ten reads returned the value written to address 1. A get() built on that register would return something with the shape of data and none of its meaning, so there is none. Every control was verified against the silicon rather than against the driver's own read-back: written through ALSA, then read from the chip over raw I2C. That covers the eight filter shapes, the modulator bandwidth, the IIR bandwidth, the four signed 16-bit THD compensation coefficients at both extremes of their range, and the automute enable, time, level and off-level fields including the neighbouring MUTE_RAMP_TO_GND bit that shares a register with the 11-bit time field. The coefficient upload, whose RAM cannot be read back, was verified from the I2C bus itself. System suspend gates the part's internal clock tree. Where the part is clocked from the SoC, system suspend stops its clock as a side effect and there is nothing for a driver to do; where the board feeds it a free-running oscillator - which the ASRC makes an attractive design, since MCLK then need not track BCLK or LRCK - nothing in the system can gate that clock and the part stays fully clocked for the whole of suspend. Clearing ENABLE_DAC_CLK is the only part of its consumption software can reach; the analogue supplies are the board's business. Measured on an RK3588 board with a fixed 24.576 MHz oscillator, system suspend power fell from 1.98 W to 1.95 W and followed the register in both directions across an A/B. Resume mutes the part before regcache_sync(), because the sync walks registers in ascending address order. A board that keeps the supplies up needs nothing, but one that removes them brings the part back at reset defaults - DAC_MODE clear, both channels unmuted, both volume registers at 0 dB - and the sync would set DAC_MODE in register 0 and only restore the real mute in register 86, lighting the datapath up at full scale in between. Resume also re-uploads the programmable FIR coefficient RAM. regcache_sync() cannot restore it - the RAM is write-only, so nothing caches it - but the sync does restore the filter selection, so a board that removed the supplies would come back with the programmable filter selected and the RAM holding whatever it powered up with. The driver keeps a shadow of each stage it uploaded and replays it after the sync. Stages never written have no shadow and keep the part's own defaults. Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Asseily Link: https://patch.msgid.link/20260918182317.143223-4-karl@asseily.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 14 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/es9039q2m.c | 2008 ++++++++++++++++++++++++++++++++++ 3 files changed, 2024 insertions(+) create mode 100644 sound/soc/codecs/es9039q2m.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index d3730c4da51b..6352f6b5acfb 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -123,6 +123,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_ES8328_I2C imply SND_SOC_ES8375 imply SND_SOC_ES8389 + imply SND_SOC_ES9039Q2M imply SND_SOC_ES9356 imply SND_SOC_ES7134 imply SND_SOC_ES7241 @@ -1317,6 +1318,19 @@ config SND_SOC_ES8389 tristate "Everest Semi ES8389 CODEC" depends on I2C +config SND_SOC_ES9039Q2M + tristate "ESS Technology ES9039Q2M CODEC" + depends on I2C + select REGMAP_I2C + help + Enable support for the ESS Technology ES9039Q2M, a 32-bit + two-channel audio DAC with an asynchronous sample rate converter, + supporting PCM, DSD, DoP and S/PDIF input. This driver covers the + I2C control interface, which the MODE pin selects. + + To compile this driver as a module, choose M here: the module + will be called snd-soc-es9039q2m. + config SND_SOC_ES9356 tristate "Everest Semi ES9356 CODEC SDW" depends on SND_SOC_SDCA diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 9287a602d41e..1679c3ffc634 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -140,6 +140,7 @@ snd-soc-es8328-i2c-y := es8328-i2c.o snd-soc-es8328-spi-y := es8328-spi.o snd-soc-es8375-y := es8375.o snd-soc-es8389-y := es8389.o +snd-soc-es9039q2m-y := es9039q2m.o snd-soc-es9356-y := es9356.o snd-soc-framer-y := framer-codec.o snd-soc-fs-amp-lib-y := fs-amp-lib.o @@ -586,6 +587,7 @@ obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o obj-$(CONFIG_SND_SOC_ES8375) += snd-soc-es8375.o obj-$(CONFIG_SND_SOC_ES8389) += snd-soc-es8389.o +obj-$(CONFIG_SND_SOC_ES9039Q2M) += snd-soc-es9039q2m.o obj-$(CONFIG_SND_SOC_ES9356) += snd-soc-es9356.o obj-$(CONFIG_SND_SOC_FRAMER) += snd-soc-framer.o obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+= snd-soc-fs-amp-lib.o diff --git a/sound/soc/codecs/es9039q2m.c b/sound/soc/codecs/es9039q2m.c new file mode 100644 index 000000000000..4e9f86cb7860 --- /dev/null +++ b/sound/soc/codecs/es9039q2m.c @@ -0,0 +1,2008 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// ESS Technology ES9039Q2M 32-bit 2-channel audio DAC +// +// Copyright (C) 2026 Karl Asseily +// +// Every register number, bit field and default in this file was taken from +// ES9039Q2M datasheet v0.2.3 and then verified by reading the defaults back off +// a live part over I2C. +// +// The part has two control personalities selected by the MODE pin: hardware +// mode (strapped by HW0/HW1/HW2, no bus at all) and software mode (I2C or SPI). +// This driver implements software mode over I2C, which MODE = GND selects. +// +// Three properties shape the driver: +// +// - There is an ASRC in front of the DAC, so MCLK need not be synchronous with +// BCLK or LRCK. A board may feed it a fixed oscillator, or may run it +// synchronously and let the part generate BCLK and WS; both are supported. +// +// - INPUT_SEL chooses PCM / DSD / DoP / S/PDIF. It does NOT choose I2S vs +// left-justified - that is TDM_LJ_MODE in register 60, and there is no +// right-justified mode to map onto at all. +// +// - Several registers have non-zero reserved defaults (register 88 reads +// 0xb8 at reset), so every write here is read-modify-write. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* ------------------------------------------------- read/write registers ---- */ + +#define ES9039_SYSTEM_CONFIG 0x00 /* reg 0 */ +#define ES9039_DAC_MODE BIT(1) /* CLEAR at reset - datapath off */ +#define ES9039_64FS_MODE BIT(6) + +/* + * Register 1 selects which DECODERS are running, and it is separate from + * INPUT_SEL in register 57, which only says which port to listen to. Both are + * needed: on reset only ENABLE_TDM_DECODE is set, so selecting DoP as the input + * while leaving bit 2 clear leaves the part hunting for a marker with the + * marker decoder switched off. It then finds no valid DoP and mutes - silence, + * DOP_VALID reading 0, and nothing anywhere saying why. Measured on hardware. + */ +#define ES9039_SYS_MODE 0x01 /* reg 1, reset 0xb1 */ +#define ES9039_ENABLE_TDM_DECODE BIT(0) /* set at reset */ +#define ES9039_ENABLE_DSD_DECODE BIT(1) +#define ES9039_ENABLE_DOP_DECODE BIT(2) +#define ES9039_ENABLE_SPDIF_DECODE BIT(3) +#define ES9039_SYNC_MODE BIT(6) /* 0 = ASYNC */ +#define ES9039_ENABLE_DAC_CLK BIT(7) /* set at reset */ +/* + * Every decoder the part has. hw_params() enables one of these and clears the + * rest, so leaving S/PDIF out of the mask would have left its decoder running + * alongside the selected one. + */ +#define ES9039_DECODE_MASK (ES9039_ENABLE_TDM_DECODE | \ + ES9039_ENABLE_DSD_DECODE | \ + ES9039_ENABLE_DOP_DECODE | \ + ES9039_ENABLE_SPDIF_DECODE) + +#define ES9039_AUTO_FS_DETECT 0x03 /* reg 3 */ +#define ES9039_AUTO_FS_DETECT_EN BIT(7) + +#define ES9039_CLOCK_CONFIG 0x04 /* reg 4, MASTER_BCK_DIV */ + +#define ES9039_INPUT_SEL 0x39 /* reg 57 */ +#define ES9039_AUTO_INPUT_SEL BIT(0) +#define ES9039_INPUT_SEL_MASK GENMASK(2, 1) +#define ES9039_INPUT_PCM 0x0 +#define ES9039_INPUT_DSD 0x1 +#define ES9039_INPUT_DOP 0x2 +#define ES9039_INPUT_SPDIF 0x3 +#define ES9039_PCM_MASTER_MODE BIT(4) +#define ES9039_DSD_MASTER_MODE BIT(5) +#define ES9039_DSD_FAULT_DETECT BIT(6) /* set at reset */ + +#define ES9039_MASTER_ENC 0x3a /* reg 58 */ +#define ES9039_BCK_INV BIT(6) + +#define ES9039_TDM_CH_NUM 0x3b /* reg 59, slots = value + 1 */ +#define ES9039_TDM_CH_NUM_MASK GENMASK(4, 0) + +#define ES9039_TDM_CONFIG1 0x3c /* reg 60 */ +#define ES9039_TDM_VALID_EDGE BIT(6) +#define ES9039_TDM_LJ_MODE BIT(7) /* 0 = standard I2S */ + +#define ES9039_TDM_CONFIG2 0x3d /* reg 61 */ +#define ES9039_TDM_BIT_WIDTH_MASK GENMASK(6, 5) +#define ES9039_WIDTH_32 0x0 +#define ES9039_WIDTH_24 0x1 +#define ES9039_WIDTH_16 0x2 + +#define ES9039_MONITOR_CFG 0x3e /* reg 62 */ +#define ES9039_DISABLE_PCM_DC BIT(3) +#define ES9039_ENABLE_BCK_MONITOR BIT(4) /* set at reset */ +#define ES9039_ENABLE_WS_MONITOR BIT(5) /* set at reset */ +#define ES9039_DISABLE_DSD_MUTE BIT(6) +#define ES9039_DISABLE_DSD_DC BIT(7) + +#define ES9039_VOLUME_CH1 0x4a /* reg 74, 0x00 = 0 dB */ +#define ES9039_VOLUME_CH2 0x4b /* reg 75, 0xff = -127.5 dB */ +#define ES9039_VOL_MAX 0xff + +#define ES9039_VOL_RATE_UP 0x52 /* reg 82 */ +#define ES9039_VOL_RATE_DOWN 0x53 /* reg 83 */ + +#define ES9039_DAC_MUTE 0x56 /* reg 86, 1 = muted */ +#define ES9039_MUTE_CH1 BIT(0) +#define ES9039_MUTE_CH2 BIT(1) +#define ES9039_MUTE_BOTH (ES9039_MUTE_CH1 | ES9039_MUTE_CH2) + +#define ES9039_DAC_INVERT 0x57 /* reg 87 */ + +#define ES9039_FILTER_SHAPE 0x58 /* reg 88, [7:3] reset to 10111 */ +#define ES9039_FILTER_SHAPE_MASK GENMASK(2, 0) +#define ES9039_FILTER_APODIZING 1 /* linear phase apodizing fast */ + +#define ES9039_IIR_SPDIF 0x59 /* reg 89 */ +#define ES9039_IIR_BW_MASK GENMASK(2, 0) +#define ES9039_VOLUME_HOLD BIT(3) +#define ES9039_SPDIF_SEL_MASK GENMASK(7, 4) + +#define ES9039_DAC_PATH 0x5a /* reg 90 */ +#define ES9039_BYPASS_FIR2X BIT(0) +#define ES9039_BYPASS_FIR4X BIT(1) +#define ES9039_BYPASS_IIR BIT(2) + +#define ES9039_THD_C2 0x5b /* regs 91-94: CH1 lo, CH2 hi */ +#define ES9039_THD_C3 0x6b /* regs 107-110 */ + +#define ES9039_AUTOMUTE_EN 0x7b /* reg 123, both set at reset */ +#define ES9039_AUTOMUTE_TIME 0x7c /* regs 124-125 */ +#define ES9039_AUTOMUTE_TIME_MASK GENMASK(10, 0) +#define ES9039_MUTE_RAMP_TO_GND BIT(11) /* set at reset */ +#define ES9039_AUTOMUTE_LEVEL 0x7e /* regs 126-127 */ +#define ES9039_AUTOMUTE_OFF_LEVEL 0x80 /* regs 128-129 */ + +#define ES9039_SOFT_RAMP 0x82 /* reg 130, valid 0..12 */ +#define ES9039_SOFT_RAMP_MASK GENMASK(4, 0) +#define ES9039_SOFT_RAMP_MAX 12 + +#define ES9039_NSMOD 0x83 /* reg 131 */ +#define ES9039_NSMOD_WIDE_BW_MASK GENMASK(4, 1) +#define ES9039_NSMOD_DEFAULT 0x4 +#define ES9039_NSMOD_WIDE 0xc + +#define ES9039_PROG_RAM_CTRL 0x87 /* reg 135 */ +#define ES9039_PROG_COEFF_EN BIT(0) +#define ES9039_PROG_COEFF_WE BIT(1) + +#define ES9039_PROG_RAM_ADDR 0x89 /* reg 137 */ +#define ES9039_PROG_ADDR_MASK GENMASK(6, 0) +#define ES9039_PROG_STAGE_4X BIT(7) + +#define ES9039_PROG_RAM_DATA 0x8a /* regs 138-140, 24-bit signed */ + +#define ES9039_LAST_RW 0x8e /* reg 145 */ + +/* ----------------------------------------------------- readback registers -- */ + +#define ES9039_READBACK_BASE 0xe0 /* reg 224 */ + +#define ES9039_CHIP_ID 0xe1 /* reg 225 */ +#define ES9039_CHIP_ID_ES9039Q2M 0x63 + +#define ES9039_IRQ_SOURCES 0xea /* regs 234-235, 16-bit */ +#define ES9039_SRC_VOL_MIN_MASK GENMASK(1, 0) +#define ES9039_SRC_AUTOMUTE_MASK GENMASK(3, 2) +#define ES9039_SRC_SS_RAMP_MASK GENMASK(5, 4) +#define ES9039_SRC_DOP_VALID BIT(6) +#define ES9039_SRC_BCK_WS_FAIL BIT(7) +#define ES9039_SRC_TDM_VALID BIT(11) + +#define ES9039_AUTO_FS_READ 0xef /* reg 239 */ +#define ES9039_FS_DIV_MASK GENMASK(5, 0) +#define ES9039_FS_HALF_DIV BIT(6) +#define ES9039_FS_DIV_VALID BIT(7) + +#define ES9039_AUTOMUTE_READ 0xf2 /* reg 242 */ + +#define ES9039_INPUT_STREAM_READ 0xf5 /* reg 245 */ +#define ES9039_RD_INPUT_SEL_MASK GENMASK(1, 0) +#define ES9039_RD_DOP_VALID BIT(2) +#define ES9039_RD_TDM_VALID BIT(3) +#define ES9039_RD_SPDIF_VALID BIT(4) + +#define ES9039_MAX_REGISTER 0xfb /* reg 251 */ + +/* Programmable oversampling FIR: 128 taps in the 2x stage, 32 in the 4x. */ +#define ES9039_FIR2X_TAPS 128 +#define ES9039_FIR4X_TAPS 32 +#define ES9039_COEFF_BYTES 3 +#define ES9039_FIR_STAGES 2 /* [0] = 2x stage, [1] = 4x */ + +/* In power-up order. Reversed on the way down. Datasheet figure 22. */ +#define ES9039_NUM_SUPPLIES 4 +#define ES9039_FIR_MAX_BYTES (ES9039_FIR2X_TAPS * ES9039_COEFF_BYTES) + +/* ------------------------------------------------------------------ private */ + +struct es9039q2m_priv { + struct regmap *regmap; + struct clk *mclk; + unsigned int mclk_rate; + unsigned int fmt; + unsigned int stream_rate; /* last rate from hw_params */ + unsigned int bclk_ratio; /* bit clocks per frame, 0 = unknown */ + + /* + * Serialises dop_auto against hw_params(), which reads it while + * deciding what to program. Without it a control write racing a + * stream start can leave the two DoP registers disagreeing. + */ + struct mutex lock; + bool dop_auto; /* let the part detect DoP itself */ + + /* + * ES9039_DAC_MUTE has two owners and neither can hold it alone: the + * "Master Playback Switch" control, and mute_stream() around stream + * start and stop. Both record what they want here and the register is + * written from the union, so a user unmute cannot un-mute a stopped + * stream and a stream start cannot override a user mute. + * + * mute_stream starts FALSE and only a real mute_stream(1) sets it. + * Starting it true seemed the safe choice - come up muted - but it is + * not: if a card never reaches the DAI mute callbacks, the flag never + * clears, and because the control now records intent rather than + * writing the register there is nothing the user can do about it. The + * DAC is then muted for ever and the mixer looks fine. Measured on + * hardware: silent with the switch on, off, stream or no stream. Pop + * suppression at probe comes from writing the register + * directly instead, which is what the driver did before this rework + * and is a state either owner may legitimately lift. + */ + unsigned int mute_user; /* ES9039_MUTE_CH* the user asked for */ + bool mute_stream; /* ASoC has the stream muted */ + + bool provider; /* part drives BCK and WS */ + bool mclk_fixed; /* MCLK rate cannot be changed, so ASRC it is */ + + /* + * The four supply inputs, held IN POWER-UP ORDER - the array order is + * the datasheet's sequence and the enable loop walks it forwards, the + * disable loop backwards. DVDD is not among them: the part's 1.2 V + * digital rail is generated on chip and that pin wants a decoupling + * capacitor, not a regulator. + */ + struct regulator_bulk_data supplies[ES9039_NUM_SUPPLIES]; + + /* + * Shadow of whatever was last uploaded to the programmable FIR RAM. + * + * That RAM cannot be read back - see the upload code below - so regmap + * cannot cache it and nothing else in the system knows its contents. + * A board that removes the part's supplies in system suspend therefore + * comes back with the RAM undefined while regcache_sync() restores the + * filter SELECTION from the cache, which would point the interpolator + * at whatever the RAM powered up holding. Keep a copy and re-upload. + */ + u8 fir_shadow[ES9039_FIR_STAGES][ES9039_FIR_MAX_BYTES]; + unsigned int fir_taps[ES9039_FIR_STAGES]; /* 0 = never uploaded */ +}; + +/* + * Multi-byte fields are little-endian across ascending register addresses: + * register N holds bits [7:0], N+1 holds [15:8], and so on. + */ +static int es9039_read_le(struct regmap *map, unsigned int reg, int n, u32 *out) +{ + u8 buf[4]; + int ret, i; + + ret = regmap_bulk_read(map, reg, buf, n); + if (ret) + return ret; + + *out = 0; + for (i = 0; i < n; i++) + *out |= (u32)buf[i] << (8 * i); + + return 0; +} + +static int es9039_write_le(struct regmap *map, unsigned int reg, int n, u32 val) +{ + u8 buf[4]; + int i; + + for (i = 0; i < n; i++) + buf[i] = (val >> (8 * i)) & 0xff; + + return regmap_bulk_write(map, reg, buf, n); +} + +/* ------------------------------------------------------------------ regmap */ + +static bool es9039q2m_writeable_reg(struct device *dev, unsigned int reg) +{ + return reg <= ES9039_LAST_RW; +} + +static bool es9039q2m_readable_reg(struct device *dev, unsigned int reg) +{ + return reg <= ES9039_LAST_RW || reg >= ES9039_READBACK_BASE; +} + +static bool es9039q2m_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg >= ES9039_READBACK_BASE; +} + +static const struct regmap_config es9039q2m_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ES9039_MAX_REGISTER, + .writeable_reg = es9039q2m_writeable_reg, + .readable_reg = es9039q2m_readable_reg, + .volatile_reg = es9039q2m_volatile_reg, + .cache_type = REGCACHE_MAPLE, +}; + +/* --------------------------------------------------- signed 16-bit controls */ + +struct es9039_s16_ctl { + unsigned int reg; + unsigned int shift; /* 0 for CH1, 16 for CH2 within the 32-bit pair */ +}; + +static int es9039_s16_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = S16_MIN; + uinfo->value.integer.max = S16_MAX; + return 0; +} + +static int es9039_s16_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_s16_ctl *p = (void *)kcontrol->private_value; + u32 v; + int ret; + + ret = es9039_read_le(priv->regmap, p->reg + (p->shift / 8), 2, &v); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = (s16)v; + return 0; +} + +static int es9039_s16_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_s16_ctl *p = (void *)kcontrol->private_value; + long v = ucontrol->value.integer.value[0]; + u32 old; + int ret; + + if (v < S16_MIN || v > S16_MAX) + return -EINVAL; + + ret = es9039_read_le(priv->regmap, p->reg + (p->shift / 8), 2, &old); + if (ret) + return ret; + + if ((s16)old == (s16)v) + return 0; + + ret = es9039_write_le(priv->regmap, p->reg + (p->shift / 8), 2, + (u16)v); + if (ret) + return ret; + + return 1; +} + +#define ES9039_S16(xname, xreg, xshift) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = es9039_s16_info, \ + .get = es9039_s16_get, \ + .put = es9039_s16_put, \ + .private_value = (unsigned long)&(struct es9039_s16_ctl) \ + { .reg = xreg, .shift = xshift }, \ +} + +/* ------------------------------------------------- multi-register integers */ + +struct es9039_wide_ctl { + unsigned int reg; + unsigned int bytes; + unsigned int mask; + unsigned int max; +}; + +static int es9039_wide_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct es9039_wide_ctl *p = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = p->max; + return 0; +} + +static int es9039_wide_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_wide_ctl *p = (void *)kcontrol->private_value; + u32 v; + int ret; + + ret = es9039_read_le(priv->regmap, p->reg, p->bytes, &v); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = v & p->mask; + return 0; +} + +static int es9039_wide_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_wide_ctl *p = (void *)kcontrol->private_value; + long v = ucontrol->value.integer.value[0]; + u32 old; + int ret, i; + + if (v < 0 || v > p->max) + return -EINVAL; + + ret = es9039_read_le(priv->regmap, p->reg, p->bytes, &old); + if (ret) + return ret; + + if ((old & p->mask) == (u32)v) + return 0; + + /* + * Write one register at a time through regmap_update_bits() rather + * than reading the whole field and writing it back. + * + * The bits outside the field are not ours: ES9039_AUTOMUTE_TIME spans + * registers 124-125 while MUTE_RAMP_TO_GND is bit 3 of register 125 + * and has a control of its own. A read-modify-write over the pair + * leaves a window in which that other control can write between our + * read and our write, and its change is then lost - the ALSA put path + * holds card->controls_rwsem for READ, so two controls really can run + * at once. Per-register update_bits closes the window inside regmap's + * own lock and never touches a bit outside p->mask. + */ + for (i = 0; i < p->bytes; i++) { + u8 mask = (p->mask >> (8 * i)) & 0xff; + + if (!mask) + continue; + + ret = regmap_update_bits(priv->regmap, p->reg + i, mask, + ((u32)v >> (8 * i)) & mask); + if (ret) + return ret; + } + + return 1; +} + +#define ES9039_WIDE(xname, xreg, xbytes, xmask, xmax) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = es9039_wide_info, \ + .get = es9039_wide_get, \ + .put = es9039_wide_put, \ + .private_value = (unsigned long)&(struct es9039_wide_ctl) \ + { .reg = xreg, .bytes = xbytes, \ + .mask = xmask, .max = xmax }, \ +} + +/* ------------------------------------------------ programmable FIR upload -- + * + * Write-only, deliberately. The chip has a PROG_COEFF_OUT register (248-246) + * described only as "Programmable FIR coefficient readback", but it is not a + * RAM read port: it returns the LAST COEFFICIENT WRITTEN, whatever address is + * selected in PROG_COEFF_ADDR. Measured over raw I2C with this driver out of + * the path - two different coefficients written to + * addresses 0 and 1, then read back with five different sequences (plain, with + * a settle delay, with PROG_COEFF_EN set, with the address written twice, and + * with a WE pulse after the address). All ten reads returned the value written + * to address 1. + * + * A get() built on that register would return something with the shape of data + * and none of its meaning, so there is no get(). If ESS documents a real + * readback sequence, add one. + */ + +/* + * Per-control data rides in our own struct with the soc_bytes_ext EMBEDDED, + * recovered by container_of. Not in soc_bytes_ext.dobj: that field belongs to + * the topology subsystem and only exists under CONFIG_SND_SOC_TOPOLOGY, so a + * driver stashing its own data there fails to build on any config without it. + */ +struct es9039_fir_ctl { + struct soc_bytes_ext be; + unsigned int taps; + bool stage_4x; +}; + +/* + * Push one stage's coefficients into the RAM. Callers hold priv->lock: both + * FIR controls drive the same address, data and strobe registers, so two + * uploads at once would interleave into each other's RAM, and resume re-uploads + * through this same path. + * + * PROG_COEFF_WE is a per-coefficient strobe, not a gate held open across the + * upload. The datasheet's sequence is address, data, raise WE, lower WE, once + * per coefficient. Holding it high for the whole loop also appears to work on + * ES9039Q2M silicon, but "appears to work" is not a specification. + */ +static int es9039_fir_upload(struct es9039q2m_priv *priv, const u8 *data, + unsigned int taps, bool stage_4x) +{ + unsigned int i; + int ret = 0; + + for (i = 0; i < taps; i++) { + ret = regmap_write(priv->regmap, ES9039_PROG_RAM_ADDR, + (stage_4x ? ES9039_PROG_STAGE_4X : 0) | + FIELD_PREP(ES9039_PROG_ADDR_MASK, i)); + if (ret) + goto out; + + ret = regmap_bulk_write(priv->regmap, ES9039_PROG_RAM_DATA, + &data[i * ES9039_COEFF_BYTES], + ES9039_COEFF_BYTES); + if (ret) + goto out; + + ret = regmap_update_bits(priv->regmap, ES9039_PROG_RAM_CTRL, + ES9039_PROG_COEFF_WE, + ES9039_PROG_COEFF_WE); + if (ret) + goto out; + + ret = regmap_update_bits(priv->regmap, ES9039_PROG_RAM_CTRL, + ES9039_PROG_COEFF_WE, 0); + if (ret) + goto out; + } + +out: + regmap_update_bits(priv->regmap, ES9039_PROG_RAM_CTRL, + ES9039_PROG_COEFF_WE, 0); + return ret; +} + +static int es9039_fir_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *bytes, unsigned int size) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct soc_bytes_ext *be = (void *)kcontrol->private_value; + struct es9039_fir_ctl *p = container_of(be, struct es9039_fir_ctl, be); + unsigned int idx = p->stage_4x ? 1 : 0; + u8 *buf; + int ret; + + if (size != p->taps * ES9039_COEFF_BYTES) + return -EINVAL; + + buf = memdup_user(bytes, size); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + scoped_guard(mutex, &priv->lock) { + ret = es9039_fir_upload(priv, buf, p->taps, p->stage_4x); + if (!ret) { + /* + * Keep a copy. The RAM cannot be read back, so this is + * the only record of what is in it, and resume has + * nothing else to restore from. + */ + memcpy(priv->fir_shadow[idx], buf, size); + priv->fir_taps[idx] = p->taps; + } + } + + kfree(buf); + if (ret) + return ret; + + return 1; +} + +static struct es9039_fir_ctl es9039_fir2x = { + .be = { .max = ES9039_FIR2X_TAPS * ES9039_COEFF_BYTES, + .put = es9039_fir_put }, + .taps = ES9039_FIR2X_TAPS, +}; + +static struct es9039_fir_ctl es9039_fir4x = { + .be = { .max = ES9039_FIR4X_TAPS * ES9039_COEFF_BYTES, + .put = es9039_fir_put }, + .taps = ES9039_FIR4X_TAPS, + .stage_4x = true, +}; + +#define ES9039_FIR(xname, xctl) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = snd_soc_bytes_info_ext, \ + .tlv.c = snd_soc_bytes_tlv_callback, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_WRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \ + .private_value = (unsigned long)&(xctl).be, \ +} + +/* ------------------------------------------------------- status (read-only) */ + +struct es9039_stat_ctl { + unsigned int reg; + unsigned int mask; + unsigned int max; +}; + +static int es9039_stat_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct es9039_stat_ctl *p = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = p->max; + return 0; +} + +static int es9039_stat_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_stat_ctl *p = (void *)kcontrol->private_value; + unsigned int v; + int ret; + + ret = regmap_read(priv->regmap, p->reg, &v); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = + (v & p->mask) >> (ffs(p->mask) - 1); + return 0; +} + +#define ES9039_STAT(xname, xreg, xmask, xmax) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | \ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = es9039_stat_info, \ + .get = es9039_stat_get, \ + .private_value = (unsigned long)&(struct es9039_stat_ctl) \ + { .reg = xreg, .mask = xmask, .max = xmax }, \ +} + +/* + * Detected sample rate. + * + * When the part's own rate detector has a valid ratio, use it - it is measured + * from the incoming frame clock and is the ground truth: + * + * FS = Y * SYS_CLK / ((X + 1) * (128 >> 64FS_MODE)) + * + * with X = IDAC_DIV_AUTO and Y = 2 when IDAC_HALF_DIV_AUTO reports a + * half-integer multiple. + * + * That detector is UNAVAILABLE on any board running the DAC asynchronously - + * register 3[7] AUTO_FS_DETECT carries the note "Cannot be used in ASYNC mode". + * A board feeding a free-running oscillator and letting the ASRC absorb the + * difference is precisely that case, and it is the preferable design, so the + * detector reading 0 there is expected rather than a fault. Fall back to the + * rate the stream was opened at, which is what a front panel wants to show. + * Reports 0 only when nothing is playing and the chip has no lock either. + */ +static int es9039_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1536000; + return 0; +} + +static int es9039_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + unsigned int fsreg, sysreg, div, y, den; + int ret; + + ucontrol->value.integer.value[0] = priv->stream_rate; + + if (!priv->mclk_rate) + return 0; + + ret = regmap_read(priv->regmap, ES9039_AUTO_FS_READ, &fsreg); + if (ret) + return ret; + + if (!(fsreg & ES9039_FS_DIV_VALID)) + return 0; /* async mode: keep the stream rate set above */ + + ret = regmap_read(priv->regmap, ES9039_SYSTEM_CONFIG, &sysreg); + if (ret) + return ret; + + div = FIELD_GET(ES9039_FS_DIV_MASK, fsreg) + 1; + y = (fsreg & ES9039_FS_HALF_DIV) ? 2 : 1; + den = div * ((sysreg & ES9039_64FS_MODE) ? 64 : 128); + + ucontrol->value.integer.value[0] = + DIV_ROUND_CLOSEST(priv->mclk_rate * y, den); + return 0; +} + +static const char * const es9039_stream_texts[] = { + "PCM", "DSD", "DoP", "S/PDIF", +}; + +static int es9039_stream_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(es9039_stream_texts), + es9039_stream_texts); +} + +static int es9039_stream_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + unsigned int v; + int ret; + + ret = regmap_read(priv->regmap, ES9039_INPUT_STREAM_READ, &v); + if (ret) + return ret; + + ucontrol->value.enumerated.item[0] = + FIELD_GET(ES9039_RD_INPUT_SEL_MASK, v); + return 0; +} + +/* ----------------------------------------------------------------- controls */ + +static const DECLARE_TLV_DB_SCALE(es9039_vol_tlv, -12750, 50, 1); + +static const char * const es9039_filter_texts[] = { + "Minimum Phase", + "Linear Phase Apodizing Fast Roll-Off", + "Linear Phase Fast Roll-Off", + "Linear Phase Fast Roll-Off Low Ripple", + "Linear Phase Slow Roll-Off", + "Minimum Phase Fast Roll-Off", + "Minimum Phase Slow Roll-Off", + "Minimum Phase Slow Roll-Off Low Dispersion", +}; + +static SOC_ENUM_SINGLE_DECL(es9039_filter_enum, ES9039_FILTER_SHAPE, 0, + es9039_filter_texts); + +/* IIR_BW is a multiple of the datapath bandwidth, not a frequency. */ +static const char * const es9039_iir_texts[] = { + "Reserved", "BW x8", "BW x4", "BW x2", "BW", "BW /2", "BW /4", "BW /8", +}; + +static SOC_ENUM_SINGLE_DECL(es9039_iir_enum, ES9039_IIR_SPDIF, 0, + es9039_iir_texts); + +static const char * const es9039_nsmod_texts[] = { + "Default", "Wide Bandwidth", +}; + +static const unsigned int es9039_nsmod_values[] = { + ES9039_NSMOD_DEFAULT, ES9039_NSMOD_WIDE, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(es9039_nsmod_enum, ES9039_NSMOD, 1, + GENMASK(3, 0), es9039_nsmod_texts, + es9039_nsmod_values); + +static int es9039_dop_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + + ucontrol->value.integer.value[0] = priv->dop_auto; + return 0; +} + +/* + * Automatic DoP detection is two bits in two registers, and they are one + * setting: AUTO_INPUT_SEL lets the part choose a decoder from the data, and + * ENABLE_DOP_DECODE is what gives it a DoP decoder to choose. Enabling the + * first without the second leaves the part hunting for a marker it has no + * decoder for, whereupon it finds no valid DoP and mutes. + * + * Everything that changes the setting goes through here, so the pair can + * never be written apart. Callers hold priv->lock. + */ +static int es9039_apply_dop(struct es9039q2m_priv *priv, bool on) +{ + int ret; + + ret = regmap_update_bits(priv->regmap, ES9039_SYS_MODE, + ES9039_ENABLE_DOP_DECODE, + on ? ES9039_ENABLE_DOP_DECODE : 0); + if (ret) + return ret; + + return regmap_update_bits(priv->regmap, ES9039_INPUT_SEL, + ES9039_AUTO_INPUT_SEL, + on ? ES9039_AUTO_INPUT_SEL : 0); +} + +/* Caller holds priv->lock. */ +static int es9039_apply_mute(struct es9039q2m_priv *priv) +{ + unsigned int val = priv->mute_user; + + if (priv->mute_stream) + val = ES9039_MUTE_BOTH; + + return regmap_update_bits(priv->regmap, ES9039_DAC_MUTE, + ES9039_MUTE_BOTH, val); +} + +/* + * The control is inverted - 1 means playing - so a zero here is a mute + * request. It reports priv->mute_user rather than the register, because the + * register also carries the stream mute and the user did not ask for that. + */ +static int es9039_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + + guard(mutex)(&priv->lock); + + ucontrol->value.integer.value[0] = !(priv->mute_user & ES9039_MUTE_CH1); + ucontrol->value.integer.value[1] = !(priv->mute_user & ES9039_MUTE_CH2); + + return 0; +} + +static int es9039_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + unsigned int val = 0, old; + int ret; + + if (!ucontrol->value.integer.value[0]) + val |= ES9039_MUTE_CH1; + if (!ucontrol->value.integer.value[1]) + val |= ES9039_MUTE_CH2; + + guard(mutex)(&priv->lock); + + if (val == priv->mute_user) + return 0; + + /* + * Commit to the register first. If the write fails and the shadow has + * already moved, get() reports a mute the part is not in and the + * val == mute_user test above turns a retry with the same value into a + * no-op, leaving the control permanently wrong. + */ + old = priv->mute_user; + priv->mute_user = val; + + ret = es9039_apply_mute(priv); + if (ret) { + priv->mute_user = old; + return ret; + } + + return 1; +} + +static int es9039_dop_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + bool on = !!ucontrol->value.integer.value[0], old; + int ret; + + guard(mutex)(&priv->lock); + + if (on == priv->dop_auto) + return 0; + + /* + * hw_params() programs the decoder and INPUT_SEL from dop_auto, so + * changing it under a running stream would apply half the setting now + * and the rest at the next open. Refuse instead of half-applying. + */ + if (priv->stream_rate) + return -EBUSY; + + /* Same reasoning as es9039_mute_put(): the shadow follows the write. */ + old = priv->dop_auto; + priv->dop_auto = on; + + ret = es9039_apply_dop(priv, on); + if (ret) { + priv->dop_auto = old; + return ret; + } + + return 1; +} + +/* + * The two channel volumes live in separate registers, so a stereo change is + * two I2C writes and the channels are briefly at different levels in between. + * VOLUME_HOLD exists for exactly this: while it is set the part accepts writes + * to registers 74-75 without applying them, and clearing it applies both at + * once. Raise it, let the generic handler do the writes, drop it again. + * + * It is dropped unconditionally, including on the error path, because a stuck + * VOLUME_HOLD would silently freeze the volume control. + */ +static int es9039_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + int ret; + + /* + * The hold has to cover the pair of channel writes, so it has to be + * serialised. Two callers interleaving would let the second write its + * volumes after the first had already dropped VOLUME_HOLD, which is + * exactly the torn stereo update the bit exists to prevent. + */ + guard(mutex)(&priv->lock); + + ret = regmap_update_bits(priv->regmap, ES9039_IIR_SPDIF, + ES9039_VOLUME_HOLD, ES9039_VOLUME_HOLD); + if (ret) + return ret; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + + regmap_update_bits(priv->regmap, ES9039_IIR_SPDIF, + ES9039_VOLUME_HOLD, 0); + + return ret; +} + +static const struct snd_kcontrol_new es9039q2m_controls[] = { + /* --- level --- */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_soc_info_volsw, + .get = snd_soc_get_volsw, + .put = es9039_vol_put, + .tlv.p = es9039_vol_tlv, + .private_value = SOC_DOUBLE_R_VALUE(ES9039_VOLUME_CH1, + ES9039_VOLUME_CH2, 0, 0, + ES9039_VOL_MAX, 1), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_soc_info_volsw, + .get = es9039_mute_get, + .put = es9039_mute_put, + .private_value = SOC_DOUBLE_VALUE(ES9039_DAC_MUTE, 0, 1, 0, 1, + 1, 0), + }, + SOC_DOUBLE("DAC Invert Switch", ES9039_DAC_INVERT, 0, 1, 1, 0), + SOC_SINGLE("Volume Ramp Up Rate", ES9039_VOL_RATE_UP, 0, 255, 0), + SOC_SINGLE("Volume Ramp Down Rate", ES9039_VOL_RATE_DOWN, 0, 255, 0), + SOC_SINGLE("Soft Ramp Time", ES9039_SOFT_RAMP, 0, + ES9039_SOFT_RAMP_MAX, 0), + + /* --- reconstruction filter --- */ + SOC_ENUM("Filter Shape", es9039_filter_enum), + SOC_ENUM("IIR Bandwidth", es9039_iir_enum), + SOC_ENUM("Modulator Bandwidth", es9039_nsmod_enum), + SOC_SINGLE("IIR Filter Bypass Switch", ES9039_DAC_PATH, 2, 1, 0), + SOC_SINGLE("FIR 2x Bypass Switch", ES9039_DAC_PATH, 0, 1, 0), + SOC_SINGLE("FIR 4x Bypass Switch", ES9039_DAC_PATH, 1, 1, 0), + SOC_SINGLE("Custom FIR Switch", ES9039_PROG_RAM_CTRL, 0, 1, 0), + ES9039_FIR("FIR 2x Coefficients", es9039_fir2x), + ES9039_FIR("FIR 4x Coefficients", es9039_fir4x), + + /* + * Not "... Volume", because these do not set a level. They are signed + * correction coefficients for the second and third harmonic: they + * cancel distortion the analogue stage adds, and turning one up does + * not make anything louder. Useful values come from measuring a given + * board's distortion on an analyser and solving for them; zero, the + * reset value, is the only honest default until someone has. + */ + /* --- distortion compensation --- */ + ES9039_S16("THD Compensation C2 CH1", ES9039_THD_C2, 0), + ES9039_S16("THD Compensation C2 CH2", ES9039_THD_C2, 16), + ES9039_S16("THD Compensation C3 CH1", ES9039_THD_C3, 0), + ES9039_S16("THD Compensation C3 CH2", ES9039_THD_C3, 16), + + /* --- automute --- */ + SOC_DOUBLE("Automute Switch", ES9039_AUTOMUTE_EN, 0, 1, 1, 0), + ES9039_WIDE("Automute Time", ES9039_AUTOMUTE_TIME, 2, + ES9039_AUTOMUTE_TIME_MASK, 2047), + ES9039_WIDE("Automute Level", ES9039_AUTOMUTE_LEVEL, 2, 0xffff, 65535), + ES9039_WIDE("Automute Off Level", ES9039_AUTOMUTE_OFF_LEVEL, 2, + 0xffff, 65535), + SOC_SINGLE("Mute Ramp To Ground Switch", ES9039_AUTOMUTE_TIME + 1, + 3, 1, 0), + SOC_SINGLE("DSD DC Automute Switch", ES9039_MONITOR_CFG, 7, 1, 1), + SOC_SINGLE("DSD Mute Pattern Switch", ES9039_MONITOR_CFG, 6, 1, 1), + SOC_SINGLE("PCM DC Automute Switch", ES9039_MONITOR_CFG, 3, 1, 1), + + /* --- stream --- */ + /* + * On by default. DoP is designed to be detected, not announced: the + * player just sends it and a DoP-aware DAC notices the marker, which + * is why a DAC that does not notice plays it as near-silence rather + * than noise. Players rely on that, and none of them can reach into + * ALSA to flip a mode first. + * + * Left switchable because automatic detection is a pattern match, and + * anyone worried about PCM material that happens to look like DoP can + * turn it off and get strictly PCM. + */ + SOC_SINGLE_BOOL_EXT("DoP Auto Detect Switch", 0, + es9039_dop_get, es9039_dop_put), + + /* --- status, read-only --- */ + ES9039_STAT("Automute Active CH1", ES9039_AUTOMUTE_READ, BIT(0), 1), + ES9039_STAT("Automute Active CH2", ES9039_AUTOMUTE_READ, BIT(1), 1), + ES9039_STAT("DoP Valid", ES9039_INPUT_STREAM_READ, + ES9039_RD_DOP_VALID, 1), + ES9039_STAT("TDM Data Valid", ES9039_INPUT_STREAM_READ, + ES9039_RD_TDM_VALID, 1), + ES9039_STAT("SPDIF Valid", ES9039_INPUT_STREAM_READ, + ES9039_RD_SPDIF_VALID, 1), + /* + * Register 235-234 is one 16-bit word and BCK_WS_FAIL_SOURCE is bit + * 7 of it, so it sits in the low byte at ES9039_IRQ_SOURCES. This + * read the byte above with the same mask, which is bit 15 - reserved + * - so the control could never have reported a fault. The named + * constant now carries the bit rather than a bare BIT(7). + * + * The flag is meaningful only with the BCK and WS monitors enabled, + * which they are at reset and the driver leaves alone. + */ + ES9039_STAT("Clock Fault", ES9039_IRQ_SOURCES, + ES9039_SRC_BCK_WS_FAIL, 1), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Detected Sample Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = es9039_rate_info, + .get = es9039_rate_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Detected Input Format", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = es9039_stream_info, + .get = es9039_stream_get, + }, +}; + +/* --------------------------------------------------------------------- DAPM */ + +static const struct snd_soc_dapm_widget es9039q2m_widgets[] = { + SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("AOUTL"), + SND_SOC_DAPM_OUTPUT("AOUTR"), +}; + +static const struct snd_soc_dapm_route es9039q2m_routes[] = { + { "DAC", NULL, "Playback" }, + { "AOUTL", NULL, "DAC" }, + { "AOUTR", NULL, "DAC" }, +}; + +/* ---------------------------------------------------------------------- DAI */ + +/* + * MCLK limits, all from the datasheet: + * + * - 50 MHz absolute maximum ("Max MCLK Frequency", electrical specification). + * + * - Table 7 note 1: MCLK >= 128 * FS synchronous, MCLK > 130 * FS + * asynchronous. The gap is small but not academic - with a 24.576 MHz + * clock, 128 * FS puts 192 kHz exactly at the limit while 130 * FS puts the + * ceiling at 189 kHz. Note that the asynchronous bound is strict. + * + * - Register 0[6] ENABLE_64FS_MODE runs the interpolation path at 64FS and is + * "used only for PCM high sample rates such as 768kHz with a 49.152MHz or + * 384kHz with 24.576MHz clock" - both of which are MCLK = 64 * FS. Since + * 128 * 768000 is 98.304 MHz, well over the ceiling, 64FS mode is the only + * way 705.6 and 768 kHz are reachable at all. + */ +#define ES9039_MAX_MCLK 50000000 +#define ES9039_64FS_MCLK_FS 64 +#define ES9039_SYNC_MIN_MCLK_FS 128 +#define ES9039_ASYNC_MIN_MCLK_FS 130 + +/* + * MCLK/FS ratios to try on a settable clock, highest first. 256 leaves the + * part inside the window its automatic clock gearing aims for - register 5[2]: + * "MCLK will be geared down until 128FS <= SYS_CLK < 256FS" - and 64 is 64FS + * mode, which only becomes the choice when nothing larger fits under 50 MHz. + */ +static const unsigned int es9039_mclk_ratios[] = { 512, 256, 128, 64 }; + +/* + * Synchronous means the frame clock and MCLK come from the same place. That is + * true when the part generates BCK and WS itself, and it is true when the MCLK + * we were given can be set to a multiple of the sample rate. Only a fixed + * oscillator feeding a consumer-mode part is genuinely asynchronous, and only + * then does the ASRC have anything to do. + */ +static bool es9039_is_sync(struct es9039q2m_priv *priv) +{ + return priv->provider || !priv->mclk_fixed; +} + +/* + * 64FS mode is an exact MCLK/FS ratio, not a rate threshold, and it only makes + * sense synchronously: at 64 * FS an asynchronous part would sit far below its + * own 130 * FS floor, so a fixed oscillator that happens to land on 64 * FS is + * a coincidence rather than a mode. + */ +static bool es9039_is_64fs(struct es9039q2m_priv *priv, unsigned int mclk, + unsigned int rate) +{ + return es9039_is_sync(priv) && rate && + mclk == ES9039_64FS_MCLK_FS * rate; +} + +/* The lowest MCLK this rate may run at outside 64FS mode. */ +static unsigned int es9039_min_mclk(struct es9039q2m_priv *priv, + unsigned int rate) +{ + if (es9039_is_sync(priv)) + return ES9039_SYNC_MIN_MCLK_FS * rate; + + /* Asynchronous is strictly greater than 130 * FS, not equal to it. */ + return ES9039_ASYNC_MIN_MCLK_FS * rate + 1; +} + +static int es9039q2m_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + unsigned int max_rate; + + /* + * A clock whose rate can be set imposes no ceiling here: hw_params() + * raises MCLK to suit the rate, and fails cleanly if it cannot. + * Constraining from the rate MCLK merely happens to be idling at would + * reject rates the board can carry perfectly well. + */ + if (!priv->mclk_rate || !priv->mclk_fixed) + return 0; + + if (!es9039_is_sync(priv)) { + max_rate = priv->mclk_rate / ES9039_ASYNC_MIN_MCLK_FS; + } else if (priv->mclk_rate % ES9039_64FS_MCLK_FS == 0) { + /* + * 64FS mode reaches exactly one rate above the ordinary + * ceiling - MCLK / 64 - so admit it here and leave + * hw_params() to reject anything in between, where neither + * 64 * FS nor 128 * FS is satisfied. + */ + max_rate = priv->mclk_rate / ES9039_64FS_MCLK_FS; + } else { + max_rate = priv->mclk_rate / ES9039_SYNC_MIN_MCLK_FS; + } + + return snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 8000, max_rate); +} + +/* + * Put the clocking where it needs to be for this rate, and tell the part which + * of its two timing worlds it is living in. + * + * With a settable MCLK the useful thing is to pull it to an exact multiple of + * the sample rate: the ASRC then has nothing to correct, which is the better + * arrangement whenever the board can manage it. With a fixed oscillator there + * is nothing to pull, and the ASRC earns its keep. + */ +static int es9039_setup_clocking(struct es9039q2m_priv *priv, + struct snd_soc_dai *dai, unsigned int rate) +{ + unsigned int ratio, min_mclk, div, i, target = 0; + bool sixtyfour; + int ret; + + if (!priv->mclk_rate) + return 0; + + ratio = priv->bclk_ratio ? priv->bclk_ratio : 64; + + if (!priv->mclk_fixed) { + /* + * Take the highest ratio that still fits under the 50 MHz + * ceiling rather than always asking for 256 * FS, which would + * put 384 kHz at 98.304 MHz and 768 kHz at 196.608 MHz. A + * settable clock is synchronous by definition here - see + * es9039_is_sync() - so only the synchronous floors apply. + */ + for (i = 0; i < ARRAY_SIZE(es9039_mclk_ratios); i++) { + unsigned int mult = es9039_mclk_ratios[i]; + + if (mult * rate > ES9039_MAX_MCLK) + continue; + if (mult < ES9039_SYNC_MIN_MCLK_FS && + mult != ES9039_64FS_MCLK_FS) + continue; + /* + * Master mode divides MCLK down to BCK by a whole + * number, so a ratio that is not a multiple of the + * frame size cannot produce the bit clock. + */ + if (priv->provider && mult % ratio) + continue; + + target = mult * rate; + break; + } + + if (!target) { + dev_err(dai->dev, + "no mclk ratio for %u Hz within %u Hz\n", + rate, ES9039_MAX_MCLK); + return -EINVAL; + } + + ret = clk_set_rate(priv->mclk, target); + if (ret) + return ret; + + /* Take what the clock settled on, not what was asked for. */ + priv->mclk_rate = clk_get_rate(priv->mclk); + } + + if (priv->mclk_rate > ES9039_MAX_MCLK) { + dev_err(dai->dev, "mclk %u Hz is above the %u Hz maximum\n", + priv->mclk_rate, ES9039_MAX_MCLK); + return -EINVAL; + } + + sixtyfour = es9039_is_64fs(priv, priv->mclk_rate, rate); + min_mclk = es9039_min_mclk(priv, rate); + + if (!sixtyfour && priv->mclk_rate < min_mclk) { + dev_err(dai->dev, + "mclk %u Hz is below the %u Hz %u Hz needs in this mode\n", + priv->mclk_rate, min_mclk, rate); + return -EINVAL; + } + + /* + * Register 0[6]. Decided from the MCLK actually in use rather than from + * the sample rate alone, because the part cares about the ratio: 384 kHz + * from 24.576 MHz needs 64FS mode exactly as 768 kHz from 49.152 MHz + * does. The mode also forces a minimum phase filter regardless of + * FILTER_SHAPE. + */ + ret = regmap_update_bits(priv->regmap, ES9039_SYSTEM_CONFIG, + ES9039_64FS_MODE, + sixtyfour ? ES9039_64FS_MODE : 0); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_SYS_MODE, + ES9039_SYNC_MODE, + es9039_is_sync(priv) ? ES9039_SYNC_MODE : 0); + if (ret) + return ret; + + if (!priv->provider) + return 0; + + /* + * Register 4: BCK = MCLK / (MASTER_BCK_DIV + 1), and WS follows from + * the frame length. An MCLK that is not a whole multiple of the bit + * clock cannot produce the requested rate at all, so say so rather + * than emitting something close. + */ + if (priv->mclk_rate % (ratio * rate)) { + dev_err(dai->dev, + "mclk %u Hz cannot produce %u * %u Hz bit clock\n", + priv->mclk_rate, ratio, rate); + return -EINVAL; + } + + div = priv->mclk_rate / (ratio * rate); + if (div < 1 || div > 256) { + dev_err(dai->dev, "master bck divider %u out of range\n", div); + return -EINVAL; + } + + return regmap_write(priv->regmap, ES9039_CLOCK_CONFIG, div - 1); +} + +static int es9039q2m_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + unsigned int cfg1 = 0, enc = 0; + int ret; + + /* + * Both directions are useful and which is right is a board decision, + * not a driver one. Driving BCK and WS from a good MCLK is the + * conventional arrangement and keeps everything synchronous; taking + * them from a consumer-mode host and letting the ASRC absorb the + * mismatch is what a board with a fixed oscillator wants. The part can + * do either, so the driver offers either. + */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + priv->provider = false; + break; + case SND_SOC_DAIFMT_CBP_CFP: + if (!priv->mclk) + return -EINVAL; + priv->provider = true; + break; + default: + /* The part cannot split them: it drives both or neither. */ + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, ES9039_INPUT_SEL, + ES9039_PCM_MASTER_MODE, + priv->provider ? ES9039_PCM_MASTER_MODE : 0); + if (ret) + return ret; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + cfg1 |= ES9039_TDM_LJ_MODE; + break; + default: + /* Register 60 offers I2S or LJ. There is no RJ mode. */ + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + enc |= ES9039_BCK_INV; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, ES9039_TDM_CONFIG1, + ES9039_TDM_LJ_MODE, cfg1); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_MASTER_ENC, + ES9039_BCK_INV, enc); + if (ret) + return ret; + + priv->fmt = fmt; + return 0; +} + +/* + * TDM_BIT_WIDTH describes the SLOT width on the wire, not the sample size. Those + * are routinely different: 16-bit samples are usually carried left-justified in + * 32-bit slots. Getting this wrong misaligns the channel boundaries and the + * result is one channel, or noise. + * + * ASoC does not hand the codec the bit clock ratio unless a machine driver sets + * it, so take it when offered and otherwise assume 32-bit slots - by far the + * most common arrangement, and what the RK3588 I2S does unconditionally + * (rockchip_i2s.c sets bclk_ratio = 64 at probe and never varies it with + * format). + */ +static int es9039q2m_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + + priv->bclk_ratio = ratio; + return 0; +} + +static int es9039q2m_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + unsigned int input_sel, decode, width, slot_bits, isel; + bool auto_sel; + int ret; + + /* + * Held for the whole call: dop_auto decides both INPUT_SEL and + * SYS_MODE below, and a kcontrol write landing between the two would + * leave automatic detection enabled with no DoP decoder behind it. + */ + guard(mutex)(&priv->lock); + + priv->stream_rate = params_rate(params); + + ret = es9039_setup_clocking(priv, dai, priv->stream_rate); + if (ret) + return ret; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + /* + * Forced, not auto-detected: the datasheet requires DSD data on + * DATA1 and DATA2 for AUTO_INPUT_SEL to identify it, which a + * two-channel I2S link does not provide. + */ + input_sel = ES9039_INPUT_DSD; + decode = ES9039_ENABLE_DSD_DECODE; + auto_sel = false; + break; + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_3LE: + case SNDRV_PCM_FORMAT_S32_LE: + /* + * DoP arrives inside ordinary PCM frames and is indistinguishable + * from PCM until the part finds the marker, so both decoders run + * and AUTO_INPUT_SEL picks between them. INPUT_SEL is programmed + * anyway as the fallback the part uses when auto-detection is + * switched off. + */ + input_sel = ES9039_INPUT_PCM; + decode = ES9039_ENABLE_TDM_DECODE | + (priv->dop_auto ? ES9039_ENABLE_DOP_DECODE : 0); + auto_sel = priv->dop_auto; + break; + default: + return -EINVAL; + } + + slot_bits = priv->bclk_ratio ? + priv->bclk_ratio / params_channels(params) : 32; + + switch (slot_bits) { + case 16: + width = ES9039_WIDTH_16; + break; + case 24: + width = ES9039_WIDTH_24; + break; + case 32: + width = ES9039_WIDTH_32; + break; + default: + dev_err(dai->dev, "unsupported slot width %u\n", slot_bits); + return -EINVAL; + } + + /* + * AUTO_INPUT_SEL belongs in the value as well as the mask. It was in + * the mask alone, so every hw_params quietly cleared it and undid what + * the component probe had set - which is why enabling auto-detection + * by hand mid-stream worked while enabling it in probe() did not. + * INPUT_SEL is still programmed underneath: it is what the part falls + * back to when auto-detection is switched off. + */ + isel = FIELD_PREP(ES9039_INPUT_SEL_MASK, input_sel); + if (auto_sel) + isel |= ES9039_AUTO_INPUT_SEL; + + ret = regmap_update_bits(priv->regmap, ES9039_INPUT_SEL, + ES9039_AUTO_INPUT_SEL | ES9039_INPUT_SEL_MASK, + isel); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_TDM_CONFIG2, + ES9039_TDM_BIT_WIDTH_MASK, + FIELD_PREP(ES9039_TDM_BIT_WIDTH_MASK, width)); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_TDM_CH_NUM, + ES9039_TDM_CH_NUM_MASK, + params_channels(params) - 1); + if (ret) + return ret; + + /* Switch the right decoder on for this stream, and the others off. */ + return regmap_update_bits(priv->regmap, ES9039_SYS_MODE, + ES9039_DECODE_MASK, decode); +} + +static int es9039q2m_mute_stream(struct snd_soc_dai *dai, int mute, int dir) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + bool old; + int ret; + + guard(mutex)(&priv->lock); + + old = priv->mute_stream; + priv->mute_stream = mute; + + ret = es9039_apply_mute(priv); + if (ret) + priv->mute_stream = old; + + return ret; +} + +/* + * stream_rate doubles as "a stream is open", which es9039_dop_put() needs in + * order to refuse a change it could only half-apply. + */ +static void es9039q2m_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + + guard(mutex)(&priv->lock); + + priv->stream_rate = 0; +} + +static const struct snd_soc_dai_ops es9039q2m_dai_ops = { + .startup = es9039q2m_startup, + .shutdown = es9039q2m_shutdown, + .set_fmt = es9039q2m_set_fmt, + .set_bclk_ratio = es9039q2m_set_bclk_ratio, + .hw_params = es9039q2m_hw_params, + .mute_stream = es9039q2m_mute_stream, + .no_capture_mute = 1, +}; + +#define ES9039_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_DSD_U8 | \ + SNDRV_PCM_FMTBIT_DSD_U16_LE | \ + SNDRV_PCM_FMTBIT_DSD_U32_LE) + +static struct snd_soc_dai_driver es9039q2m_dai = { + .name = "es9039q2m-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_768000, + .formats = ES9039_FORMATS, + }, + .ops = &es9039q2m_dai_ops, +}; + +/* ---------------------------------------------------------------- component */ + +static int es9039q2m_component_probe(struct snd_soc_component *component) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(component); + int ret; + + /* + * Come up muted. The volume registers default to 0 dB, and an unmuted + * DAC arriving into an already-powered analogue stage is how you get a + * thump. ASoC unmutes via mute_stream() when a stream starts. + */ + priv->mute_user = 0; + priv->mute_stream = false; + ret = regmap_update_bits(priv->regmap, ES9039_DAC_MUTE, + ES9039_MUTE_BOTH, ES9039_MUTE_BOTH); + if (ret) + return ret; + + /* + * Enable the datapath. SYSTEM_CONFIG[1] is clear at reset, so in + * software mode nothing turns the DAC on at power-up: the analogue + * output stays dead until this bit is set. + * + * After the mute above and never before it. Bringing the datapath up + * into an unmuted part whose volume registers still sit at their 0 dB + * reset value is exactly the thump the mute is there to prevent. + * + * Note when testing that a warm reboot does not reset this part - + * registers and coefficient RAM both survive one, and only removing + * power clears them - so the previous boot's value can hide whether + * this write happened at all. + */ + ret = regmap_update_bits(priv->regmap, ES9039_SYSTEM_CONFIG, + ES9039_DAC_MODE, ES9039_DAC_MODE); + if (ret) + return ret; + + /* + * VOLUME_HOLD set means "do not apply volume register writes", so its + * resting state has to be clear or the volume control would appear + * dead. es9039_vol_put() raises it around the pair of writes and drops + * it again, which is how both channels are made to move together. + */ + ret = regmap_update_bits(priv->regmap, ES9039_IIR_SPDIF, + ES9039_VOLUME_HOLD, 0); + if (ret) + return ret; + + /* + * Let the part identify DoP for itself. Register 57[0] AUTO_INPUT_SEL + * makes it choose between PCM and DoP from the data, which is the only + * way DoP can work in practice: players send DoP-encoded PCM and expect + * the DAC to notice, and none of them can flip an ALSA control first. + * Without this the part is told "PCM", never looks for the marker, and + * renders a DoP stream as the near-silent hiss the DoP marker design + * deliberately degrades to. Verified on hardware by enabling this + * mid-stream and watching reg 245 flip from PCM to DoP with DOP_VALID + * set. + * + * The datasheet's "data must be provided on the DATA2 pin" applies to + * identifying DSD, whose two channels arrive on separate data lines. + * DoP is ordinary stereo I2S on one line and detects correctly without + * it, which the same measurement establishes. + */ + priv->dop_auto = true; + scoped_guard(mutex, &priv->lock) + ret = es9039_apply_dop(priv, true); + if (ret) + return ret; + + /* + * Board defaults, applied once at probe so the part is deterministic + * from cold instead of inheriting whatever its reset value happens to + * be. Both stay user-settable through their kcontrols; these are + * defaults, not policy. + * + * Reconstruction filter: linear phase apodizing fast roll-off. It keeps + * the sharp cut and flat passband of the plain fast linear-phase filter + * while suppressing pre-ringing, and an apodizing response also + * suppresses pre-ringing already baked into the source material by the + * recording chain - which none of the other seven addresses. The cost + * is a little stopband rejection right at the band edge, well above + * where it can matter. Chosen this way because a blind A/B listening + * test found no audible difference between any of the eight, + * so the tie is broken on theory rather than on preference. + * + * Modulator: wide bandwidth, which is ESS's own recommendation. It + * moves the modulator's noise further out of band and improves + * linearity at high frequencies. + */ + ret = regmap_update_bits(priv->regmap, ES9039_FILTER_SHAPE, + ES9039_FILTER_SHAPE_MASK, + ES9039_FILTER_APODIZING); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_NSMOD, + ES9039_NSMOD_WIDE_BW_MASK, + FIELD_PREP(ES9039_NSMOD_WIDE_BW_MASK, + ES9039_NSMOD_WIDE)); + if (ret) + return ret; + + /* Let the part work out the incoming rate; the ASRC does the rest. */ + return regmap_update_bits(priv->regmap, ES9039_AUTO_FS_DETECT, + ES9039_AUTO_FS_DETECT_EN, + ES9039_AUTO_FS_DETECT_EN); +} + +/* + * System suspend. + * + * Where the part is clocked from the SoC, system suspend stops its clock as a + * side effect and there is nothing for a driver to do. Where the board feeds + * it a free-running oscillator instead - which the ASRC in front of the DAC + * makes an attractive design, since MCLK then need not track BCLK or LRCK - + * nothing in the system can gate that clock, and the part stays fully clocked + * for the whole of suspend. + * + * Clearing ENABLE_DAC_CLK gates the part's internal clock tree, which is the + * only part of its consumption software can reach. The analogue supplies are + * the board's business, and not every board is able to switch them. + * + * The write deliberately bypasses the cache. The cached value must keep + * ENABLE_DAC_CLK set, so that regcache_sync() on resume restores whatever + * state userspace last left the part in without this code having to remember + * anything itself. + */ +static int es9039q2m_suspend(struct snd_soc_component *component) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(component); + int ret; + + guard(mutex)(&priv->lock); + + regcache_cache_bypass(priv->regmap, true); + ret = regmap_update_bits(priv->regmap, ES9039_SYS_MODE, + ES9039_ENABLE_DAC_CLK, 0); + regcache_cache_bypass(priv->regmap, false); + + if (ret) + dev_warn(component->dev, + "could not gate the DAC clock for suspend: %d\n", ret); + + regcache_mark_dirty(priv->regmap); + regcache_cache_only(priv->regmap, true); + + return 0; +} + +static int es9039q2m_resume(struct snd_soc_component *component) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(component); + unsigned int i; + int ret; + + guard(mutex)(&priv->lock); + + regcache_cache_only(priv->regmap, false); + + /* + * Mute the part before anything else, and write it straight to the + * hardware rather than through the cache. + * + * Where the board kept the supplies up this is a no-op - the part still + * holds the mute state it went to sleep with. Where the board removed + * them it is not: the part comes back at reset defaults, which are + * DAC_MODE clear, both channels UNMUTED, and both volume registers at + * 0x00, their 0 dB setting. regcache_sync() walks registers in ascending + * address order, so it would set DAC_MODE in register 0 - lighting the + * datapath up unmuted at full scale - and only restore the real mute in + * register 86, long after. That is the thump the probe path mutes to + * avoid, arriving by the other door. + * + * The cache is bypassed so the cached mute state is untouched; the sync + * below puts it back, after the volume registers at 74 and 75. + */ + regcache_cache_bypass(priv->regmap, true); + ret = regmap_update_bits(priv->regmap, ES9039_DAC_MUTE, + ES9039_MUTE_BOTH, ES9039_MUTE_BOTH); + regcache_cache_bypass(priv->regmap, false); + + if (ret) + dev_warn(component->dev, + "could not mute before resync: %d\n", ret); + + ret = regcache_sync(priv->regmap); + if (ret) + dev_err(component->dev, + "failed to restore registers on resume: %d\n", ret); + + /* + * Re-upload the programmable FIR RAM. regcache_sync() cannot restore + * it - it is write-only, so there is no cache of it to sync - and a + * board that removed the part's supplies comes back with that RAM + * undefined while the sync has just restored the filter SELECTION from + * the cache. Selecting the programmable filter would then point the + * interpolator at whatever the RAM powered up holding. After the sync, + * so the selection is already in place. Stages never uploaded have + * fir_taps == 0 and keep the part's own defaults. + */ + for (i = 0; i < ES9039_FIR_STAGES; i++) { + int err; + + if (!priv->fir_taps[i]) + continue; + + err = es9039_fir_upload(priv, priv->fir_shadow[i], + priv->fir_taps[i], i == 1); + if (err) + dev_err(component->dev, + "failed to restore FIR stage %u on resume: %d\n", + i, err); + } + + return ret; +} + +static const struct snd_soc_component_driver es9039q2m_component = { + .probe = es9039q2m_component_probe, + .suspend = es9039q2m_suspend, + .resume = es9039q2m_resume, + .controls = es9039q2m_controls, + .num_controls = ARRAY_SIZE(es9039q2m_controls), + .dapm_widgets = es9039q2m_widgets, + .num_dapm_widgets = ARRAY_SIZE(es9039q2m_widgets), + .dapm_routes = es9039q2m_routes, + .num_dapm_routes = ARRAY_SIZE(es9039q2m_routes), + .idle_bias_on = 1, + .endianness = 1, +}; + +/* ------------------------------------------------------------------- power */ + +/* + * The part has four supply inputs and the datasheet is specific about the + * order: figure 22 brings AVDD up first, VCCA about 200 us later, then the two + * output-stage references, and only then is the part enabled. Power-down is + * the exact reverse. + * + * That ordering is the whole reason these are enabled one at a time rather + * than with regulator_bulk_enable(), which makes no ordering guarantee at all. + * The bulk API is still used to GET them, where order is irrelevant. On a + * board that ties all four to one always-on rail the sequence costs nothing; + * on one that switches them separately, bringing an output stage up before its + * reference is precisely what the sequence exists to prevent. + * + * The datasheet gives no settling time between the last supply and the first + * register access, so none is invented here. + */ +static const char * const es9039_supply_names[ES9039_NUM_SUPPLIES] = { + "avdd", "vcca", "avcc-dac1", "avcc-dac2", +}; + +static int es9039_power_on(struct es9039q2m_priv *priv) +{ + int i, ret; + + for (i = 0; i < ES9039_NUM_SUPPLIES; i++) { + ret = regulator_enable(priv->supplies[i].consumer); + if (ret) + goto err; + + /* AVDD leads VCCA. It is the only interval the part specifies. */ + if (i == 0) + fsleep(200); + } + + return 0; + +err: + while (--i >= 0) + regulator_disable(priv->supplies[i].consumer); + + return ret; +} + +static void es9039_power_off(void *data) +{ + struct es9039q2m_priv *priv = data; + int i; + + for (i = ES9039_NUM_SUPPLIES - 1; i >= 0; i--) + regulator_disable(priv->supplies[i].consumer); +} + +/* --------------------------------------------------------------------- I2C */ + +static int es9039q2m_i2c_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct es9039q2m_priv *priv; + unsigned int id; + int i, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = devm_regmap_init_i2c(i2c, &es9039q2m_regmap); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "failed to init regmap\n"); + + for (i = 0; i < ES9039_NUM_SUPPLIES; i++) + priv->supplies[i].supply = es9039_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ES9039_NUM_SUPPLIES, priv->supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to get the supplies\n"); + + ret = es9039_power_on(priv); + if (ret) + return dev_err_probe(dev, ret, "failed to enable the supplies\n"); + + ret = devm_add_action_or_reset(dev, es9039_power_off, priv); + if (ret) + return ret; + + priv->mclk = devm_clk_get_optional_enabled(dev, "mclk"); + if (IS_ERR(priv->mclk)) + return dev_err_probe(dev, PTR_ERR(priv->mclk), + "failed to get mclk\n"); + + if (priv->mclk) { + priv->mclk_rate = clk_get_rate(priv->mclk); + if (priv->mclk_rate > 50000000) + return dev_err_probe(dev, -EINVAL, + "mclk %u Hz exceeds the 50 MHz maximum\n", + priv->mclk_rate); + + /* + * A fixed-rate clock answers every rounding question with the + * one rate it has. That is the board saying "this is an + * oscillator, use the ASRC"; anything else can be pulled to + * suit the sample rate and run synchronously. + */ + priv->mclk_fixed = + clk_round_rate(priv->mclk, priv->mclk_rate / 2) == + priv->mclk_rate; + } + + ret = devm_mutex_init(dev, &priv->lock); + if (ret) + return ret; + + i2c_set_clientdata(i2c, priv); + + ret = regmap_read(priv->regmap, ES9039_CHIP_ID, &id); + if (ret) + return dev_err_probe(dev, ret, "no response at 0x%02x\n", + i2c->addr); + + if (id != ES9039_CHIP_ID_ES9039Q2M) + return dev_err_probe(dev, -ENODEV, + "unexpected chip id 0x%02x, want 0x%02x\n", + id, ES9039_CHIP_ID_ES9039Q2M); + + dev_info(dev, "ES9039Q2M at 0x%02x, mclk %u Hz\n", + i2c->addr, priv->mclk_rate); + + return devm_snd_soc_register_component(dev, &es9039q2m_component, + &es9039q2m_dai, 1); +} + +static const struct of_device_id es9039q2m_of_match[] = { + { .compatible = "esstech,es9039q2m" }, + { } +}; +MODULE_DEVICE_TABLE(of, es9039q2m_of_match); + +static const struct i2c_device_id es9039q2m_i2c_id[] = { + { "es9039q2m" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, es9039q2m_i2c_id); + +static struct i2c_driver es9039q2m_i2c_driver = { + .driver = { + .name = "es9039q2m", + .of_match_table = es9039q2m_of_match, + }, + .probe = es9039q2m_i2c_probe, + .id_table = es9039q2m_i2c_id, +}; +module_i2c_driver(es9039q2m_i2c_driver); + +MODULE_DESCRIPTION("ASoC ES9039Q2M driver"); +MODULE_AUTHOR("Karl Asseily "); +MODULE_LICENSE("GPL"); From 10d321c76b1e6605031b604bf89833d7ff2f8385 Mon Sep 17 00:00:00 2001 From: Karl Asseily Date: Fri, 18 Sep 2026 21:23:17 +0300 Subject: [PATCH 268/924] MAINTAINERS: add entry for the ES9039Q2M codec driver Add myself as maintainer of the ES9039Q2M codec driver and its binding. Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Asseily Link: https://patch.msgid.link/20260918182317.143223-5-karl@asseily.com Signed-off-by: Mark Brown --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 20d67cc0e7af..1095476c1d9f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9699,6 +9699,13 @@ S: Maintained F: include/linux/errseq.h F: lib/errseq.c +ES9039Q2M AUDIO CODEC DRIVER +M: Karl Asseily +L: linux-sound@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml +F: sound/soc/codecs/es9039q2m.c + ESD CAN NETWORK DRIVERS M: Stefan Mätje R: socketcan@esd.eu From 78da571393daf0ece9067fe040018a2c02c081a7 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Tue, 8 Sep 2026 11:04:43 +0200 Subject: [PATCH 269/924] ASoC: dt-bindings: Add Airoha AN7581 AFE Sound card Add YAML schema for Airoha AN7581 AFE SoC sound card. The AFE handling on this SoC is very basic with clock always enabled and all the power saving feature not available. Signed-off-by: Christian Marangi Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260908090448.3332-2-ansuelsmth@gmail.com Signed-off-by: Mark Brown --- .../bindings/sound/airoha,an7581-afe.yaml | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml diff --git a/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml b/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml new file mode 100644 index 000000000000..80d9e87f1470 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/airoha,an7581-afe.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Airoha AFE PCM controller for AN7581 + +maintainers: + - Christian Marangi + +properties: + compatible: + const: airoha,an7581-afe + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + #include + #include + + afe@1fbe2200 { + compatible = "airoha,an7581-afe"; + reg = <0x1fbe2200 0x9000>; + + interrupts = ; + }; + +... From 4974ffa0d6c28dfaba838a29503c3af02080a8a1 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Tue, 8 Sep 2026 11:04:46 +0200 Subject: [PATCH 270/924] ASoC: airoha: Add AFE driver for Airoha AN7581 Add support for the Sound system present on Airoha AN7581 SoC. This is based on the Mediatek AFE drivers and adds the PCM and the ETDM driver. Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260908090448.3332-5-ansuelsmth@gmail.com Signed-off-by: Mark Brown --- MAINTAINERS | 8 + sound/soc/mediatek/Kconfig | 17 +- sound/soc/mediatek/Makefile | 1 + sound/soc/mediatek/an7581/Makefile | 8 + sound/soc/mediatek/an7581/an7581-afe-common.h | 49 ++ sound/soc/mediatek/an7581/an7581-afe-pcm.c | 515 ++++++++++++++++++ sound/soc/mediatek/an7581/an7581-dai-etdm.c | 433 +++++++++++++++ sound/soc/mediatek/an7581/an7581-reg.h | 114 ++++ 8 files changed, 1144 insertions(+), 1 deletion(-) create mode 100644 sound/soc/mediatek/an7581/Makefile create mode 100644 sound/soc/mediatek/an7581/an7581-afe-common.h create mode 100644 sound/soc/mediatek/an7581/an7581-afe-pcm.c create mode 100644 sound/soc/mediatek/an7581/an7581-dai-etdm.c create mode 100644 sound/soc/mediatek/an7581/an7581-reg.h diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c..679da67ce63d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -767,6 +767,14 @@ F: Documentation/devicetree/bindings/phy/airoha,en7581-pcie-phy.yaml F: drivers/phy/phy-airoha-pcie-regs.h F: drivers/phy/phy-airoha-pcie.c +AIROHA SOUND DRIVER +M: Christian Marangi +L: linux-sound@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml +F: Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml +F: sound/soc/mediatek/an7581/* + AIROHA SPI SNFI DRIVER M: Lorenzo Bianconi M: Ray Liu diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index 224746e7664f..eb6f2a443b23 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -1,10 +1,25 @@ # SPDX-License-Identifier: GPL-2.0-only -menu "Mediatek" config SND_SOC_MEDIATEK tristate select REGMAP_MMIO +menu "Airoha" + +config SND_SOC_AN7581 + tristate "ASoC support for Airoha AN7581 chip" + depends on ARCH_AIROHA || COMPILE_TEST + select SND_SOC_MEDIATEK + help + This adds ASoC platform driver support for Airoha AN7581 chip + that can be used with other codecs. + Select Y if you have such device. + If unsure select "N". + +endmenu + +menu "Mediatek" + config SND_SOC_MT2701 tristate "ASoC support for Mediatek MT2701 chip" depends on ARCH_MEDIATEK || COMPILE_TEST diff --git a/sound/soc/mediatek/Makefile b/sound/soc/mediatek/Makefile index a6815a3c5988..54522e1fa016 100644 --- a/sound/soc/mediatek/Makefile +++ b/sound/soc/mediatek/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_SND_SOC_MEDIATEK) += common/ +obj-$(CONFIG_SND_SOC_AN7581) += an7581/ obj-$(CONFIG_SND_SOC_MT2701) += mt2701/ obj-$(CONFIG_SND_SOC_MT6797) += mt6797/ obj-$(CONFIG_SND_SOC_MT7986) += mt7986/ diff --git a/sound/soc/mediatek/an7581/Makefile b/sound/soc/mediatek/an7581/Makefile new file mode 100644 index 000000000000..d6de5ee1668e --- /dev/null +++ b/sound/soc/mediatek/an7581/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +# platform driver +snd-soc-an7581-afe-y := \ + an7581-afe-pcm.o \ + an7581-dai-etdm.o + +obj-$(CONFIG_SND_SOC_AN7581) += snd-soc-an7581-afe.o diff --git a/sound/soc/mediatek/an7581/an7581-afe-common.h b/sound/soc/mediatek/an7581/an7581-afe-common.h new file mode 100644 index 000000000000..ebc8e2f7fec4 --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-afe-common.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * an7581-afe-common.h -- Airoha AN7581 audio driver definitions + */ + +#ifndef _AN7581_AFE_COMMON_H_ +#define _AN7581_AFE_COMMON_H_ + +#include +#include +#include +#include + +#include "../../mediatek/common/mtk-base-afe.h" + +enum { + AN7581_MEMIF_DL1, + AN7581_MEMIF_UL1, + AN7581_MEMIF_NUM, + AN7581_DAI_ETDM = AN7581_MEMIF_NUM, + AN7581_DAI_NUM, +}; + +enum { + AN7581_IRQ_0, + AN7581_IRQ_1, + AN7581_IRQ_NUM, +}; + +struct an7581_memif_irq_desc { + u8 irq; + u32 status_bit; + u32 clear_reg; +}; + +struct an7581_afe_private { + unsigned int users; + /* protect concurrent ETDM user tracking */ + struct mutex user_lock; + + void *dai_priv[AN7581_DAI_NUM]; +}; + +unsigned int an7581_afe_rate_transform(struct device *dev, + unsigned int rate); + +int an7581_dai_etdm_register(struct mtk_base_afe *afe); + +#endif diff --git a/sound/soc/mediatek/an7581/an7581-afe-pcm.c b/sound/soc/mediatek/an7581/an7581-afe-pcm.c new file mode 100644 index 000000000000..a1d742362538 --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-afe-pcm.c @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha ALSA SoC AFE platform driver for AN7581 + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "an7581-afe-common.h" +#include "an7581-reg.h" +#include "../common/mtk-afe-platform-driver.h" +#include "../common/mtk-afe-fe-dai.h" + +enum { + ARH_AFE_RATE_8K = 0, + ARH_AFE_RATE_12K = 1, + ARH_AFE_RATE_16K = 2, + ARH_AFE_RATE_24K = 3, + ARH_AFE_RATE_32K = 4, + ARH_AFE_RATE_48K = 5, + ARH_AFE_RATE_96K = 6, + ARH_AFE_RATE_192K = 7, + ARH_AFE_RATE_384K = 8, + ARH_AFE_RATE_7K = 16, + ARH_AFE_RATE_11K = 17, + ARH_AFE_RATE_14K = 18, + ARH_AFE_RATE_22K = 19, + ARH_AFE_RATE_29K = 20, + ARH_AFE_RATE_44K = 21, + ARH_AFE_RATE_88K = 22, + ARH_AFE_RATE_176K = 23, + ARH_AFE_RATE_352K = 24, +}; + +unsigned int an7581_afe_rate_transform(struct device *dev, unsigned int rate) +{ + switch (rate) { + case 7350: + return ARH_AFE_RATE_7K; + case 8000: + return ARH_AFE_RATE_8K; + case 11025: + return ARH_AFE_RATE_11K; + case 12000: + return ARH_AFE_RATE_12K; + case 14700: + return ARH_AFE_RATE_14K; + case 16000: + return ARH_AFE_RATE_16K; + case 22050: + return ARH_AFE_RATE_22K; + case 24000: + return ARH_AFE_RATE_24K; + case 29400: + return ARH_AFE_RATE_29K; + case 32000: + return ARH_AFE_RATE_32K; + case 44100: + return ARH_AFE_RATE_44K; + case 48000: + return ARH_AFE_RATE_48K; + case 88200: + return ARH_AFE_RATE_88K; + case 96000: + return ARH_AFE_RATE_96K; + case 176400: + return ARH_AFE_RATE_176K; + case 192000: + return ARH_AFE_RATE_192K; + case 352800: + return ARH_AFE_RATE_352K; + case 384000: + return ARH_AFE_RATE_384K; + default: + dev_warn_ratelimited(dev, "%s(), rate %u invalid, using %d!\n", + __func__, rate, ARH_AFE_RATE_48K); + return ARH_AFE_RATE_48K; + } +} + +static const struct an7581_memif_irq_desc an7581_memif_irq_descs[AN7581_MEMIF_NUM] = { + [AN7581_MEMIF_DL1] = { + .irq = AN7581_IRQ_0, + .status_bit = AFE_IRQ_STS_PLAY, + .clear_reg = AFE_IRQ_CON0, + }, + [AN7581_MEMIF_UL1] = { + .irq = AN7581_IRQ_1, + .status_bit = AFE_IRQ_STS_RECORD, + .clear_reg = AFE_IRQ1_CON0, + }, +}; + +static const struct snd_pcm_hardware an7581_afe_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 512, + .period_bytes_max = 128 * 1024, + .periods_min = 2, + .periods_max = 256, + .buffer_bytes_max = 256 * 1024, +}; + +static int an7581_memif_irq_fs(struct snd_pcm_substream *substream, unsigned int rate) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); + + return an7581_afe_rate_transform(afe->dev, rate); +} + +static int an7581_afe_fe_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = mtk_afe_fe_startup(substream, dai); + if (ret < 0) + return ret; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + 0x2000, UINT_MAX); + if (ret < 0) + dev_err(afe->dev, "hw_constraint_minmax failed\n"); + } + + return ret; +} + +const struct snd_soc_dai_ops an7581_afe_fe_ops = { + .startup = an7581_afe_fe_startup, + .shutdown = mtk_afe_fe_shutdown, + .hw_params = mtk_afe_fe_hw_params, + .hw_free = mtk_afe_fe_hw_free, + .prepare = mtk_afe_fe_prepare, + .trigger = mtk_afe_fe_trigger, +}; + +#define ARH_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver an7581_memif_dai_driver[] = { + /* FE DAIs: memory intefaces to CPU */ + { + .name = "DL1", + .id = AN7581_MEMIF_DL1, + .playback = { + .stream_name = "DL1", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = ARH_PCM_FORMATS, + }, + .ops = &an7581_afe_fe_ops, + }, + { + .name = "UL1", + .id = AN7581_MEMIF_UL1, + .capture = { + .stream_name = "UL1", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = ARH_PCM_FORMATS, + }, + .ops = &an7581_afe_fe_ops, + }, +}; + +static const struct snd_soc_dapm_widget an7581_memif_widgets[] = { + /* DL */ + SND_SOC_DAPM_MIXER("I032", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I033", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* UL */ + SND_SOC_DAPM_MIXER("O018", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("O019", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route an7581_memif_routes[] = { + {"I032", NULL, "DL1"}, + {"I033", NULL, "DL1"}, + {"UL1", NULL, "O018"}, + {"UL1", NULL, "O019"}, + {"O018", NULL, "I150"}, + {"O019", NULL, "I151"}, +}; + +static const struct snd_soc_component_driver an7581_afe_pcm_dai_component = { + .name = "an7581-afe-pcm-dai", +}; + +static const struct mtk_base_memif_data memif_data[AN7581_MEMIF_NUM] = { + [AN7581_MEMIF_DL1] = { + .name = "DL1", + .id = AN7581_MEMIF_DL1, + .reg_ofs_base = AFE_DL1_BASE, + .reg_ofs_cur = AFE_DL1_CUR, + .reg_ofs_end = AFE_DL1_END, + .fs_reg = -1, + .fs_shift = -1, + .fs_maskbit = -1, + .mono_reg = -1, + .mono_shift = -1, + .hd_reg = AFE_DL1_CON0, + .hd_shift = AFE_HD_SHIFT, + .hd_align_reg = -1, + .hd_align_mshift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = AFE_DL1_ENABLE_SHIFT, + .msb_reg = -1, + .msb_shift = -1, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, + [AN7581_MEMIF_UL1] = { + .name = "UL1", + .id = AN7581_MEMIF_UL1, + .reg_ofs_base = AFE_UL1_BASE, + .reg_ofs_cur = AFE_UL1_CUR, + .reg_ofs_end = AFE_UL1_END, + .fs_reg = -1, + .fs_shift = -1, + .fs_maskbit = -1, + .mono_reg = -1, + .mono_shift = -1, + .hd_reg = AFE_UL1_CON0, + .hd_shift = AFE_HD_SHIFT, + .hd_align_reg = AFE_UL1_CON0, + .hd_align_mshift = AFE_HD_ALIGN_SHIFT, + .enable_reg = AFE_DAC_CON0, + .enable_shift = AFE_UL1_ENABLE_SHIFT, + .msb_reg = -1, + .msb_shift = -1, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, +}; + +static const struct mtk_base_irq_data irq_data[AN7581_IRQ_NUM] = { + [AN7581_IRQ_0] = { + .id = AN7581_IRQ_0, + .irq_cnt_reg = AFE_IRQ_CNT, + .irq_cnt_shift = AFE_IRQ_CNT_SHIFT, + .irq_cnt_maskbit = AFE_IRQ_CNT_MASK, + .irq_en_reg = AFE_IRQ_CON0, + .irq_en_shift = AFE_IRQ_ON_SHIFT, + .irq_fs_reg = -1, + .irq_fs_shift = -1, + .irq_fs_maskbit = -1, + .irq_clr_reg = -1, + .irq_clr_shift = -1, + }, + [AN7581_IRQ_1] = { + .id = AN7581_IRQ_1, + .irq_cnt_reg = AFE_IRQ1_CNT, + .irq_cnt_shift = AFE_IRQ_CNT_SHIFT, + .irq_cnt_maskbit = AFE_IRQ_CNT_MASK, + .irq_en_reg = AFE_IRQ1_CON0, + .irq_en_shift = AFE_IRQ_ON_SHIFT, + .irq_fs_reg = -1, + .irq_fs_shift = -1, + .irq_fs_maskbit = -1, + .irq_clr_reg = -1, + .irq_clr_shift = -1, + }, +}; + +static const struct regmap_config an7581_afe_regmap_config = { + .name = "afe", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AFE_MAX_REGISTER, +}; + +static const struct regmap_config an7581_afe_irq1_regmap_config = { + .name = "afe_irq1", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AFE_IRQ1_MAX_REGISTER, +}; + +static irqreturn_t an7581_afe_irq_handler(int irq_id, void *dev) +{ + const struct an7581_memif_irq_desc *irq_desc; + struct mtk_base_afe *afe = dev; + u32 status = 0; + int i; + + regmap_read(afe->regmap, AFE_IRQ_STS, &status); + + for (i = 0; i < AN7581_MEMIF_NUM; i++) { + struct mtk_base_afe_memif *memif = &afe->memif[i]; + + if (!memif->substream) + continue; + + if (memif->irq_usage < 0) + continue; + + irq_desc = &an7581_memif_irq_descs[i]; + if (status & irq_desc->status_bit) + snd_pcm_period_elapsed(memif->substream); + } + + for (i = 0; i < AN7581_MEMIF_NUM; i++) { + struct regmap *irq_regmap; + + irq_desc = &an7581_memif_irq_descs[i]; + if (!(status & irq_desc->status_bit)) + continue; + + irq_regmap = afe->irqs[irq_desc->irq].regmap; + regmap_set_bits(irq_regmap, irq_desc->clear_reg, + BIT(AFE_IRQ_CLR_SHIFT)); + regmap_clear_bits(irq_regmap, irq_desc->clear_reg, + BIT(AFE_IRQ_CLR_SHIFT)); + + regmap_set_bits(irq_regmap, irq_desc->clear_reg, + BIT(AFE_IRQ_MISS_FLG_CLR_SHIFT)); + regmap_clear_bits(irq_regmap, irq_desc->clear_reg, + BIT(AFE_IRQ_MISS_FLG_CLR_SHIFT)); + } + + return IRQ_HANDLED; +} + +static int an7581_dai_memif_register(struct mtk_base_afe *afe) +{ + struct mtk_base_afe_dai *dai; + + dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL); + if (!dai) + return -ENOMEM; + + list_add(&dai->list, &afe->sub_dais); + + dai->dai_drivers = an7581_memif_dai_driver; + dai->num_dai_drivers = ARRAY_SIZE(an7581_memif_dai_driver); + + dai->dapm_widgets = an7581_memif_widgets; + dai->num_dapm_widgets = ARRAY_SIZE(an7581_memif_widgets); + dai->dapm_routes = an7581_memif_routes; + dai->num_dapm_routes = ARRAY_SIZE(an7581_memif_routes); + + return 0; +} + +typedef int (*dai_register_cb)(struct mtk_base_afe *); +static const dai_register_cb dai_register_cbs[] = { + an7581_dai_etdm_register, + an7581_dai_memif_register, +}; + +static int an7581_afe_pcm_dev_probe(struct platform_device *pdev) +{ + struct an7581_afe_private *afe_priv; + struct device *dev = &pdev->dev; + struct reset_control *reset; + struct mtk_base_afe *afe; + int i, irq_id, ret; + void *base; + + afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + platform_set_drvdata(pdev, afe); + + afe->platform_priv = devm_kzalloc(dev, sizeof(*afe_priv), + GFP_KERNEL); + if (!afe->platform_priv) + return -ENOMEM; + + afe->irqs_size = AN7581_IRQ_NUM; + afe->irqs = devm_kcalloc(dev, afe->irqs_size, sizeof(*afe->irqs), + GFP_KERNEL); + if (!afe->irqs) + return -ENOMEM; + + afe->memif_size = AN7581_MEMIF_NUM; + afe->memif = devm_kcalloc(dev, afe->memif_size, sizeof(*afe->memif), + GFP_KERNEL); + if (!afe->memif) + return -ENOMEM; + + afe_priv = afe->platform_priv; + mutex_init(&afe_priv->user_lock); + afe->dev = &pdev->dev; + + reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(reset)) + return PTR_ERR(reset); + + /* Global reset I2S */ + reset_control_assert(reset); + usleep_range(10, 20); + reset_control_deassert(reset); + + afe->base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + + base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(base)) + return PTR_ERR(base); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr, + &an7581_afe_regmap_config); + if (IS_ERR(afe->regmap)) + return PTR_ERR(afe->regmap); + + mutex_init(&afe->irq_alloc_lock); + + /* irq initialize */ + for (i = 0; i < afe->irqs_size; i++) + afe->irqs[i].irq_data = &irq_data[i]; + + afe->irqs[AN7581_IRQ_0].regmap = afe->regmap; + afe->irqs[AN7581_IRQ_1].regmap = devm_regmap_init_mmio(&pdev->dev, base, + &an7581_afe_irq1_regmap_config); + if (IS_ERR(afe->irqs[AN7581_IRQ_1].regmap)) + return PTR_ERR(afe->irqs[AN7581_IRQ_1].regmap); + + /* request irq */ + irq_id = platform_get_irq(pdev, 0); + if (irq_id < 0) + return irq_id; + + /* init memif */ + for (i = 0; i < afe->memif_size; i++) { + int sel_irq = an7581_memif_irq_descs[i].irq; + + afe->memif[i].data = &memif_data[i]; + afe->memif[i].irq_usage = sel_irq; + afe->memif[i].const_irq = 1; + afe->irqs[sel_irq].irq_occupyed = true; + } + + /* init sub_dais */ + INIT_LIST_HEAD(&afe->sub_dais); + + ret = devm_request_irq(dev, irq_id, an7581_afe_irq_handler, + IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); + if (ret) + return dev_err_probe(dev, ret, "Failed to request irq for asys-isr\n"); + + for (i = 0; i < ARRAY_SIZE(dai_register_cbs); i++) { + ret = dai_register_cbs[i](afe); + if (ret) + return dev_err_probe(dev, ret, "DAI register failed, i: %d\n", i); + } + + /* init dai_driver and component_driver */ + ret = mtk_afe_combine_sub_dai(afe); + if (ret) + return dev_err_probe(dev, ret, "mtk_afe_combine_sub_dai fail\n"); + + afe->mtk_afe_hardware = &an7581_afe_hardware; + afe->memif_fs = an7581_memif_irq_fs; + afe->irq_fs = an7581_memif_irq_fs; + + /* register component */ + ret = devm_snd_soc_register_component(&pdev->dev, + &mtk_afe_pcm_platform, + NULL, 0); + if (ret) + return dev_err_probe(dev, ret, "Cannot register AFE component\n"); + + ret = devm_snd_soc_register_component(afe->dev, + &an7581_afe_pcm_dai_component, + afe->dai_drivers, + afe->num_dai_drivers); + if (ret) + return dev_err_probe(dev, ret, "Cannot register PCM DAI component\n"); + + return 0; +} + +static const struct of_device_id an7581_afe_pcm_dt_match[] = { + { .compatible = "airoha,an7581-afe" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, an7581_afe_pcm_dt_match); + +static struct platform_driver an7581_afe_pcm_driver = { + .driver = { + .name = "an7581-audio", + .of_match_table = an7581_afe_pcm_dt_match, + }, + .probe = an7581_afe_pcm_dev_probe, +}; +module_platform_driver(an7581_afe_pcm_driver); + +MODULE_DESCRIPTION("Airoha SoC AFE platform driver for ALSA AN7581"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mediatek/an7581/an7581-dai-etdm.c b/sound/soc/mediatek/an7581/an7581-dai-etdm.c new file mode 100644 index 000000000000..7fbfd8fe1836 --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-dai-etdm.c @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha ALSA SoC Audio DAI eTDM Control + * + */ + +#include +#include +#include +#include +#include "an7581-afe-common.h" +#include "an7581-reg.h" + +#define HOPPING_CLK 0 +#define APLL_CLK 1 +#define MTK_DAI_ETDM_FORMAT_I2S 0 +#define MTK_DAI_ETDM_FORMAT_DSPA 4 +#define MTK_DAI_ETDM_FORMAT_DSPB 5 + +enum { + MTK_ETDM_RATE_8K = 0, + MTK_ETDM_RATE_12K = 1, + MTK_ETDM_RATE_16K = 2, + MTK_ETDM_RATE_24K = 3, + MTK_ETDM_RATE_32K = 4, + MTK_ETDM_RATE_48K = 5, + MTK_ETDM_RATE_96K = 6, + MTK_ETDM_RATE_192K = 7, + MTK_ETDM_RATE_384K = 8, + MTK_ETDM_RATE_7K = 16, + MTK_ETDM_RATE_11K = 17, + MTK_ETDM_RATE_14K = 18, + MTK_ETDM_RATE_22K = 19, + MTK_ETDM_RATE_29K = 20, + MTK_ETDM_RATE_44K = 21, + MTK_ETDM_RATE_88K = 22, + MTK_ETDM_RATE_176K = 23, + MTK_ETDM_RATE_352K = 24, +}; + +struct mtk_dai_etdm_priv { + bool bck_inv; + bool lrck_inv; + bool slave_mode; + unsigned int format; +}; + +static unsigned int an7581_etdm_rate_transform(struct device *dev, unsigned int rate) +{ + switch (rate) { + case 7350: + return MTK_ETDM_RATE_7K; + case 8000: + return MTK_ETDM_RATE_8K; + case 11025: + return MTK_ETDM_RATE_11K; + case 12000: + return MTK_ETDM_RATE_12K; + case 14700: + return MTK_ETDM_RATE_14K; + case 16000: + return MTK_ETDM_RATE_16K; + case 22050: + return MTK_ETDM_RATE_22K; + case 24000: + return MTK_ETDM_RATE_24K; + case 29400: + return MTK_ETDM_RATE_29K; + case 32000: + return MTK_ETDM_RATE_32K; + case 44100: + return MTK_ETDM_RATE_44K; + case 48000: + return MTK_ETDM_RATE_48K; + case 88200: + return MTK_ETDM_RATE_88K; + case 96000: + return MTK_ETDM_RATE_96K; + case 176400: + return MTK_ETDM_RATE_176K; + case 192000: + return MTK_ETDM_RATE_192K; + case 352800: + return MTK_ETDM_RATE_352K; + case 384000: + return MTK_ETDM_RATE_384K; + default: + dev_warn_ratelimited(dev, "%s(), rate %u invalid, using %d!\n", + __func__, rate, MTK_ETDM_RATE_48K); + return MTK_ETDM_RATE_48K; + } +} + +static int get_etdm_wlen(unsigned int bitwidth) +{ + return bitwidth <= 16 ? 16 : 32; +} + +static const struct snd_soc_dapm_widget mtk_dai_etdm_widgets[] = { + /* DL */ + SND_SOC_DAPM_MIXER("I150", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I151", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* UL */ + SND_SOC_DAPM_MIXER("O124", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("O125", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route mtk_dai_etdm_routes[] = { + {"I150", NULL, "ETDM Capture"}, + {"I151", NULL, "ETDM Capture"}, + {"ETDM Playback", NULL, "O124"}, + {"ETDM Playback", NULL, "O125"}, + {"O124", NULL, "I032"}, + {"O125", NULL, "I033"}, +}; + +/* dai ops */ +static int mtk_dai_etdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct an7581_afe_private *afe_priv = afe->platform_priv; + + guard(mutex)(&afe_priv->user_lock); + + if (!afe_priv->users) + regmap_set_bits(afe->regmap, AFE_DAC_CON0, + BIT(AFE_AFE_ENABLE_SHIFT)); + afe_priv->users++; + + return 0; +} + +static void mtk_dai_etdm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct an7581_afe_private *afe_priv = afe->platform_priv; + + guard(mutex)(&afe_priv->user_lock); + + afe_priv->users--; + if (!afe_priv->users) + regmap_clear_bits(afe->regmap, AFE_DAC_CON0, + BIT(AFE_AFE_ENABLE_SHIFT)); +} + +static unsigned int get_etdm_ch_fixup(unsigned int channels) +{ + if (channels > 16) + return 24; + + if (channels > 8) + return 16; + + if (channels > 4) + return 8; + + if (channels > 2) + return 4; + + return 2; +} + +static int mtk_dai_etdm_config(struct mtk_base_afe *afe, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, + int stream) +{ + struct an7581_afe_private *afe_priv = afe->platform_priv; + struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai->id]; + unsigned int rate = params_rate(params); + unsigned int etdm_rate = an7581_etdm_rate_transform(afe->dev, rate); + unsigned int bit_width = params_width(params); + unsigned int mask, mask1; + unsigned int val, val1; + + dev_dbg(afe->dev, "%s(), stream %d, rate %u, bitwidth %u\n", + __func__, stream, rate, params_width(params)); + + /* CON0 */ + mask = ETDM_SLAVE_MODE | ETDM_BIT_LEN | ETDM_WRD_LEN | + ETDM_FMT | ETDM_CH_NUM; + val = FIELD_PREP(ETDM_BIT_LEN, params_width(params) - 1) | + FIELD_PREP(ETDM_WRD_LEN, get_etdm_wlen(bit_width) - 1) | + FIELD_PREP(ETDM_FMT, etdm_data->format) | + FIELD_PREP(ETDM_CH_NUM, + get_etdm_ch_fixup(params_channels(params)) - 1); + if (etdm_data->slave_mode) + val |= ETDM_SLAVE_MODE; + + /* CON1 */ + mask1 = EDTM_LRCK_AUTO_MODE | EDTM_CKEN_SEL | EDTM_LRCK_AUTO_OFF | + EDTM_INITIAL_POINT | EDTM_INITIAL_COUNT; + val1 = EDTM_LRCK_AUTO_MODE | EDTM_CKEN_SEL | EDTM_LRCK_AUTO_OFF | + FIELD_PREP(EDTM_INITIAL_POINT, 14) | FIELD_PREP(EDTM_INITIAL_COUNT, 14); + + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + regmap_update_bits(afe->regmap, ETDM_OUT1_CON0, mask, val); + + mask1 |= EDTM_DIRECT_INPUT_MASTER_BCK; + val1 |= EDTM_DIRECT_INPUT_MASTER_BCK; + + regmap_update_bits(afe->regmap, ETDM_OUT1_CON1, mask1, val1); + + regmap_update_bits(afe->regmap, ETDM_OUT1_CON4, OUT_SEL_FS, + FIELD_PREP(OUT_SEL_FS, etdm_rate)); + + regmap_update_bits(afe->irqs[AN7581_IRQ_0].regmap, + afe->irqs[AN7581_IRQ_0].irq_data->irq_en_reg, + AFE_IRQ_EN_SEL, AFE_IRQ_EN_SEL_I2SOUT); + break; + case SNDRV_PCM_STREAM_CAPTURE: + regmap_update_bits(afe->regmap, ETDM_IN1_CON0, mask, val); + + regmap_update_bits(afe->regmap, ETDM_IN1_CON1, mask1, val1); + + regmap_update_bits(afe->regmap, ETDM_IN1_CON3, IN_SEL_FS, + FIELD_PREP(IN_SEL_FS, etdm_rate)); + + regmap_update_bits(afe->irqs[AN7581_IRQ_1].regmap, + afe->irqs[AN7581_IRQ_1].irq_data->irq_en_reg, + AFE_IRQ_EN_SEL, AFE_IRQ_EN_SEL_I2SIN); + break; + default: + break; + } + + return 0; +} + +static int mtk_dai_etdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + unsigned int rate = params_rate(params); + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + + regmap_update_bits(afe->regmap, ETDM_COWORK_CON0, + EDTM_IN1_SLAVE_SEL, + EDTM_IN1_SLAVE_FROM_ETDMIN1_SLAVE); + regmap_update_bits(afe->regmap, ETDM_COWORK_CON0, + EDTM_OUT1_SLAVE_SEL, + EDTM_OUT1_SLAVE_FROM_ETDMOUT1_SLAVE); + regmap_update_bits(afe->regmap, ETDM_COWORK_CON1, + EDTM_IN1_SDATA0_SEL, + EDTM_IN1_SDATA0_FROM_PAD); + + switch (rate) { + case 7350: + case 8000: + case 11025: + case 12000: + case 14700: + case 16000: + case 22050: + case 24000: + case 29400: + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + case 176400: + case 192000: + case 352800: + case 384000: + return mtk_dai_etdm_config(afe, params, dai, substream->stream); + default: + dev_err(afe->dev, "Sample rate %d invalid\n", rate); + return -EINVAL; + } +} + +static int mtk_dai_etdm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + + dev_dbg(afe->dev, "%s(), cmd %d, dai id %d\n", __func__, cmd, dai->id); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_set_bits(afe->regmap, ETDM_OUT1_CON0, + ETDM_OUT_EN); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + regmap_set_bits(afe->regmap, ETDM_IN1_CON0, + ETDM_IN_EN); + + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_clear_bits(afe->regmap, ETDM_OUT1_CON0, + ETDM_OUT_EN); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + regmap_clear_bits(afe->regmap, ETDM_IN1_CON0, + ETDM_IN_EN); + + break; + default: + break; + } + + return 0; +} + +static int mtk_dai_etdm_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct an7581_afe_private *afe_priv = afe->platform_priv; + struct mtk_dai_etdm_priv *etdm_data; + void *priv_data; + + priv_data = devm_kzalloc(afe->dev, sizeof(struct mtk_dai_etdm_priv), + GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + + afe_priv->dai_priv[dai->id] = priv_data; + etdm_data = afe_priv->dai_priv[dai->id]; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + etdm_data->format = MTK_DAI_ETDM_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + etdm_data->format = MTK_DAI_ETDM_FORMAT_DSPA; + break; + case SND_SOC_DAIFMT_DSP_B: + etdm_data->format = MTK_DAI_ETDM_FORMAT_DSPB; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + etdm_data->bck_inv = false; + etdm_data->lrck_inv = false; + break; + case SND_SOC_DAIFMT_NB_IF: + etdm_data->bck_inv = false; + etdm_data->lrck_inv = true; + break; + case SND_SOC_DAIFMT_IB_NF: + etdm_data->bck_inv = true; + etdm_data->lrck_inv = false; + break; + case SND_SOC_DAIFMT_IB_IF: + etdm_data->bck_inv = true; + etdm_data->lrck_inv = true; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BP_FP: + etdm_data->slave_mode = false; + break; + case SND_SOC_DAIFMT_BC_FC: + etdm_data->slave_mode = true; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops mtk_dai_etdm_ops = { + .startup = mtk_dai_etdm_startup, + .shutdown = mtk_dai_etdm_shutdown, + .hw_params = mtk_dai_etdm_hw_params, + .trigger = mtk_dai_etdm_trigger, + .set_fmt = mtk_dai_etdm_set_fmt, +}; + +/* dai driver */ +#define MTK_ETDM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver mtk_dai_etdm_driver[] = { + { + .name = "ETDM", + .id = AN7581_DAI_ETDM, + .capture = { + .stream_name = "ETDM Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = MTK_ETDM_FORMATS, + }, + .playback = { + .stream_name = "ETDM Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = MTK_ETDM_FORMATS, + }, + .ops = &mtk_dai_etdm_ops, + .symmetric_rate = 1, + .symmetric_sample_bits = 1, + }, +}; + +int an7581_dai_etdm_register(struct mtk_base_afe *afe) +{ + struct mtk_base_afe_dai *dai; + + dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL); + if (!dai) + return -ENOMEM; + + list_add(&dai->list, &afe->sub_dais); + + dai->dai_drivers = mtk_dai_etdm_driver; + dai->num_dai_drivers = ARRAY_SIZE(mtk_dai_etdm_driver); + + dai->dapm_widgets = mtk_dai_etdm_widgets; + dai->num_dapm_widgets = ARRAY_SIZE(mtk_dai_etdm_widgets); + dai->dapm_routes = mtk_dai_etdm_routes; + dai->num_dapm_routes = ARRAY_SIZE(mtk_dai_etdm_routes); + + return 0; +} diff --git a/sound/soc/mediatek/an7581/an7581-reg.h b/sound/soc/mediatek/an7581/an7581-reg.h new file mode 100644 index 000000000000..a6ada3ca97ab --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-reg.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * an7581-reg.h -- Airoha AN7581 audio driver reg definition + */ + +#ifndef _AN7581_REG_H_ +#define _AN7581_REG_H_ + +#define AFE_DAC_CON0 0x0 +#define AFE_DL1_ENABLE_SHIFT 17 +#define AFE_UL1_ENABLE_SHIFT 1 +#define AFE_AFE_ENABLE_SHIFT 0 + +#define ETDM_COWORK_CON0 0x4c +#define EDTM_IN1_SLAVE_SEL GENMASK(27, 24) +#define EDTM_IN1_SLAVE_FROM_ETDMIN1_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x1) +#define EDTM_IN1_SLAVE_FROM_ETDMIN2_MASTER FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x2) +#define EDTM_IN1_SLAVE_FROM_ETDMIN2_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x3) +#define EDTM_IN1_SLAVE_FROM_ETDMOUT1_MASTER FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x8) +#define EDTM_IN1_SLAVE_FROM_ETDMOUT1_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x9) +#define EDTM_IN1_SLAVE_FROM_ETDMOUT2_MASTER FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0xa) +#define EDTM_IN1_SLAVE_FROM_ETDMOUT2_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0xb) +#define EDTM_OUT1_SLAVE_SEL GENMASK(11, 8) +#define EDTM_OUT1_SLAVE_FROM_ETDMIN1_MASTER FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x0) +#define EDTM_OUT1_SLAVE_FROM_ETDMIN1_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x1) +#define EDTM_OUT1_SLAVE_FROM_ETDMIN2_MASTER FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x2) +#define EDTM_OUT1_SLAVE_FROM_ETDMIN2_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x3) +#define EDTM_OUT1_SLAVE_FROM_ETDMOUT1_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x9) +#define EDTM_OUT1_SLAVE_FROM_ETDMOUT2_MASTER FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0xa) +#define EDTM_OUT1_SLAVE_FROM_ETDMOUT2_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0xb) +#define ETDM_COWORK_CON1 0x50 +#define EDTM_IN1_SDATA0_SEL GENMASK(3, 0) +#define EDTM_IN1_SDATA0_FROM_PAD FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0x0) +#define EDTM_IN1_SDATA0_FROM_ETDMIN2 FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0x2) +#define EDTM_IN1_SDATA0_FROM_ETDMOUT1 FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0x8) +#define EDTM_IN1_SDATA0_FROM_ETDMOUT2 FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0xa) +#define ETDM_IN1_CON0 0x5c +#define ETDM_CH_NUM GENMASK(26, 23) +#define ETDM_WRD_LEN GENMASK(20, 16) +#define ETDM_BIT_LEN GENMASK(15, 11) +#define ETDM_FMT GENMASK(8, 6) +#define ETDM_SLAVE_MODE BIT(5) +#define ETDM_IN_EN BIT(0) +#define ETDM_IN1_CON1 0x60 +#define EDTM_LRCK_AUTO_MODE BIT(29) +#define EDTM_DIRECT_INPUT_MASTER_BCK BIT(30) +#define EDTM_CKEN_SEL BIT(12) +#define EDTM_LRCK_AUTO_OFF BIT(10) +#define EDTM_INITIAL_POINT GENMASK(9, 5) +#define EDTM_INITIAL_COUNT GENMASK(4, 0) +#define ETDM_IN1_CON2 0x64 +#define IN_CLK_SRC GENMASK(12, 10) +#define ETDM_IN1_CON3 0x68 +#define IN_SEL_FS GENMASK(30, 26) +#define ETDM_IN1_CON4 0x6c +#define IN_RELATCH GENMASK(24, 20) +#define IN_CLK_INV BIT(18) +#define ETDM_IN1_CON5 0x70 +#define ETDM_IN1_CON6 0x74 +#define ETDM_OUT1_CON0 0x7c +#define ETDM_SYNC_MODE BIT(1) +#define ETDM_OUT_EN BIT(0) +#define ETDM_OUT1_CON1 0x80 +#define ETDM_OUT1_CON2 0x84 +#define ETDM_OUT1_CON3 0x88 +#define ETDM_OUT1_CON4 0x8c +#define OUT_RELATCH GENMASK(28, 24) +#define OUT_CLK_SRC GENMASK(8, 6) +#define OUT_SEL_FS GENMASK(4, 0) +#define ETDM_OUT1_CON5 0x90 +#define ETDM_CLK_DIV BIT(12) +#define OUT_CLK_INV BIT(9) +#define ETDM_OUT1_CON6 0x94 +#define ETDM_OUT1_CON7 0x98 + +#define AFE_DL1_BASE 0xa8 +#define AFE_DL1_END 0xb0 +#define AFE_DL1_CUR 0xac +#define AFE_DL1_CON0 0xb4 +#define AFE_PBUF_SIZE_SHIFT 16 +#define AFE_PBUF_SIZE_MASK GENMASK(1, 0) +#define AFE_MINLEN_SHIFT 8 +#define AFE_MINLEN_MASK GENMASK(3, 0) +#define AFE_HD_SHIFT 5 + +#define AFE_UL1_BASE 0xc4 +#define AFE_UL1_END 0xc8 +#define AFE_UL1_CUR 0xcc +#define AFE_UL1_CON0 0xd0 +#define AFE_HD_ALIGN_SHIFT 6 + +#define AFE_IRQ_CON0 0xe4 +#define AFE_IRQ_EN_SEL BIT(4) +#define AFE_IRQ_EN_SEL_I2SIN FIELD_PREP_CONST(AFE_IRQ_EN_SEL, 0x0) +#define AFE_IRQ_EN_SEL_I2SOUT FIELD_PREP_CONST(AFE_IRQ_EN_SEL, 0x1) +#define AFE_IRQ_MISS_FLG_CLR_SHIFT 3 +#define AFE_IRQ_CLR_SHIFT 2 +#define AFE_IRQ_ON_SHIFT 0 +#define AFE_IRQ_CNT 0xe8 +#define AFE_IRQ_CNT_SHIFT 0 +#define AFE_IRQ_CNT_MASK GENMASK(31, 0) + +#define AFE_IRQ_STS 0xf8 +#define AFE_IRQ_STS_PLAY BIT(1) +#define AFE_IRQ_STS_RECORD BIT(0) + +#define AFE_MAX_REGISTER AFE_IRQ_STS + +#define AFE_IRQ1_CON0 0x0 +#define AFE_IRQ1_CNT 0x4 + +#define AFE_IRQ1_MAX_REGISTER AFE_IRQ1_CNT + +#endif From 8fcf0497e6da049a3ff19e6baa958bbb146da996 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Tue, 8 Sep 2026 11:04:44 +0200 Subject: [PATCH 271/924] ASoC: dt-bindings: Add Airoha AN7581 AFE with WM8960 Codec schema Add DT schema for Airoha AN7581 Sound Card with the specific WM8960 i2c Codec. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260908090448.3332-3-ansuelsmth@gmail.com Signed-off-by: Mark Brown --- .../bindings/sound/airoha,an7581-wm8960.yaml | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml diff --git a/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml b/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml new file mode 100644 index 000000000000..8886fbb6b43c --- /dev/null +++ b/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/airoha,an7581-wm8960.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Airoha AN7581 sound card with WM8960 codec + +maintainers: + - Christian Marangi + +allOf: + - $ref: sound-card-common.yaml# + +properties: + compatible: + const: airoha,an7581-wm8960-sound + + platform: + type: object + + additionalProperties: false + + properties: + sound-dai: + items: + - description: The phandle of AN7581 platform. + + required: + - sound-dai + + codec: + type: object + + additionalProperties: false + + properties: + sound-dai: + items: + - description: The phandle of WM8960 i2c codec. + + required: + - sound-dai + +required: + - compatible + - audio-routing + - platform + - codec + +unevaluatedProperties: false + +examples: + - | + sound { + compatible = "airoha,an7581-wm8960-sound"; + model = "an7581-wm8960"; + audio-routing = + "Headphone", "HP_L", + "Headphone", "HP_R", + "LINPUT1", "AMIC", + "RINPUT1", "AMIC"; + + platform { + sound-dai = <&afe>; + }; + + codec { + sound-dai = <&wm8960>; + }; + }; From 272d6e51bc1f7db9ee58bb4029c95118a587c1c9 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Tue, 8 Sep 2026 11:04:47 +0200 Subject: [PATCH 272/924] ASoC: airoha: Add machine driver for Airoha AN7581 Add support for the machine driver for Airoha AN7581 paired with the WM8960 i2c codec. This driver creates the sound card for the AFE driver. Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260908090448.3332-6-ansuelsmth@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/Kconfig | 10 ++ sound/soc/mediatek/an7581/Makefile | 1 + sound/soc/mediatek/an7581/an7581-wm8960.c | 161 ++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 sound/soc/mediatek/an7581/an7581-wm8960.c diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index eb6f2a443b23..ff2869641a35 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -16,6 +16,16 @@ config SND_SOC_AN7581 Select Y if you have such device. If unsure select "N". +config SND_SOC_AN7581_WM8960 + tristate "ASoc Audio driver for Airoha AN7581 with WM8960 codec" + depends on SND_SOC_AN7581 && I2C + select SND_SOC_WM8960 + help + This adds support for ASoC machine driver for Airoha AN7581 + boards with the WM8960 codecs. + Select Y if you have such device. + If unsure select "N". + endmenu menu "Mediatek" diff --git a/sound/soc/mediatek/an7581/Makefile b/sound/soc/mediatek/an7581/Makefile index d6de5ee1668e..f48cd0269bea 100644 --- a/sound/soc/mediatek/an7581/Makefile +++ b/sound/soc/mediatek/an7581/Makefile @@ -6,3 +6,4 @@ snd-soc-an7581-afe-y := \ an7581-dai-etdm.o obj-$(CONFIG_SND_SOC_AN7581) += snd-soc-an7581-afe.o +obj-$(CONFIG_SND_SOC_AN7581_WM8960) += an7581-wm8960.o diff --git a/sound/soc/mediatek/an7581/an7581-wm8960.c b/sound/soc/mediatek/an7581/an7581-wm8960.c new file mode 100644 index 000000000000..03ea5732563c --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-wm8960.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha ALSA SoC I2S platform driver for AN7581 + * + */ + +#include +#include + +#include "an7581-afe-common.h" + +SND_SOC_DAILINK_DEFS(playback, + DAILINK_COMP_ARRAY(COMP_CPU("DL1")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY(), COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(capture, + DAILINK_COMP_ARRAY(COMP_CPU("UL1")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY(), COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(codec, + DAILINK_COMP_ARRAY(COMP_CPU("ETDM")), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY(), COMP_EMPTY())); + +static struct snd_soc_dai_link an7581_wm8960_dai_links[] = { + /* FE */ + { + .name = "wm8960-playback", + .stream_name = "wm8960-playback", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .playback_only = 1, + SND_SOC_DAILINK_REG(playback), + }, + { + .name = "wm8960-capture", + .stream_name = "wm8960-capture", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .capture_only = 1, + SND_SOC_DAILINK_REG(capture), + }, + /* BE */ + { + .name = "wm8960-codec", + .no_pcm = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP | + SND_SOC_DAIFMT_GATED, + SND_SOC_DAILINK_REG(codec), + }, +}; + +static struct snd_soc_card an7581_wm8960_card = { + .name = "an7581-wm8960", + .owner = THIS_MODULE, + .dai_link = an7581_wm8960_dai_links, + .num_links = ARRAY_SIZE(an7581_wm8960_dai_links), +}; + +static int an7581_wm8960_machine_probe(struct platform_device *pdev) +{ + struct device_node *platform_dai_node, *codec_dai_node; + struct snd_soc_card *card = &an7581_wm8960_card; + struct device_node *of_platform, *codec; + struct snd_soc_dai_link *dai_link; + int i, d, ret; + + card->dev = &pdev->dev; + + of_platform = of_get_child_by_name(pdev->dev.of_node, "platform"); + + if (of_platform) { + platform_dai_node = of_parse_phandle(of_platform, "sound-dai", 0); + of_node_put(of_platform); + + if (!platform_dai_node) { + dev_err(&pdev->dev, "Failed to parse platform/sound-dai property\n"); + return -EINVAL; + } + } else { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for_each_card_prelinks(card, i, dai_link) { + struct snd_soc_dai_link_component *platform; + + dai_link->num_platforms = 2; + for_each_link_platforms(dai_link, d, platform) { + if (platform->name) + continue; + platform->of_node = platform_dai_node; + } + } + + codec = of_get_child_by_name(pdev->dev.of_node, "codec"); + + if (codec) { + codec_dai_node = of_parse_phandle(codec, "sound-dai", 0); + of_node_put(codec); + + if (!codec_dai_node) { + of_node_put(platform_dai_node); + dev_err(&pdev->dev, "Failed to parse codec/sound-dai property\n"); + return -EINVAL; + } + } else { + of_node_put(platform_dai_node); + dev_err(&pdev->dev, "Property 'codec' missing or invalid\n"); + return -EINVAL; + } + + for_each_card_prelinks(card, i, dai_link) { + if (dai_link->codecs->name) + continue; + dai_link->codecs->of_node = codec_dai_node; + } + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "Failed to parse audio-routing: %d\n", ret); + goto err_of_node_put; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err_probe(&pdev->dev, ret, "%s snd_soc_register_card fail\n", __func__); + goto err_of_node_put; + } + + return 0; + +err_of_node_put: + of_node_put(platform_dai_node); + of_node_put(codec_dai_node); + return ret; +} + +static const struct of_device_id an7581_wm8960_machine_dt_match[] = { + { .compatible = "airoha,an7581-wm8960-sound" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, an7581_wm8960_machine_dt_match); + +static struct platform_driver an7581_wm8960_driver = { + .driver = { + .name = "an7581-wm8960", + .of_match_table = an7581_wm8960_machine_dt_match, + }, + .probe = an7581_wm8960_machine_probe, +}; +module_platform_driver(an7581_wm8960_driver); + +MODULE_DESCRIPTION("Airoha SoC I2S platform driver for ALSA AN7581"); +MODULE_LICENSE("GPL"); From b49799e403137b12fb9e57671901fe9d56f73d99 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Tue, 8 Sep 2026 11:04:45 +0200 Subject: [PATCH 273/924] ASoC: mediatek: common: permit to provide dedicated regmap for irq Some SoC might require dedicated regmap to configure some specific IRQ register. Add an extra entry in the base_afe_irq struct and use the specific regmap if defined. If not defined then the global AFE regmap is used instead. Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260908090448.3332-4-ansuelsmth@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/common/mtk-afe-fe-dai.c | 14 +++++++++----- sound/soc/mediatek/common/mtk-base-afe.h | 2 ++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c index 2a20fa5dba49..948f606cbaf2 100644 --- a/sound/soc/mediatek/common/mtk-afe-fe-dai.c +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -204,11 +204,15 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, struct mtk_base_afe_irq *irqs = &afe->irqs[memif->irq_usage]; const struct mtk_base_irq_data *irq_data = irqs->irq_data; unsigned int counter = runtime->period_size; + struct regmap *regmap = afe->regmap; int fs; int ret; dev_dbg(afe->dev, "%s %s cmd=%d\n", __func__, memif->data->name, cmd); + if (irqs->regmap) + regmap = irqs->regmap; + switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: @@ -220,7 +224,7 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, } /* set irq counter */ - mtk_regmap_update_bits(afe->regmap, irq_data->irq_cnt_reg, + mtk_regmap_update_bits(regmap, irq_data->irq_cnt_reg, irq_data->irq_cnt_maskbit, counter, irq_data->irq_cnt_shift); @@ -230,12 +234,12 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, if (fs < 0) return -EINVAL; - mtk_regmap_update_bits(afe->regmap, irq_data->irq_fs_reg, + mtk_regmap_update_bits(regmap, irq_data->irq_fs_reg, irq_data->irq_fs_maskbit, fs, irq_data->irq_fs_shift); /* enable interrupt */ - mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, + mtk_regmap_update_bits(regmap, irq_data->irq_en_reg, 1, 1, irq_data->irq_en_shift); return 0; @@ -248,10 +252,10 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, } /* disable interrupt */ - mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, + mtk_regmap_update_bits(regmap, irq_data->irq_en_reg, 1, 0, irq_data->irq_en_shift); /* and clear pending IRQ */ - mtk_regmap_write(afe->regmap, irq_data->irq_clr_reg, + mtk_regmap_write(regmap, irq_data->irq_clr_reg, 1 << irq_data->irq_clr_shift); return ret; default: diff --git a/sound/soc/mediatek/common/mtk-base-afe.h b/sound/soc/mediatek/common/mtk-base-afe.h index a406f2e3e7a8..b5300c9ed415 100644 --- a/sound/soc/mediatek/common/mtk-base-afe.h +++ b/sound/soc/mediatek/common/mtk-base-afe.h @@ -153,6 +153,8 @@ struct mtk_base_afe_memif { struct mtk_base_afe_irq { const struct mtk_base_irq_data *irq_data; int irq_occupyed; + + struct regmap *regmap; }; struct mtk_base_afe_dai { From 4454102d6565afedfcde64b222595f067a5b7e93 Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Thu, 17 Sep 2026 11:33:59 +0100 Subject: [PATCH 274/924] ASoC: SDCA: Improve scanning the SWFT during FDL Walking through SWFT is now improved to verify if it is not reading past the end of the table. Introduce file length checks that are done for SWFs from disk to files from SWFT too. Fixes: 71f7990a34cd ("ASoC: SDCA: Add FDL library for XU entities") Signed-off-by: Maciej Strozek Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260917103400.2032913-1-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_fdl.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c index 150e36ed24bc..e08a553e0eab 100644 --- a/sound/soc/sdca/sdca_fdl.c +++ b/sound/soc/sdca/sdca_fdl.c @@ -199,7 +199,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, struct sdca_fdl_file *fdl_file; char *disk_filename; int ret; - int i; if (!set) { dev_err(dev, "request to load SWF with no set\n"); @@ -209,9 +208,18 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, fdl_file = &set->files[file_index]; if (fdl_data->swft) { - tmp = fdl_data->swft->files; - for (i = 0; i < fdl_data->swft->header.length; i += tmp->file_length, - tmp = ACPI_ADD_PTR(struct acpi_sw_file, tmp, tmp->file_length)) { + struct acpi_sw_file *table_end, *next; + + table_end = ACPI_ADD_PTR(struct acpi_sw_file, fdl_data->swft, + fdl_data->swft->header.length); + for (tmp = fdl_data->swft->files; tmp + 1 <= table_end; tmp = next) { + next = ACPI_ADD_PTR(struct acpi_sw_file, tmp, tmp->file_length); + + if (tmp->file_length < sizeof(*tmp) || next > table_end) { + dev_err(dev, "bad file length in SWFT: %u\n", tmp->file_length); + break; + } + if (tmp->vendor_id == fdl_file->vendor_id && tmp->file_id == fdl_file->file_id) { dev_dbg(dev, "located SWF in ACPI: %x-%x-%x\n", From dfd8940277c9a07193c2d168760e19d054f5047c Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Thu, 17 Sep 2026 11:34:00 +0100 Subject: [PATCH 275/924] ASoC: SDCA: Check return value on a missing FDL_Set_Index Range In case of a missing FDL_Set_Index control the return value can be NULL, ensure the functions returns before passing it on to range search. Fixes: 71f7990a34cd ("ASoC: SDCA: Add FDL library for XU entities") Signed-off-by: Maciej Strozek Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260917103400.2032913-2-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_fdl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c index e08a553e0eab..7fc809707944 100644 --- a/sound/soc/sdca/sdca_fdl.c +++ b/sound/soc/sdca/sdca_fdl.c @@ -301,6 +301,8 @@ static struct sdca_fdl_set *fdl_get_set(struct sdca_interrupt *interrupt) range = sdca_selector_find_range(dev, xu, SDCA_CTL_XU_FDL_SET_INDEX, SDCA_FDL_SET_INDEX_NCOLS, 0); + if (!range) + return NULL; val = sdca_range_search(range, SDCA_FDL_SET_INDEX_SET_NUMBER, val, SDCA_FDL_SET_INDEX_FILE_SET_ID); From a389c4dd55d5568571b57dfd7200fd0fe24a55d4 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:05 +0200 Subject: [PATCH 276/924] ASoC: stm32: spdifrx: Drop mention of the deprecated and unused call The error message was not updated while driver moved from the deprecated dma_request_slave_channel() to the dma_request_chan(). Update the message by replacing it with the correct function. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260918113727.1898998-2-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/stm/stm32_spdifrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c index e0fef47a227e..0ece54740773 100644 --- a/sound/soc/stm/stm32_spdifrx.c +++ b/sound/soc/stm/stm32_spdifrx.c @@ -400,7 +400,7 @@ static int stm32_spdifrx_dma_ctrl_register(struct device *dev, spdifrx->ctrl_chan = dma_request_chan(dev, "rx-ctrl"); if (IS_ERR(spdifrx->ctrl_chan)) return dev_err_probe(dev, PTR_ERR(spdifrx->ctrl_chan), - "dma_request_slave_channel error\n"); + "dma_request_chan error\n"); spdifrx->dmab = devm_kzalloc(dev, sizeof(struct snd_dma_buffer), GFP_KERNEL); From 08d77e31df21fd16a2430277c09a84296972c86c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:06 +0200 Subject: [PATCH 277/924] ASoC: sprd: Replace dma_request_slave_channel() by dma_request_chan() Replace dma_request_slave_channel() by dma_request_chan() as suggested since the former is deprecated. With this, propagate all possible errors to the caller. Reviewed-by: Baolin Wang Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260918113727.1898998-3-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sprd/sprd-pcm-compress.c | 9 +++++---- sound/soc/sprd/sprd-pcm-dma.c | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/sound/soc/sprd/sprd-pcm-compress.c b/sound/soc/sprd/sprd-pcm-compress.c index e5249924b54d..f394121d417a 100644 --- a/sound/soc/sprd/sprd-pcm-compress.c +++ b/sound/soc/sprd/sprd-pcm-compress.c @@ -145,6 +145,7 @@ static int sprd_platform_compr_dma_config(struct snd_soc_component *component, enum dma_slave_buswidth bus_width; int period, period_cnt, sg_num = 2; dma_addr_t src_addr, dst_addr; + struct dma_chan *chan; unsigned long flags; int ret, j; @@ -153,12 +154,12 @@ static int sprd_platform_compr_dma_config(struct snd_soc_component *component, return -EINVAL; } - dma->chan = dma_request_slave_channel(dev, - dma_params->chan_name[channel]); - if (!dma->chan) { + chan = dma_request_chan(dev, dma_params->chan_name[channel]); + if (IS_ERR(chan)) { dev_err(dev, "failed to request dma channel\n"); - return -ENODEV; + return PTR_ERR(chan); } + dma->chan = chan; sgt = sg = kzalloc_objs(*sg, sg_num); if (!sg) { diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c index f509a4601de2..62e84496942a 100644 --- a/sound/soc/sprd/sprd-pcm-dma.c +++ b/sound/soc/sprd/sprd-pcm-dma.c @@ -170,15 +170,16 @@ static int sprd_pcm_request_dma_channel(struct snd_soc_component *component, for (i = 0; i < channels; i++) { struct sprd_pcm_dma_data *data = &dma_private->data[i]; + struct dma_chan *chan; - data->chan = dma_request_slave_channel(dev, - dma_params->chan_name[i]); - if (!data->chan) { + chan = dma_request_chan(dev, dma_params->chan_name[i]); + if (IS_ERR(chan)) { dev_err(dev, "failed to request dma channel:%s\n", dma_params->chan_name[i]); sprd_pcm_release_dma_channel(substream); - return -ENODEV; + return PTR_ERR(chan); } + data->chan = chan; } return 0; From 77593bbdb8aa546ee1b99c2a7c806a00784c68cf Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:07 +0200 Subject: [PATCH 278/924] ASoC: soc-generic-dmaengine-pcm: Replace dma_request_slave_channel() by dma_request_chan() Replace dma_request_slave_channel() by dma_request_chan() as suggested since the former is deprecated. Reviewed-by: Cezary Rojewski Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260918113727.1898998-4-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/soc-generic-dmaengine-pcm.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index 65b7ec52a054..6d1dbee8f6c1 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -242,9 +242,11 @@ static int dmaengine_pcm_new(struct snd_soc_component *component, if (!substream) continue; - if (!pcm->chan[i] && config->chan_names[i]) - pcm->chan[i] = dma_request_slave_channel(dev, - config->chan_names[i]); + if (!pcm->chan[i] && config->chan_names[i]) { + pcm->chan[i] = dma_request_chan(dev, config->chan_names[i]); + if (IS_ERR(pcm->chan[i])) + pcm->chan[i] = NULL; + } if (!pcm->chan[i] && (pcm->flags & SND_DMAENGINE_PCM_FLAG_COMPAT)) { pcm->chan[i] = dmaengine_pcm_compat_request_channel( From ca11bc272ab4075afbcbf7d56116244243e1f2e1 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:08 +0200 Subject: [PATCH 279/924] ASoC: pxa: Replace dma_request_slave_channel() by dma_request_chan() Replace dma_request_slave_channel() by dma_request_chan() as suggested since the former is deprecated. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260918113727.1898998-5-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/pxa/pxa2xx-pcm-lib.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sound/soc/pxa/pxa2xx-pcm-lib.c b/sound/soc/pxa/pxa2xx-pcm-lib.c index 88a9d3226302..4b5b07f0c444 100644 --- a/sound/soc/pxa/pxa2xx-pcm-lib.c +++ b/sound/soc/pxa/pxa2xx-pcm-lib.c @@ -79,6 +79,7 @@ static int pxa2xx_pcm_open(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dmaengine_dai_dma_data *dma_params; + struct dma_chan *chan; int ret; runtime->hw = pxa2xx_pcm_hardware; @@ -107,9 +108,11 @@ static int pxa2xx_pcm_open(struct snd_pcm_substream *substream) if (ret < 0) return ret; - return snd_dmaengine_pcm_open( - substream, dma_request_slave_channel(snd_soc_rtd_to_cpu(rtd, 0)->dev, - dma_params->chan_name)); + chan = dma_request_chan(snd_soc_rtd_to_cpu(rtd, 0)->dev, dma_params->chan_name); + if (IS_ERR(chan)) + return -ENXIO; + + return snd_dmaengine_pcm_open(substream, chan); } static int pxa2xx_pcm_close(struct snd_pcm_substream *substream) From b045248399d0cd8b6188ce1a5b9d722709ad3ddc Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:09 +0200 Subject: [PATCH 280/924] ASoC: fsl_asrc: Replace dma_request_slave_channel() by dma_request_chan() Replace dma_request_slave_channel() by dma_request_chan() as suggested since the former is deprecated. With this, propagate all possible errors to the caller. Signed-off-by: Andy Shevchenko Reviewed-by: Frank Li Link: https://patch.msgid.link/20260918113727.1898998-6-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc.c | 2 +- sound/soc/fsl/fsl_asrc_dma.c | 19 +++++++++++-------- sound/soc/fsl/fsl_asrc_m2m.c | 15 +++++++++------ sound/soc/fsl/fsl_easrc.c | 2 +- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index b24737add001..ae682bf450f6 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -638,7 +638,7 @@ static struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, sprintf(name, "%cx%c", dir == IN ? 'r' : 't', index + 'a'); - return dma_request_slave_channel(&asrc->pdev->dev, name); + return dma_request_chan(&asrc->pdev->dev, name); } static int fsl_asrc_dai_startup(struct snd_pcm_substream *substream, diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index 2f662bdf14d0..86489f70e262 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -136,8 +136,8 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, struct snd_dmaengine_dai_dma_data *dma_params_be = NULL; struct snd_pcm_runtime *runtime = substream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; - struct dma_chan *tmp_chan = NULL, *be_chan = NULL; struct snd_soc_component *component_be = NULL; + struct dma_chan *tmp_chan, *be_chan = NULL; struct fsl_asrc *asrc = pair->asrc; struct dma_slave_config config_fe = {}, config_be = {}; struct sdma_peripheral_config audio_config; @@ -190,11 +190,12 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, dma_params_fe->addr = asrc->paddr + asrc->get_fifo_addr(!dir, index); dma_params_fe->maxburst = dma_params_be->maxburst; - pair->dma_chan[!dir] = asrc->get_dma_channel(pair, !dir); - if (!pair->dma_chan[!dir]) { + tmp_chan = asrc->get_dma_channel(pair, !dir); + if (IS_ERR(tmp_chan)) { dev_err(dev, "failed to request DMA channel\n"); - return -EINVAL; + return PTR_ERR(tmp_chan); } + pair->dma_chan[!dir] = tmp_chan; ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, &config_fe); if (ret) { @@ -223,6 +224,8 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, be_chan = pcm->chan[substream->stream]; tmp_chan = be_chan; + } else { + tmp_chan = NULL; } if (!tmp_chan) { tmp_chan = dma_request_chan(dev_be, tx ? "tx" : "rx"); @@ -404,9 +407,9 @@ static int fsl_asrc_dma_startup(struct snd_soc_component *component, /* Request a dummy dma channel, which will be released later. */ tmp_chan = asrc->get_dma_channel(pair, dir); - if (!tmp_chan) { + ret = PTR_ERR_OR_ZERO(tmp_chan); + if (ret) { dev_err(dev, "failed to get dma channel\n"); - ret = -EINVAL; goto dma_chan_err; } @@ -497,9 +500,9 @@ static int fsl_asrc_dma_pcm_new(struct snd_soc_component *component, /* Request a dma channel, which will be released later. */ chan = asrc->get_dma_channel(pair, IN); - if (!chan) { + ret = PTR_ERR_OR_ZERO(chan); + if (ret) { dev_err(dev, "failed to get dma channel\n"); - ret = -EINVAL; goto dma_chan_err; } diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index 4bc40f328f58..e7b91196d35b 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -466,6 +466,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, struct snd_compr_runtime *runtime = stream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; struct device *dev = &asrc->pdev->dev; + struct dma_chan *tmp_chan; int ret; exp_info_in.ops = &fsl_asrc_m2m_dma_buf_ops; @@ -502,19 +503,21 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, } /* Request dma channels */ - pair->dma_chan[IN] = asrc->get_dma_channel(pair, IN); - if (!pair->dma_chan[IN]) { + tmp_chan = asrc->get_dma_channel(pair, IN); + ret = PTR_ERR_OR_ZERO(tmp_chan); + if (ret) { dev_err(dev, "[ctx%d] failed to get input DMA channel\n", pair->index); - ret = -EBUSY; goto err_dma_channel_in; } + pair->dma_chan[IN] = tmp_chan; - pair->dma_chan[OUT] = asrc->get_dma_channel(pair, OUT); - if (!pair->dma_chan[OUT]) { + tmp_chan = asrc->get_dma_channel(pair, OUT); + ret = PTR_ERR_OR_ZERO(tmp_chan); + if (ret) { dev_err(dev, "[ctx%d] failed to get output DMA channel\n", pair->index); - ret = -EBUSY; goto err_dma_channel_out; } + pair->dma_chan[OUT] = tmp_chan; return 0; diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index aced16d1228a..d30cc3e90215 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -1433,7 +1433,7 @@ static struct dma_chan *fsl_easrc_get_dma_channel(struct fsl_asrc_pair *ctx, /* Example of dma name: ctx0_rx */ sprintf(name, "ctx%c_%cx", index + '0', dir == IN ? 'r' : 't'); - return dma_request_slave_channel(&easrc->pdev->dev, name); + return dma_request_chan(&easrc->pdev->dev, name); }; static const unsigned int easrc_rates[] = { From 4f846b8b69fd0a114ebba076493ddb62efc9ce51 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:10 +0200 Subject: [PATCH 281/924] gpib: fmh_gpib: Replace dma_request_slave_channel() by dma_request_chan() Replace dma_request_slave_channel() by dma_request_chan() as suggested since the former is deprecated. With this, propagate all possible errors to the caller. Reviewed-by: Dave Penkler Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260918113727.1898998-7-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/gpib/fmh_gpib/fmh_gpib.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpib/fmh_gpib/fmh_gpib.c b/drivers/gpib/fmh_gpib/fmh_gpib.c index 5e10e9353fed..4ab9b0a317fd 100644 --- a/drivers/gpib/fmh_gpib/fmh_gpib.c +++ b/drivers/gpib/fmh_gpib/fmh_gpib.c @@ -1458,11 +1458,15 @@ static int fmh_gpib_attach_impl(struct gpib_board *board, const struct gpib_boar e_priv->irq = irq; if (acquire_dma) { - e_priv->dma_channel = dma_request_slave_channel(board->dev, "rxtx"); - if (!e_priv->dma_channel) { + struct dma_chan *tmp_chan; + + tmp_chan = dma_request_chan(board->dev, "rxtx"); + retval = PTR_ERR_OR_ZERO(tmp_chan); + if (retval) { dev_err(board->dev, "failed to acquire dma channel \"rxtx\".\n"); - return -EIO; + return retval; } + e_priv->dma_channel = tmp_chan; } /* * in the future we might want to know the half-fifo size From ae710b66c715933fb3e9aa53d4a024641921772e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:11 +0200 Subject: [PATCH 282/924] dmaengine: Remove deprecated dma_request_slave_channel() No more users. Reviewed-by: Frank Li Signed-off-by: Andy Shevchenko Acked-by: Vinod Koul Link: https://patch.msgid.link/20260918113727.1898998-8-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- include/linux/dmaengine.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h index fe33a20abc61..f322e501ff62 100644 --- a/include/linux/dmaengine.h +++ b/include/linux/dmaengine.h @@ -1760,15 +1760,6 @@ void dma_run_dependencies(struct dma_async_tx_descriptor *tx); #define dma_request_channel(mask, x, y) \ __dma_request_channel(&(mask), x, y, NULL) -/* Deprecated, please use dma_request_chan() directly */ -static inline struct dma_chan * __deprecated -dma_request_slave_channel(struct device *dev, const char *name) -{ - struct dma_chan *ch = dma_request_chan(dev, name); - - return IS_ERR(ch) ? NULL : ch; -} - static inline struct dma_chan *dma_request_slave_channel_compat(const dma_cap_mask_t mask, dma_filter_fn fn, void *fn_param, From f42fbd560cd834c7d10118743683def979c68739 Mon Sep 17 00:00:00 2001 From: Baojun Xu Date: Mon, 21 Sep 2026 19:19:30 +0800 Subject: [PATCH 283/924] ASoC: tas2783-sdw: add firmware download status check Currently, firmware download during system resume from suspend introduces significant wake-up latency. However, since PRAM content in the AMP persists across resets and is not erased, this step can be optimized. By verifying that the firmware version read from registers matches the expected value, we can confirm that memory content was retained. Consequently, the PRAM download can be skipped, significantly reducing resume latency. Signed-off-by: Baojun Xu Link: https://patch.msgid.link/20260921111930.1040-1-baojun.xu@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2783-sdw.c | 70 ++++++++++++++++++++-------------- sound/soc/codecs/tas2783.h | 4 +- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index 85ab3fd83c7b..a57230e01f75 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -97,6 +97,7 @@ struct tas2783_prv { u8 rca_binaryname[64]; u8 dev_name[32]; bool hw_init; + unsigned int fw_version; /* wq for firmware download */ wait_queue_head_t fw_wait; bool fw_dl_task_done; @@ -315,8 +316,10 @@ static int tas2783_sdca_mbq_size(struct device *dev, u32 reg) case 0x300 ... 0x340: /* Data port 3. */ case 0x400 ... 0x440: /* Data port 4. */ case 0x500 ... 0x540: /* Data port 5. */ - case 0x800000 ... 0x803fff: /* Page 0 ~ 127. */ - case 0x807e80 ... 0x807eff: /* Page 253. */ + case TASDEV_REG_SDW(0, 0, 0) ... TASDEV_REG_SDW(0x00, 0x01, 0x80): + case TASDEV_REG_SDW(0, 0xfd, 0) ... TASDEV_REG_SDW(0, 0xfd, 0x80): + case PRAM_ADDR_START ... PRAM_ADDR_END: + case YRAM_ADDR_START ... YRAM_ADDR_END: case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_UDMPU23, TAS2783_SDCA_CTL_UDMPU_CLUSTER, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU21, TAS2783_SDCA_CTL_FU_MUTE, @@ -517,7 +520,7 @@ static const struct regmap_config tas_regmap = { .volatile_reg = tas2783_volatile_register, .reg_defaults = tas2783_reg_default, .num_reg_defaults = ARRAY_SIZE(tas2783_reg_default), - .max_register = 0x41008000 + TASDEV_REG_SDW(0xa1, 0x60, 0x7f), + .max_register = 0x41000000 + PRAM_ADDR_END, .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, @@ -745,6 +748,7 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) const u8 *buf = NULL; s32 img_sz, ret = 0, cur_file = 0; s32 offset = 0; + u32 val[4], fw_version; struct tas_fw_hdr *hdr __free(kfree) = kzalloc_obj(*hdr); struct tas_fw_file *file __free(kfree) = kzalloc_obj(*file); @@ -786,6 +790,11 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) } mutex_lock(&tas_dev->pde_lock); + ret = regmap_bulk_read(tas_dev->regmap, TAS2783_FW_VERSION, &val, 4); + fw_version = (val[0] << 24) | (val[1] << 16) | (val[2] << 8) | val[3]; + dev_dbg(tas_dev->dev, "Get Firmware version: %08x == %08x?, err=%d", + fw_version, tas_dev->fw_version, ret); + while (offset < (img_sz - FW_FL_HDR)) { offset += tas_fw_get_next_file(&buf[offset], file); dev_dbg(tas_dev->dev, @@ -794,6 +803,13 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) file->version, file->length, file->dest_addr, file->fw_data); + if (tas_dev->fw_version == fw_version && + file->dest_addr >= PRAM_ADDR_START && + (file->dest_addr + file->length) <= PRAM_ADDR_END) { + cur_file++; + dev_dbg(tas_dev->dev, "Ignore PRAM block"); + continue; + } ret = sdw_nwrite_no_pm(tas_dev->sdw_peripheral, file->dest_addr, file->length, @@ -801,17 +817,34 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) if (ret < 0) { dev_err(tas_dev->dev, "FW download failed: %d", ret); - break; + /* + * We do retry here for some special case of download + * failed after Power-On. + */ + ret = sdw_nwrite_no_pm(tas_dev->sdw_peripheral, + file->dest_addr, + file->length, + file->fw_data); + if (ret < 0) { + dev_err(tas_dev->dev, + "FW download failed again: %d", ret); + break; + } } cur_file++; } mutex_unlock(&tas_dev->pde_lock); + regcache_drop_region(tas_dev->regmap, 0, UINT_MAX); if (cur_file == 0) { dev_err(tas_dev->dev, "fw with no files"); ret = -EINVAL; } else { tas2783_update_calibdata(tas_dev); + ret = regmap_bulk_read(tas_dev->regmap, TAS2783_FW_VERSION, + &val, 4); + tas_dev->fw_version = (val[0] << 24) | (val[1] << 16) | + (val[2] << 8) | val[3]; } out: @@ -962,30 +995,6 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, snd_sdw_params_to_config(substream, params, &stream_config, &port_config); - /* - * The two mono amps each render one channel of the stereo stream: - * snd_sdw_params_to_config() hands every codec the full mask for - * playback, which leaves the pair in mirror mode and one channel - * unreproduced. Claim a single channel instead, keyed off the - * machine-assigned component prefix rather than the SoundWire - * address, which is board-specific: soc_sdw_ti_amp.c names the amps - * tas2783-1..4. - * - * Which side an amp then renders does not follow from the bit that - * is set - sdw_compute_slave_ports() advances the payload offset by - * the popcount of ch_mask and never looks at which bit it is - but - * from the amp's position in the codec order of the DAI link, which - * on these boards matches the prefix numbering. - */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && - params_channels(params) == 2 && component->name_prefix) { - const char *idx_str = strrchr(component->name_prefix, '-'); - unsigned long idx; - - if (idx_str && !kstrtoul(idx_str + 1, 10, &idx) && idx) - port_config.ch_mask = (idx & 1) ? BIT(0) : BIT(1); - } - /* port 1 for playback */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) port_config.num = 1; @@ -1074,7 +1083,7 @@ static const struct snd_soc_component_driver soc_codec_driver_tasdevice = { .num_dapm_widgets = ARRAY_SIZE(tas_dapm_widgets), .dapm_routes = tas_audio_map, .num_dapm_routes = ARRAY_SIZE(tas_audio_map), - .idle_bias_on = 1, + .idle_bias_on = 0, .endianness = 1, }; @@ -1234,6 +1243,8 @@ static s32 tas_io_init(struct device *dev, struct sdw_slave *slave) ret = regmap_multi_reg_write(tas_dev->regmap, tas2783_init_seq, ARRAY_SIZE(tas2783_init_seq)); + /* Re-active AMP after resume. */ + regmap_write(tas_dev->regmap, TASDEV_REG_SDW(0, 0, 2), 0); if (ret) dev_err(tas_dev->dev, "init writes failed, err=%d", ret); @@ -1413,6 +1424,7 @@ static s32 tas_sdw_probe(struct sdw_slave *peripheral, tas_dev->dev = dev; tas_dev->sdw_peripheral = peripheral; tas_dev->hw_init = false; + tas_dev->fw_version = 0; mutex_init(&tas_dev->calib_lock); mutex_init(&tas_dev->pde_lock); diff --git a/sound/soc/codecs/tas2783.h b/sound/soc/codecs/tas2783.h index d5996c73526c..2f034617e356 100644 --- a/sound/soc/codecs/tas2783.h +++ b/sound/soc/codecs/tas2783.h @@ -35,10 +35,12 @@ #define TAS2783_AMP_LEVEL_MASK GENMASK(5, 1) #define PRAM_ADDR_START TASDEV_REG_SDW(0x8c, 0x01, 0x8) -#define PRAM_ADDR_END TASDEV_REG_SDW(0x8c, 0xff, 0x7f) +#define PRAM_ADDR_END TASDEV_REG_SDW(0x8c, 0xff, 0x80) #define YRAM_ADDR_START TASDEV_REG_SDW(0x00, 0x02, 0x8) #define YRAM_ADDR_END TASDEV_REG_SDW(0x00, 0x37, 0x7f) +#define TAS2783_FW_VERSION TASDEV_REG_SDW(0x00, 0x20, 0x3c) + /* Calibration data */ #define TAS2783_CAL_R0 TASDEV_REG_SDW(0, 0x16, 0x4C) #define TAS2783_CAL_INVR0 TASDEV_REG_SDW(0, 0x16, 0x5C) From e5db53237fe63e2e3529660632249598308add32 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:17:44 +0200 Subject: [PATCH 284/924] ASoC: codecs: rt286: Revert delay value for jack setup Fat fingered the change when reorganizing the jack-detect initialization. Equivalent changes for rt274 and rt298: Commit a43b4394bb35 ("ASoC: codecs: rt274: Always init jack_detect_work") Commit 1eb73102da28 ("ASoC: codecs: rt298: Reorganize jack detect handling") carry no update to the delay value. >From user perspective, no difference has been observed between 1250 and 50 values on Intel's SkyLake, KabyLake and AmberLake configurations. Fixes: 3082afe097cc ("ASoC: codecs: rt286: Reorganize jack detect handling") Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260918111745.3589393-1-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt286.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index 51b407b78e6d..c60e81ba87f3 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -953,7 +953,7 @@ static int rt286_probe(struct snd_soc_component *component) if (rt286->i2c->irq) schedule_delayed_work(&rt286->jack_detect_work, - msecs_to_jiffies(50)); + msecs_to_jiffies(1250)); return 0; } From 3997b7bdedb5ecbfbb1ede75320baa21656f57c7 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:17:45 +0200 Subject: [PATCH 285/924] ASoC: codecs: rt274: Fix format setting for 44100 rate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per specification, Sample Base Rate (bit 14) shall be set to 1 for 44.1 kHz. Fixes: c7e79b2b2d2d ("ASoC: rt274: add rt274 codec driver") Signed-off-by: Amadeusz Sławiński Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260918111745.3589393-2-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt274.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt274.c b/sound/soc/codecs/rt274.c index 62fc071d646a..5c67b1d210b2 100644 --- a/sound/soc/codecs/rt274.c +++ b/sound/soc/codecs/rt274.c @@ -619,9 +619,11 @@ static int rt274_hw_params(struct snd_pcm_substream *substream, unsigned int val = 0; int d_len_code = 0, c_len_code = 0; + /* bit 14 Sample Rate Base, 0: 48kHz 1: 44.1kHz */ switch (params_rate(params)) { - /* bit 14 0:48K 1:44.1K */ case 44100: + val |= BIT(14); + break; case 48000: break; default: From 7325b35a12d7bdab94b9712c07f226c9354c65b4 Mon Sep 17 00:00:00 2001 From: Oder Chiou Date: Thu, 17 Sep 2026 18:42:44 +0800 Subject: [PATCH 286/924] ASoC: rt721: Add support for the RT721_U and RT718 codec variants The RT721_U and RT718 variants share most of the register layout and driver behavior with RT721. Extend the existing RT721 driver to recognize these variants and apply variant-specific settings where required. Signed-off-by: Oder Chiou Link: https://patch.msgid.link/20260917104244.2512641-1-oder_chiou@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca-sdw.c | 3 + sound/soc/codecs/rt721-sdca.c | 199 +++++++++++++++++++++++++--- sound/soc/codecs/rt721-sdca.h | 12 ++ sound/soc/sdw_utils/soc_sdw_utils.c | 46 +++++++ 4 files changed, 242 insertions(+), 18 deletions(-) diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c index 0ebb5688f848..a8c7b42898db 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.c +++ b/sound/soc/codecs/rt721-sdca-sdw.c @@ -91,6 +91,7 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re case 0x5810037: case 0x5810038: case 0x5810039: + case 0x5810100: case 0x5b10018: case 0x5b10019: case 0x5f00045: @@ -167,6 +168,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x5810037: case 0x5810038: case 0x5810039: + case 0x5810100: case 0x5b10018: case 0x5b10019: case 0x6100006: @@ -448,6 +450,7 @@ static void rt721_sdca_sdw_remove(struct sdw_slave *slave) static const struct sdw_device_id rt721_sdca_id[] = { SDW_SLAVE_ENTRY_EXT(0x025d, 0x721, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x025d, 0x718, 0x3, 0x1, 0), {}, }; MODULE_DEVICE_TABLE(sdw, rt721_sdca_id); diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index dbf61fa382a2..ef003c93f533 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -169,8 +169,12 @@ static void rt721_sdca_dmic_preset(struct rt721_sdca_priv *rt721) RT721_ENT_FLOAT_CTL8, 0x1e00); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_ENT_FLOAT_CTL7, 0x1515); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_CH_FLOAT_CTL3, 0x0304); + if (rt721->wf_id == RT721_S) + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL3, 0x0304); + else + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL5, 0x0304); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_CH_FLOAT_CTL4, 0x0304); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, @@ -183,7 +187,12 @@ static void rt721_sdca_dmic_preset(struct rt721_sdca_priv *rt721) RT721_RC_CALIB_CTRL0, 0x0b00); rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, RT721_RC_CALIB_CTRL0, 0x0b40); - regmap_write(rt721->regmap, 0x2f5c, 0x25); + if (rt721->wf_id == RT721_S) { + regmap_write(rt721->regmap, 0x2f5c, 0x25); + } else { + regmap_write(rt721->regmap, 0x2f5c, 0x01); + regmap_write(rt721->regmap, 0x2f52, 0x00); + } /* clear flag */ regmap_write(rt721->regmap, SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, @@ -208,9 +217,11 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) RT721_VREF1_HV_CTRL1, 0xe000); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, RT721_MISC_POWER_CTL31, 0x8007); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6420); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6421); - regmap_write(rt721->mbq_regmap, 0x5810000, 0xe421); + if (rt721->wf_id == RT721_S) { + regmap_write(rt721->mbq_regmap, 0x5810000, 0x6420); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x6421); + regmap_write(rt721->mbq_regmap, 0x5810000, 0xe421); + } rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_CH_FLOAT_CTL6, 0x5561); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_REG, @@ -231,6 +242,8 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); regmap_write(rt721->regmap, 0x2f5d, 0x1); + if (rt721->wf_id == RT721_U) + regmap_write(rt721->regmap, 0x2f54, 0x00); /* clear flag */ regmap_write(rt721->regmap, SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), @@ -245,7 +258,7 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) regmap_read(rt721->regmap, SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), - &jack_func_status); + &jack_func_status); dev_dbg(dev, "%s jack func_status=0x%x\n", __func__, jack_func_status); if ((jack_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt721->first_hw_init)) { @@ -330,6 +343,113 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) } } +static void rt721u_sdca_jack_preset(struct rt721_sdca_priv *rt721) +{ + unsigned int jack_func_status, calibration_status, i; + int ret; + struct device *dev = &rt721->slave->dev; + + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + &jack_func_status); + dev_dbg(dev, "%s jack func_status=0x%x\n", __func__, jack_func_status); + + if ((jack_func_status & BIT(5)) || (!rt721->first_hw_init)) { + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF1_HV_CTRL1, 0xe000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8007); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + 0x10, 0xffb7); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + 0x20, 0xffb7); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + 0x30, 0xffb7); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6048); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_GE_REL_CTRL1, 0x8011); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL3, 0xcf00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL4, 0x000f); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL1, 0x1100); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL5, 0x0c12); + rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_CTRL2, 0xc004); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b40); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x333b); + regmap_write(rt721->mbq_regmap, 0x5810135, 0x8036); + regmap_write(rt721->mbq_regmap, 0x5810130, 0xee03); + regmap_write(rt721->mbq_regmap, 0x5810133, 0x6000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL1, 0x4140); + regmap_write(rt721->mbq_regmap, 0x5810100, 0x0006); + regmap_write(rt721->mbq_regmap, 0x5810100, 0x8006); + for (i = 0; i < 5; i++) { + ret = regmap_read(rt721->mbq_regmap, 0x5810100, + &calibration_status); + if (ret < 0) + dev_dbg(dev, "calibration failed!, ret=%d\n", + ret); + if (!(calibration_status & 0x8000)) + break; + } + if (rt721->hw_vid == RT721_U_VB) + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x2205); + else + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x2e05); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3b3b); + regmap_write(rt721->mbq_regmap, 0x310400, 0x3043); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3f33); + regmap_write(rt721->mbq_regmap, 0x310401, 0x3000); + regmap_write(rt721->mbq_regmap, 0x310402, 0x1b00); + regmap_write(rt721->mbq_regmap, 0x310300, 0x000f); + regmap_write(rt721->mbq_regmap, 0x310301, 0x3000); + regmap_write(rt721->mbq_regmap, 0x310302, 0x1b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON17, 0x0008); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL2, 0x1234); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL3, 0x3512); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL1, 0x4040); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL4, 0x1201); + regmap_write(rt721->regmap, 0x2f58, 0x01); + regmap_write(rt721->regmap, 0x2f59, 0x01); + regmap_write(rt721->regmap, 0x2f51, 0x00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6040); + regmap_write(rt721->mbq_regmap, 0x910001, 0x3256); + regmap_write(rt721->mbq_regmap, 0x910002, 0x0000); + regmap_write(rt721->mbq_regmap, 0x910003, 0x0000); + regmap_write(rt721->mbq_regmap, 0x900004, 0x7778); + regmap_write(rt721->mbq_regmap, 0x910202, 0x02e4); + regmap_write(rt721->mbq_regmap, 0x800002, 0xcc04); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL4, 0xa105); + rt_sdca_index_write(rt721->mbq_regmap, 0x0b, 0x01, 0x0000); + /* clear flag */ + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0), FUNCTION_NEEDS_INITIALIZATION); + } +} + static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) { guard(mutex)(&rt721->calibrate_mutex); @@ -733,19 +853,47 @@ static int rt721_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol, return changed; } -static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(out_vol_tlv_s, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(out_vol_tlv_u, -9225, 75, 0); static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -1725, 75, 0); static const DECLARE_TLV_DB_SCALE(boost_vol_tlv, 0, 1000, 0); static const DECLARE_TLV_DB_SCALE(mic2_boost_vol_tlv, -200, 200, 0); -static const struct snd_kcontrol_new rt721_sdca_controls[] = { +static const struct snd_kcontrol_new rt721s_sdca_controls[] = { /* Headphone playback settings */ SOC_DOUBLE_R_EXT_TLV("FU05 Playback Volume", SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_USER_FU05, RT721_SDCA_CTL_FU_VOLUME, CH_L), SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_USER_FU05, RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x57, 0, - rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv), + rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv_s), + /* AMP playback settings */ + SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume", + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, + RT721_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, + RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x57, 0, + rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv_s), +}; + +static const struct snd_kcontrol_new rt721u_sdca_controls[] = { + /* Headphone playback settings */ + SOC_DOUBLE_R_EXT_TLV("FU05 Playback Volume", + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_USER_FU05, + RT721_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_USER_FU05, + RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x7b, 0, + rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv_u), + /* AMP playback settings */ + SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume", + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, + RT721_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, + RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x7b, 0, + rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv_u), +}; + +static const struct snd_kcontrol_new rt721_sdca_controls[] = { /* Headset mic capture settings */ SOC_DOUBLE_EXT("FU0F Capture Switch", SND_SOC_NOPM, 0, 1, 1, 0, rt721_sdca_fu0f_capture_get, rt721_sdca_fu0f_capture_put), @@ -761,13 +909,6 @@ static const struct snd_kcontrol_new rt721_sdca_controls[] = { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_PLATFORM_FU44, RT721_SDCA_CTL_FU_CH_GAIN, CH_R), 1, 0x15, 0, rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, mic2_boost_vol_tlv), - /* AMP playback settings */ - SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume", - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, - RT721_SDCA_CTL_FU_VOLUME, CH_L), - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, - RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x57, 0, - rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv), /* DMIC capture settings */ RT_SDCA_FU_CTRL("FU1E Capture Switch", SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT_USER_FU1E, @@ -1276,6 +1417,15 @@ static int rt721_sdca_probe(struct snd_soc_component *component) if (ret < 0 && ret != -EACCES) return ret; + if (rt721->wf_id == RT721_S) + snd_soc_add_component_controls(component, + rt721s_sdca_controls, + ARRAY_SIZE(rt721s_sdca_controls)); + else + snd_soc_add_component_controls(component, + rt721u_sdca_controls, + ARRAY_SIZE(rt721u_sdca_controls)); + return 0; } @@ -1533,6 +1683,7 @@ int rt721_sdca_init(struct device *dev, struct regmap *regmap, rt721->fu0f_mixer_l_mute = rt721->fu0f_mixer_r_mute = true; rt721->fu1e_mixer_mute[0] = rt721->fu1e_mixer_mute[1] = rt721->fu1e_mixer_mute[2] = rt721->fu1e_mixer_mute[3] = true; + rt721->wf_id = -1; return devm_snd_soc_register_component(dev, &soc_sdca_dev_rt721, rt721_sdca_dai, ARRAY_SIZE(rt721_sdca_dai)); @@ -1550,6 +1701,7 @@ static void rt721_sdca_reset(struct rt721_sdca_priv *rt721) int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) { struct rt721_sdca_priv *rt721 = dev_get_drvdata(dev); + int val; rt721->disable_irq = false; @@ -1581,12 +1733,23 @@ int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) pm_runtime_get_noresume(&slave->dev); + if (rt721->wf_id < 0) { + rt_sdca_index_read(rt721->mbq_regmap, RT721_VENDOR_REG, + RT721_JD_PRODUCT_NUM, &val); + rt721->wf_id = (val >> 4) & 0xf; + rt721->hw_vid = val & 0xf; + dev_dbg(&slave->dev, "wf_id = %d hw_vid = %d\n", rt721->wf_id, rt721->hw_vid); + } + if (!rt721->first_hw_init) rt721_sdca_reset(rt721); rt721_sdca_dmic_preset(rt721); rt721_sdca_amp_preset(rt721); - rt721_sdca_jack_preset(rt721); + if (rt721->wf_id == RT721_S) + rt721_sdca_jack_preset(rt721); + else + rt721u_sdca_jack_preset(rt721); if (rt721->hs_jack && (!rt721->first_hw_init)) rt721_sdca_jack_init(rt721); diff --git a/sound/soc/codecs/rt721-sdca.h b/sound/soc/codecs/rt721-sdca.h index b8873b18a648..16cf40cd88de 100644 --- a/sound/soc/codecs/rt721-sdca.h +++ b/sound/soc/codecs/rt721-sdca.h @@ -40,6 +40,8 @@ struct rt721_sdca_priv { /* For DMIC */ bool fu1e_dapm_mute; bool fu1e_mixer_mute[4]; + int wf_id; + int hw_vid; }; struct rt721_sdca_dmic_kctrl_priv { @@ -273,6 +275,16 @@ enum { RT721_AIFS, }; +enum rt721_wf_id { + RT721_S, + RT721_U, +}; + +enum rt721u_hw_ver { + RT721_U_VB = 1, + RT721_U_VD = 3, +}; + int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave); int rt721_sdca_init(struct device *dev, struct regmap *regmap, struct regmap *mbq_regmap, struct sdw_slave *slave); diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 0d7182bebcdc..f0f0822d8ed6 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -685,6 +685,52 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, .dai_num = 3, }, + { + .vendor_id = 0x025d, + .part_id = 0x718, + .name_prefix = "rt721", + .version_id = 3, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt721-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt721-sdca-aif2", + .component_name = "rt721", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + /* No feedback capability is provided by rt721-sdca codec driver*/ + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt721-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + .quirk = SOC_SDW_CODEC_MIC, + .quirk_exclude = true, + }, + }, + .dai_num = 3, + }, { .vendor_id = 0x025d, .part_id = 0x722, From 6d3400863baea26232cb1a85948887fa2b7df700 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:10 +0800 Subject: [PATCH 287/924] ASoC: SOF: get nhlt from all topologies The existing code assumes there is only one NHLT blob either from BIOS or topology. As function topologies are used and each function topology contains a NHLT blob section, we need to search the matching NHLT blob from all the NHLT blobs. The commit suggests adding a list of NHLTs and search the NHLTs from the list. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-2-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/apl.c | 1 + sound/soc/sof/intel/cnl.c | 1 + sound/soc/sof/intel/hda-dai.c | 26 ++++++++++++-- sound/soc/sof/intel/icl.c | 1 + sound/soc/sof/intel/mtl.c | 1 + sound/soc/sof/intel/skl.c | 1 + sound/soc/sof/intel/tgl.c | 1 + sound/soc/sof/ipc4-priv.h | 10 ++++-- sound/soc/sof/ipc4-topology.c | 65 ++++++++++++++++++++++++----------- 9 files changed, 81 insertions(+), 26 deletions(-) diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index b0072601181e..753da09301fa 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -59,6 +59,7 @@ int sof_apl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_5; diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 660c1475e5a4..ec33cd18e669 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -406,6 +406,7 @@ int sof_cnl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_8; diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index bb44d4f8a4da..c8729b6d82bd 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -760,8 +760,20 @@ void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4 && !hda_use_tplg_nhlt) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct snd_ipc4_nhlt *entry; - ipc4_data->nhlt = intel_nhlt_init(sdev->dev); + entry = devm_kzalloc(sdev->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return; + + entry->nhlt = intel_nhlt_init(sdev->dev); + if (!entry->nhlt) { + devm_kfree(sdev->dev, entry); + return; + } + + entry->from_acpi = true; + list_add(&entry->list, &ipc4_data->nhlt_list); } } EXPORT_SYMBOL_NS(hda_set_dai_drv_ops, "SND_SOC_SOF_INTEL_HDA_COMMON"); @@ -770,9 +782,17 @@ void hda_ops_free(struct snd_sof_dev *sdev) { if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct snd_ipc4_nhlt *entry; + struct snd_ipc4_nhlt *tmp; - if (!hda_use_tplg_nhlt) - intel_nhlt_free(ipc4_data->nhlt); + if (!hda_use_tplg_nhlt) { + list_for_each_entry_safe(entry, tmp, &ipc4_data->nhlt_list, list) { + if (entry->from_acpi && entry->nhlt) + intel_nhlt_free(entry->nhlt); + + list_del(&entry->list); + } + } kfree(sdev->private); sdev->private = NULL; diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c index 549bb4ca73e1..196caba14be0 100644 --- a/sound/soc/sof/intel/icl.c +++ b/sound/soc/sof/intel/icl.c @@ -131,6 +131,7 @@ int sof_icl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2; diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index 3d67d6777f1b..2506506f813a 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -762,6 +762,7 @@ int sof_mtl_set_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *dsp_ops) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2; diff --git a/sound/soc/sof/intel/skl.c b/sound/soc/sof/intel/skl.c index 90519ebd3168..f831143a7afc 100644 --- a/sound/soc/sof/intel/skl.c +++ b/sound/soc/sof/intel/skl.c @@ -67,6 +67,7 @@ int sof_skl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET_CAVS_1_5; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_5; diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index 7936361e2e39..09a8a4a2b017 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -95,6 +95,7 @@ int sof_tgl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2; diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index a8cdf9bc750b..61dd48282dd3 100644 --- a/sound/soc/sof/ipc4-priv.h +++ b/sound/soc/sof/ipc4-priv.h @@ -58,13 +58,18 @@ struct sof_ipc4_fw_library { struct sof_ipc4_fw_module *modules; }; +struct snd_ipc4_nhlt { + struct list_head list; + void *nhlt; + bool from_acpi; +}; + /** * struct sof_ipc4_fw_data - IPC4-specific data * @manifest_fw_hdr_offset: FW header offset in the manifest * @fw_lib_xa: XArray for firmware libraries, including basefw (ID = 0) * Used to store the FW libraries and to manage the unique IDs of the * libraries. - * @nhlt: NHLT table either from the BIOS or the topology manifest * @mtrace_type: mtrace type supported on the booted platform * @mtrace_log_bytes: log bytes as reported by the firmware via fw_config reply * @num_playback_streams: max number of playback DMAs, needed for CHAIN_DMA offset @@ -74,6 +79,7 @@ struct sof_ipc4_fw_library { * base firmware * @fw_context_save: Firmware supports full context save and restore * @libraries_restored: The libraries have been retained during firmware boot + * @nhlt_list: The NHLT tables from the BIOS and the topology manifest * * @load_library: Callback function for platform dependent library loading * @pipeline_state_mutex: Mutex to protect pipeline triggers, ref counts, states and deletion @@ -81,7 +87,6 @@ struct sof_ipc4_fw_library { struct sof_ipc4_fw_data { u32 manifest_fw_hdr_offset; struct xarray fw_lib_xa; - void *nhlt; enum sof_ipc4_mtrace_type mtrace_type; u32 mtrace_log_bytes; int num_playback_streams; @@ -90,6 +95,7 @@ struct sof_ipc4_fw_data { u32 max_libs_count; bool fw_context_save; bool libraries_restored; + struct list_head nhlt_list; int (*load_library)(struct snd_sof_dev *sdev, struct sof_ipc4_fw_library *fw_lib, bool reload); diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 24a1175dd690..f6617dcbf789 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1844,12 +1844,13 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai u32 linktype, u8 dir, u32 **dst, u32 *len) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; - struct nhlt_specific_cfg *cfg; + struct nhlt_specific_cfg *cfg = NULL; + struct snd_ipc4_nhlt *entry = NULL; int sample_rate, channel_count; bool format_change = false; int bit_depth, ret; u32 nhlt_type; - int dev_type = 0; + int dev_type = -EINVAL; /* convert to NHLT type */ switch (linktype) { @@ -1880,10 +1881,17 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai * Query the type for the port and then pass that information back * to the blob lookup function. */ - dev_type = intel_nhlt_ssp_device_type(sdev->dev, ipc4_data->nhlt, - dai_index); - if (dev_type < 0) + list_for_each_entry(entry, &ipc4_data->nhlt_list, list) { + dev_type = intel_nhlt_ssp_device_type(sdev->dev, entry->nhlt, + dai_index); + if (dev_type >= 0) + break; + } + if (dev_type < 0) { + dev_err(sdev->dev, "%s: No match for SSP%d in NHLT table\n", + __func__, dai_index); return dev_type; + } break; default: return 0; @@ -1893,9 +1901,14 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai dai_index, nhlt_type, dir, dev_type); /* find NHLT blob with matching params */ - cfg = intel_nhlt_get_endpoint_blob(sdev->dev, ipc4_data->nhlt, dai_index, nhlt_type, - bit_depth, bit_depth, channel_count, sample_rate, - dir, dev_type); + list_for_each_entry(entry, &ipc4_data->nhlt_list, list) { + cfg = intel_nhlt_get_endpoint_blob(sdev->dev, entry->nhlt, dai_index, + nhlt_type, bit_depth, bit_depth, + channel_count, sample_rate, dir, + dev_type); + if (cfg) + break; + } if (!cfg) { bool get_new_blob = false; @@ -1929,13 +1942,15 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai } if (get_new_blob) { - cfg = intel_nhlt_get_endpoint_blob(sdev->dev, ipc4_data->nhlt, - dai_index, nhlt_type, - bit_depth, bit_depth, - channel_count, sample_rate, - dir, dev_type); - if (cfg) - goto out; + list_for_each_entry(entry, &ipc4_data->nhlt_list, list) { + cfg = intel_nhlt_get_endpoint_blob(sdev->dev, entry->nhlt, + dai_index, nhlt_type, + bit_depth, bit_depth, + channel_count, sample_rate, + dir, dev_type); + if (cfg) + goto out; + } } dev_err(sdev->dev, @@ -4069,6 +4084,7 @@ static int sof_ipc4_parse_manifest(struct snd_soc_component *scomp, int index, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct sof_manifest_tlv *manifest_tlv; + struct snd_ipc4_nhlt *tplg_nhlt; struct sof_manifest *manifest; u32 size = le32_to_cpu(man->priv.size); u8 *man_ptr = man->priv.data; @@ -4104,13 +4120,20 @@ static int sof_ipc4_parse_manifest(struct snd_soc_component *scomp, int index, switch (le32_to_cpu(manifest_tlv->type)) { case SOF_MANIFEST_DATA_TYPE_NHLT: - /* no NHLT in BIOS, so use the one from topology manifest */ - if (ipc4_data->nhlt) - break; - ipc4_data->nhlt = devm_kmemdup(sdev->dev, manifest_tlv->data, - le32_to_cpu(manifest_tlv->size), GFP_KERNEL); - if (!ipc4_data->nhlt) + /* Get the nhlt from topology manifest */ + tplg_nhlt = devm_kzalloc(sdev->dev, sizeof(*tplg_nhlt), GFP_KERNEL); + if (!tplg_nhlt) return -ENOMEM; + + tplg_nhlt->nhlt = devm_kmemdup(sdev->dev, manifest_tlv->data, + le32_to_cpu(manifest_tlv->size), GFP_KERNEL); + if (!tplg_nhlt->nhlt) + return -ENOMEM; + + tplg_nhlt->from_acpi = false; + + list_add(&tplg_nhlt->list, &ipc4_data->nhlt_list); + break; default: dev_warn(scomp->dev, "Skipping unknown manifest data type %d\n", From 3313c59b1888cf2916a8ee4f5a3f4e9ffbc02e1c Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:11 +0800 Subject: [PATCH 288/924] ALSA: intel-nhlt: lower intel_nhlt_ssp_device_type log level To support multiple topology fragments per function, this function could be called multiple types with different NHLT blobs from different fragments. Downgrade the error message to debug level since the SSP device type could be found from one of the nhlt. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-3-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/hda/core/intel-nhlt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/hda/core/intel-nhlt.c b/sound/hda/core/intel-nhlt.c index 6d72a871bda0..af4d469dbd76 100644 --- a/sound/hda/core/intel-nhlt.c +++ b/sound/hda/core/intel-nhlt.c @@ -351,7 +351,7 @@ int intel_nhlt_ssp_device_type(struct device *dev, struct nhlt_acpi_table *nhlt, int i; if (!nhlt) { - dev_err(dev, "%s: NHLT table is missing (query for SSP%d)\n", + dev_dbg(dev, "%s: NHLT table is missing (query for SSP%d)\n", __func__, virtual_bus_id); return -EINVAL; } @@ -369,7 +369,7 @@ int intel_nhlt_ssp_device_type(struct device *dev, struct nhlt_acpi_table *nhlt, epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); } - dev_err(dev, "%s: No match for SSP%d in NHLT table\n", __func__, + dev_dbg(dev, "%s: No match for SSP%d in NHLT table\n", __func__, virtual_bus_id); dev_dbg(dev, "Available endpoints:\n"); From 5a0601364c323f5820a3aeac2dd40a07e2ad2daf Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:12 +0800 Subject: [PATCH 289/924] ASoC: Intel: sof-function-topology-lib: create common helpers The existing code supports get_function_tplg_files callback for SoundWire machine driver only. Some common sections can be used to extend the support to other machines. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-4-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- .../intel/common/sof-function-topology-lib.c | 139 ++++++++++++------ 1 file changed, 90 insertions(+), 49 deletions(-) diff --git a/sound/soc/intel/common/sof-function-topology-lib.c b/sound/soc/intel/common/sof-function-topology-lib.c index b6e5a40b78cc..657c7b8e06e8 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.c +++ b/sound/soc/intel/common/sof-function-topology-lib.c @@ -28,6 +28,86 @@ enum tplg_device_id { #define SOF_INTEL_PLATFORM_NAME_MAX 4 +static int get_platform_name(struct snd_soc_card *card, + const struct snd_soc_acpi_mach *mach, char *platform) +{ + int ret; + + ret = sscanf(mach->sof_tplg_filename, "sof-%3s-*.tplg", platform); + if (ret != 1) { + dev_err(card->dev, "Invalid platform name of tplg %s\n", + mach->sof_tplg_filename); + return -EINVAL; + } + + return 0; +} + +static bool tplg_files_exist(struct device *dev, const char *tplg_files) +{ + const struct firmware *fw; + int ret; + + ret = firmware_request_nowarn(&fw, tplg_files, dev); + if (!ret) { + release_firmware(fw); + return true; + } + + dev_warn(dev, + "Failed to open topology file: %s, you might need to\n", + tplg_files); + dev_warn(dev, + "download it from https://github.com/thesofproject/sof-bin/\n"); + return false; +} + +static char *get_tplg_filename(struct device *dev, const char *prefix, + const char *platform, const char *tplg_dev_name, + int dai_link_id, int tplg_dev) +{ + char *filename = NULL; + + /* + * The tplg file naming rule is sof---id.tplg + * where is only required for the devices that need NHLT blob like DMIC + * as the nhlt blob is platform dependent. + */ + switch (tplg_dev) { + case TPLG_DEVICE_INTEL_PCH_DMIC: + filename = devm_kasprintf(dev, GFP_KERNEL, "%s/sof-%s-%s-id%d.tplg", + prefix, platform, tplg_dev_name, dai_link_id); + break; + default: + filename = devm_kasprintf(dev, GFP_KERNEL, "%s/sof-%s-id%d.tplg", + prefix, tplg_dev_name, dai_link_id); + break; + } + + return filename; +} + +static int get_dmic_tplg_dev(struct device *dev, int dmic_num, + int *tplg_dev, char **tplg_dev_name) +{ + switch (dmic_num) { + case 2: + *tplg_dev_name = "dmic-2ch"; + break; + case 4: + *tplg_dev_name = "dmic-4ch"; + break; + default: + dev_warn(dev, + "unsupported number of dmics: %d\n", + dmic_num); + return -EINVAL; + } + *tplg_dev = TPLG_DEVICE_INTEL_PCH_DMIC; + + return 0; +} + int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, const char *prefix, const char ***tplg_files, bool best_effort) { @@ -38,7 +118,6 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ */ struct snd_soc_acpi_mach_params mach_params = card_mach->mach_params; struct snd_soc_dai_link *dai_link; - const struct firmware *fw; char platform[SOF_INTEL_PLATFORM_NAME_MAX]; unsigned long tplg_mask = 0; int tplg_num = 0; @@ -46,12 +125,9 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ int ret; int i; - ret = sscanf(mach->sof_tplg_filename, "sof-%3s-*.tplg", platform); - if (ret != 1) { - dev_err(card->dev, "Invalid platform name %s of tplg %s\n", - platform, mach->sof_tplg_filename); - return -EINVAL; - } + ret = get_platform_name(card, mach, platform); + if (ret < 0) + return ret; for_each_card_prelinks(card, i, dai_link) { char *tplg_dev_name; @@ -70,20 +146,9 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ tplg_dev = TPLG_DEVICE_SDCA_MIC; tplg_dev_name = "sdca-mic"; } else if (strstr(dai_link->name, "dmic")) { - switch (mach_params.dmic_num) { - case 2: - tplg_dev_name = "dmic-2ch"; - break; - case 4: - tplg_dev_name = "dmic-4ch"; - break; - default: - dev_warn(card->dev, - "unsupported number of dmics: %d\n", - mach_params.dmic_num); + if (get_dmic_tplg_dev(card->dev, mach_params.dmic_num, + &tplg_dev, &tplg_dev_name) < 0) continue; - } - tplg_dev = TPLG_DEVICE_INTEL_PCH_DMIC; } else if (strstr(dai_link->name, "iDisp")) { tplg_dev = TPLG_DEVICE_HDMI; tplg_dev_name = "hdmi-pcm5"; @@ -111,25 +176,9 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ tplg_mask |= BIT(tplg_dev); - /* - * The tplg file naming rule is sof---id.tplg - * where is only required for the DMIC function as the nhlt blob - * is platform dependent. - */ - switch (tplg_dev) { - case TPLG_DEVICE_INTEL_PCH_DMIC: - (*tplg_files)[tplg_num] = devm_kasprintf(card->dev, GFP_KERNEL, - "%s/sof-%s-%s-id%d.tplg", - prefix, platform, - tplg_dev_name, dai_link->id); - break; - default: - (*tplg_files)[tplg_num] = devm_kasprintf(card->dev, GFP_KERNEL, - "%s/sof-%s-id%d.tplg", - prefix, tplg_dev_name, - dai_link->id); - break; - } + (*tplg_files)[tplg_num] = get_tplg_filename(card->dev, prefix, platform, + tplg_dev_name, dai_link->id, + tplg_dev); if (!(*tplg_files)[tplg_num]) return -ENOMEM; tplg_num++; @@ -139,17 +188,9 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ /* Check presence of sub-topologies */ for (i = 0; i < tplg_num; i++) { - ret = firmware_request_nowarn(&fw, (*tplg_files)[i], card->dev); - if (!ret) { - release_firmware(fw); - } else { - dev_warn(card->dev, - "Failed to open topology file: %s, you might need to\n", - (*tplg_files)[i]); - dev_warn(card->dev, - "download it from https://github.com/thesofproject/sof-bin/\n"); + if (!tplg_files_exist(card->dev, (*tplg_files)[i])) + /* return 0 to use monolithic topology */ return 0; - } } return tplg_num; From aa436d8744afa45cfea2488122e01d9257201aa6 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:13 +0800 Subject: [PATCH 290/924] ASoC: Intel: sof-function-topology-lib: skip non-exist file in best_effort mode Currently we will fallback to the monolithic topology if any function topology is missing. But the monolithic topology may not exist if best_effort is set. We should load the existing function topologies in the case. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-5-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- .../intel/common/sof-function-topology-lib.c | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/sound/soc/intel/common/sof-function-topology-lib.c b/sound/soc/intel/common/sof-function-topology-lib.c index 657c7b8e06e8..e1049bb85a13 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.c +++ b/sound/soc/intel/common/sof-function-topology-lib.c @@ -121,6 +121,7 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ char platform[SOF_INTEL_PLATFORM_NAME_MAX]; unsigned long tplg_mask = 0; int tplg_num = 0; + char *tplg_file; int tplg_dev; int ret; int i; @@ -174,25 +175,28 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ if (tplg_mask & BIT(tplg_dev)) continue; + tplg_file = get_tplg_filename(card->dev, prefix, platform, tplg_dev_name, + dai_link->id, tplg_dev); + if (!tplg_file) + return -ENOMEM; + + /* Check presence of sub-topologies */ + if (!tplg_files_exist(card->dev, tplg_file)) { + devm_kfree(card->dev, tplg_file); + if (best_effort) + continue; + + return 0; + } + tplg_mask |= BIT(tplg_dev); - (*tplg_files)[tplg_num] = get_tplg_filename(card->dev, prefix, platform, - tplg_dev_name, dai_link->id, - tplg_dev); - if (!(*tplg_files)[tplg_num]) - return -ENOMEM; + (*tplg_files)[tplg_num] = tplg_file; tplg_num++; } dev_dbg(card->dev, "tplg_mask %#lx tplg_num %d\n", tplg_mask, tplg_num); - /* Check presence of sub-topologies */ - for (i = 0; i < tplg_num; i++) { - if (!tplg_files_exist(card->dev, (*tplg_files)[i])) - /* return 0 to use monolithic topology */ - return 0; - } - return tplg_num; } EXPORT_SYMBOL_GPL(sof_sdw_get_tplg_files); From 6736426f31e807409e97ede9cfd2713cfcbc587a Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:14 +0800 Subject: [PATCH 291/924] ASoC: Intel: sof-function-topology-lib: add I2S support for sof_sdw_get_tplg_files The Intel SOF SDW machine drive also supports I2S interface. Add related supports for the sof_sdw_get_tplg_files() callback. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-6-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- .../intel/common/sof-function-topology-lib.c | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/sound/soc/intel/common/sof-function-topology-lib.c b/sound/soc/intel/common/sof-function-topology-lib.c index e1049bb85a13..eddccbebb5f4 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.c +++ b/sound/soc/intel/common/sof-function-topology-lib.c @@ -19,6 +19,10 @@ enum tplg_device_id { TPLG_DEVICE_SDCA_MIC, TPLG_DEVICE_INTEL_PCH_DMIC, TPLG_DEVICE_HDMI, + TPLG_DEVICE_SSP_JACK, + TPLG_DEVICE_SSP_AMP, + TPLG_DEVICE_SSP_BT, + TPLG_DEVICE_SSP_HDMI_IN, TPLG_DEVICE_LOOPBACK_VIRTUAL, TPLG_DEVICE_MAX }; @@ -70,11 +74,15 @@ static char *get_tplg_filename(struct device *dev, const char *prefix, /* * The tplg file naming rule is sof---id.tplg - * where is only required for the devices that need NHLT blob like DMIC + * where is required for functions that depend on NHLT blobs (e.g. DMIC/SSP) * as the nhlt blob is platform dependent. */ switch (tplg_dev) { case TPLG_DEVICE_INTEL_PCH_DMIC: + case TPLG_DEVICE_SSP_JACK: + case TPLG_DEVICE_SSP_AMP: + case TPLG_DEVICE_SSP_BT: + case TPLG_DEVICE_SSP_HDMI_IN: filename = devm_kasprintf(dev, GFP_KERNEL, "%s/sof-%s-%s-id%d.tplg", prefix, platform, tplg_dev_name, dai_link_id); break; @@ -108,6 +116,53 @@ static int get_dmic_tplg_dev(struct device *dev, int dmic_num, return 0; } +static int get_ssp_tplg_dev(struct device *dev, struct snd_soc_dai_link *dai_link, + u16 *hdmi_in_mask, int *tplg_dev, char **tplg_dev_name) +{ + unsigned int ssp_port; + + if (sscanf(dai_link->name, "SSP%d", &ssp_port) != 1) { + dev_err(dev, "Can't get SSP port from dai_link->name %s\n", dai_link->name); + return -EINVAL; + } + if (strstr(dai_link->name, "Codec")) { + /* + * Assume DAI link 0 is jack which is true in all existing + * machine drivers + */ + if (dai_link->id == 0) { + *tplg_dev = TPLG_DEVICE_SSP_JACK; + *tplg_dev_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%d-jack", ssp_port); + } else { + *tplg_dev = TPLG_DEVICE_SSP_AMP; + *tplg_dev_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%d-amp", ssp_port); + } + } else if (strstr(dai_link->name, "BT")) { + *tplg_dev = TPLG_DEVICE_SSP_BT; + *tplg_dev_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%d-bt", ssp_port); + } else if (strstr(dai_link->name, "HDMI")) { + *hdmi_in_mask |= BIT(ssp_port); + /* The number of HDMI in dai link is always 2 right now */ + if (hweight16(*hdmi_in_mask) != 2) + return -EINVAL; + + *tplg_dev = TPLG_DEVICE_SSP_HDMI_IN; + *tplg_dev_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%x-hdmiin", *hdmi_in_mask); + } else { + dev_warn(dev, + "unsupported SSP link %s\n", dai_link->name); + return -EINVAL; + } + if (!*tplg_dev_name) + return -ENOMEM; + + return 0; +} + int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, const char *prefix, const char ***tplg_files, bool best_effort) { @@ -120,6 +175,7 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ struct snd_soc_dai_link *dai_link; char platform[SOF_INTEL_PLATFORM_NAME_MAX]; unsigned long tplg_mask = 0; + u16 hdmi_in_mask = 0; int tplg_num = 0; char *tplg_file; int tplg_dev; @@ -153,6 +209,10 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ } else if (strstr(dai_link->name, "iDisp")) { tplg_dev = TPLG_DEVICE_HDMI; tplg_dev_name = "hdmi-pcm5"; + } else if (strstr(dai_link->name, "SSP")) { + if (get_ssp_tplg_dev(card->dev, dai_link, &hdmi_in_mask, + &tplg_dev, &tplg_dev_name) < 0) + continue; } else if (strstr(dai_link->name, "Loopback_Virtual")) { tplg_dev = TPLG_DEVICE_LOOPBACK_VIRTUAL; /* From 21465d34877730ea032c0469b57213bc8da6db04 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:15 +0800 Subject: [PATCH 292/924] ASoC: Intel: sof-function-topology-lib: add get_function_topology for I2S machines Add sof_i2s_get_tplg_files() callback for Intel SOF I2S machines. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-7-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- .../intel/common/sof-function-topology-lib.c | 72 +++++++++++++++++++ .../intel/common/sof-function-topology-lib.h | 3 + 2 files changed, 75 insertions(+) diff --git a/sound/soc/intel/common/sof-function-topology-lib.c b/sound/soc/intel/common/sof-function-topology-lib.c index eddccbebb5f4..8f4fdf82d00f 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.c +++ b/sound/soc/intel/common/sof-function-topology-lib.c @@ -260,3 +260,75 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ return tplg_num; } EXPORT_SYMBOL_GPL(sof_sdw_get_tplg_files); + +int sof_i2s_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, + const char *prefix, const char ***tplg_files, bool best_effort) +{ + struct snd_soc_acpi_mach_params mach_params = mach->mach_params; + struct snd_soc_dai_link *dai_link; + char platform[SOF_INTEL_PLATFORM_NAME_MAX]; + unsigned long tplg_mask = 0; + u16 hdmi_in_mask = 0; + int tplg_num = 0; + char *tplg_file; + int tplg_dev; + int ret; + int i; + + ret = get_platform_name(card, mach, platform); + if (ret < 0) + return ret; + + for_each_card_prelinks(card, i, dai_link) { + char *tplg_dev_name; + + dev_dbg(card->dev, "dai_link %s id %d\n", dai_link->name, dai_link->id); + if (strstr(dai_link->name, "SSP")) { + if (get_ssp_tplg_dev(card->dev, dai_link, &hdmi_in_mask, + &tplg_dev, &tplg_dev_name) < 0) + continue; + } else if (strstr(dai_link->name, "dmic")) { + if (get_dmic_tplg_dev(card->dev, mach_params.dmic_num, + &tplg_dev, &tplg_dev_name) < 0) + continue; + } else if (strstr(dai_link->name, "iDisp")) { + tplg_dev = TPLG_DEVICE_HDMI; + tplg_dev_name = "hdmi-pcm5"; + } else { + /* The dai link is not supported by separated tplg yet */ + dev_dbg(card->dev, + "dai_link %s is not supported by separated tplg yet\n", + dai_link->name); + if (best_effort) + continue; + + return 0; + } + if (tplg_mask & BIT(tplg_dev)) + continue; + + tplg_file = get_tplg_filename(card->dev, prefix, platform, tplg_dev_name, + dai_link->id, tplg_dev); + if (!tplg_file) + return -ENOMEM; + + /* Check presence of sub-topologies */ + if (!tplg_files_exist(card->dev, tplg_file)) { + devm_kfree(card->dev, tplg_file); + if (best_effort) + continue; + + return 0; + } + + tplg_mask |= BIT(tplg_dev); + + (*tplg_files)[tplg_num] = tplg_file; + tplg_num++; + } + + dev_dbg(card->dev, "tplg_mask %#lx tplg_num %d\n", tplg_mask, tplg_num); + + return tplg_num; +} +EXPORT_SYMBOL_GPL(sof_i2s_get_tplg_files); diff --git a/sound/soc/intel/common/sof-function-topology-lib.h b/sound/soc/intel/common/sof-function-topology-lib.h index f358f8c52d78..9755e9770968 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.h +++ b/sound/soc/intel/common/sof-function-topology-lib.h @@ -12,4 +12,7 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, const char *prefix, const char ***tplg_files, bool best_effort); +int sof_i2s_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, + const char *prefix, const char ***tplg_files, bool best_effort); + #endif From 7f1040813b63f0674ecc5bcf98a66b3411b15fb4 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:16 +0800 Subject: [PATCH 293/924] ASoC: soc-acpi-intel-ptl-match: I2S machines: use function topology Use sof_i2s_get_tplg_files() for SOF es83x6 machines. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-8-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/common/soc-acpi-intel-ptl-match.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c index dd84e97836c9..0ce51c44bfe6 100644 --- a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c @@ -51,12 +51,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_machines[] = { .drv_name = "ptl_es83x6_c1_h02", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &ptl_lt6911_hdmi, + .get_function_tplg_files = sof_i2s_get_tplg_files, .sof_tplg_filename = "sof-ptl-es83x6-ssp1-hdmi-ssp02.tplg", }, { .comp_ids = &ptl_essx_83x6, .drv_name = "sof-essx8336", .sof_tplg_filename = "sof-ptl-es8336", /* the tplg suffix is added at run time */ + .get_function_tplg_files = sof_i2s_get_tplg_files, .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, @@ -65,6 +67,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_machines[] = { { .id = "INTC10B0", .drv_name = "ptl_lt6911_hdmi_ssp", + .get_function_tplg_files = sof_i2s_get_tplg_files, .sof_tplg_filename = "sof-ptl-hdmi-ssp02.tplg", }, {}, From e845b86c58e86ea4466ee72763faeff6646c12f4 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 17 Sep 2026 17:01:20 +0800 Subject: [PATCH 294/924] ASoC: rt712: add GPIOs for LED control Certain platforms use the RT712 GPIOs to control the microphone mute LED and speaker mute LED. This patch lets the LED subsystem to handle the GPIO control. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260917090120.2506160-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca-sdw.c | 1 + sound/soc/codecs/rt712-sdca-sdw.h | 3 + sound/soc/codecs/rt712-sdca.c | 136 ++++++++++++++++++++++++++++++ sound/soc/codecs/rt712-sdca.h | 6 ++ 4 files changed, 146 insertions(+) diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c index edba0367d9ce..f2f91dd087bf 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.c +++ b/sound/soc/codecs/rt712-sdca-sdw.c @@ -395,6 +395,7 @@ static void rt712_sdca_sdw_remove(struct sdw_slave *slave) mutex_destroy(&rt712->calibrate_mutex); mutex_destroy(&rt712->disable_irq_lock); + mutex_destroy(&rt712->gc_lock); } static const struct sdw_device_id rt712_sdca_id[] = { diff --git a/sound/soc/codecs/rt712-sdca-sdw.h b/sound/soc/codecs/rt712-sdca-sdw.h index 7ad25cc27f62..162d62234b68 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.h +++ b/sound/soc/codecs/rt712-sdca-sdw.h @@ -46,6 +46,9 @@ static const struct reg_default rt712_sdca_mbq_defaults[] = { { 0x5b00029, 0x3fff }, { 0x5b0002a, 0xf000 }, { 0x6100000, 0x04e4 }, + { 0x6100009, 0x0000 }, + { 0x610000a, 0x0000 }, + { 0x610000b, 0x0020 }, { 0x610000e, 0x0007 }, { 0x6100045, 0x0860 }, { 0x6100046, 0x0029 }, diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c index 38052cb19790..a98537e23c21 100644 --- a/sound/soc/codecs/rt712-sdca.c +++ b/sound/soc/codecs/rt712-sdca.c @@ -113,6 +113,138 @@ static void rt712_sdca_clk_patch2(struct rt712_sdca_priv *rt712) rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); } +static int rt712_sdca_gpio_request(struct gpio_chip *chip, unsigned int offset) +{ + struct rt712_sdca_priv *rt712 = gpiochip_get_data(chip); + struct device *dev = &rt712->slave->dev; + unsigned int gpio_pin = offset + 3; + int ret; + + dev_dbg(&rt712->slave->dev, "%s: gpio_pin=%d\n", __func__, gpio_pin); + + mutex_lock(&rt712->gc_lock); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) + goto io_error; + + /* + * Only support GPIO3 and GPIO4 for now + */ + switch (gpio_pin) { + case 3: + break; + case 4: + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_LEGACY_CONFIG_CTL0, 0x000c, 0x0004); + if (ret < 0) + goto io_error; + break; + default: + ret = -EINVAL; + goto io_error; + } + + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_EN_CTL, (1 << gpio_pin), (1 << gpio_pin)); + if (ret < 0) + goto io_error; + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_DIRECTION_CTL, (1 << gpio_pin), (1 << gpio_pin)); + +io_error: + mutex_unlock(&rt712->gc_lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int rt712_sdca_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct rt712_sdca_priv *rt712 = gpiochip_get_data(chip); + struct device *dev = &rt712->slave->dev; + unsigned int gpio_pin = offset + 3; + int ret; + + dev_dbg(&rt712->slave->dev, "%s: gpio_pin=%d, value=%d\n", __func__, gpio_pin, value); + + mutex_lock(&rt712->gc_lock); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) + goto io_error; + + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_SET_CTL, (1 << gpio_pin), (!!value << gpio_pin)); + +io_error: + mutex_unlock(&rt712->gc_lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int rt712_sdca_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct rt712_sdca_priv *rt712 = gpiochip_get_data(chip); + struct device *dev = &rt712->slave->dev; + unsigned int gpio_pin = offset + 3; + int ret; + + dev_dbg(&rt712->slave->dev, "%s: gpio_pin=%d, value=%d\n", __func__, gpio_pin, value); + + mutex_lock(&rt712->gc_lock); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) + goto io_error; + + switch (gpio_pin) { + case 3: + case 4: + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_DIRECTION_CTL, (1 << gpio_pin), (1 << gpio_pin)); + if (ret < 0) + goto io_error; + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_SET_CTL, (1 << gpio_pin), (!!value << gpio_pin)); + break; + default: + ret = -EINVAL; + goto io_error; + } + +io_error: + mutex_unlock(&rt712->gc_lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static const struct gpio_chip rt712_sdca_template_chip = { + .label = "rt712-sdca", + .owner = THIS_MODULE, + .request = rt712_sdca_gpio_request, + .direction_output = rt712_sdca_gpio_direction_out, + .set = rt712_sdca_gpio_set, + .ngpio = 2, + .can_sleep = true, + .base = -1, +}; + +static void rt712_sdca_gpio_init(struct rt712_sdca_priv *rt712) +{ + int ret; + + mutex_init(&rt712->gc_lock); + rt712->gpio_chip = rt712_sdca_template_chip; + rt712->gpio_chip.parent = &rt712->slave->dev; + rt712->gpio_chip.fwnode = dev_fwnode(&rt712->slave->dev); + + ret = devm_gpiochip_add_data(&rt712->slave->dev, &rt712->gpio_chip, rt712); + if (ret != 0) + dev_err(&rt712->slave->dev, "Failed to add GPIOs: %d\n", ret); +} + static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) { unsigned int val, loop_rc = 0, loop_dc = 0; @@ -1724,6 +1856,10 @@ int rt712_sdca_init(struct device *dev, struct regmap *regmap, INIT_DELAYED_WORK(&rt712->jack_detect_work, rt712_sdca_jack_detect_handler); INIT_DELAYED_WORK(&rt712->jack_btn_check_work, rt712_sdca_btn_check_handler); + /* initialize GPIOs */ + if (IS_ENABLED(CONFIG_GPIOLIB)) + rt712_sdca_gpio_init(rt712); + /* * Mark hw_init to false * HW init will be performed when device reports present diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h index 6229fe341bb5..7e6ee21c8a17 100644 --- a/sound/soc/codecs/rt712-sdca.h +++ b/sound/soc/codecs/rt712-sdca.h @@ -14,6 +14,7 @@ #include #include #include +#include struct rt712_sdca_priv { struct regmap *regmap; @@ -45,6 +46,8 @@ struct rt712_sdca_priv { bool fu05_dapm_mute; bool fu05_mixer_l_mute; bool fu05_mixer_r_mute; + struct gpio_chip gpio_chip; + struct mutex gc_lock; }; struct rt712_dmic_kctrl_priv { @@ -114,6 +117,9 @@ struct rt712_dmic_kctrl_priv { #define RT712_HDA_LEGACY_MUX_CTL0 0x00 #define RT712_HDA_LEGACY_CONFIG_CTL0 0x06 #define RT712_HDA_LEGACY_RESET_CTL 0x08 +#define RT712_HDA_GPIO_EN_CTL 0x09 +#define RT712_HDA_GPIO_DIRECTION_CTL 0x0a +#define RT712_HDA_GPIO_SET_CTL 0x0b #define RT712_HDA_LEGACY_GPIO_WAKE_EN_CTL 0x0e #define RT712_DMIC_ENT_FLOAT_CTL 0x10 #define RT712_DMIC_GAIN_ENT_FLOAT_CTL0 0x11 From ba593d662183de74d2b733b4c6e8cd26f04f0a65 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 07:31:36 +0000 Subject: [PATCH 295/924] ASoC: ti: davinci-mcasp: Fix runtime PM imbalance in __davinci_mcasp_set_clkdiv() In __davinci_mcasp_set_clkdiv(), pm_runtime_get_sync() is called at the entry of the function. If an invalid div_id is provided, the function hits the default switch case and directly returns -EINVAL without calling pm_runtime_put(), resulting in a runtime PM reference leak. Add pm_runtime_put() in the default case before returning -EINVAL. Fixes: 4ed8c9b737b6 ("ASoC: McASP: add support for clock dividers") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Tested-by: Sen Wang Link: https://patch.msgid.link/20260916073136.1971673-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/ti/davinci-mcasp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index d461ab419655..481d1042ddb6 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -799,6 +799,7 @@ static int __davinci_mcasp_set_clkdiv(struct davinci_mcasp *mcasp, int div_id, break; default: + pm_runtime_put(mcasp->dev); return -EINVAL; } From 2a2d659b835c37c93d0200f50a549973ca03c67b Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Tue, 22 Sep 2026 13:31:20 -0500 Subject: [PATCH 296/924] ASoC: dt-bindings: mediatek: Fix MT7622 clock count MT7622 audio nodes have 33 clocks and clock names. The shared MT2701 binding listed the longer MT2701 clock set as the common minimum, so MT7622 DTs failed validation as too short even though the binding already capped MT7622 at a smaller list. Set the common minimum to 33 and make the MT7622 conditional require exactly 33 clocks and clock names. Assisted-by: LLM Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260922183121.289108-1-robh@kernel.org Signed-off-by: Mark Brown --- .../bindings/sound/mediatek,mt2701-audio.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt2701-audio.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt2701-audio.yaml index 30f331366566..10df1e7c7eb9 100644 --- a/Documentation/devicetree/bindings/sound/mediatek,mt2701-audio.yaml +++ b/Documentation/devicetree/bindings/sound/mediatek,mt2701-audio.yaml @@ -32,7 +32,7 @@ properties: maxItems: 1 clocks: - minItems: 34 + minItems: 33 items: - description: audio infra sys clock - description: top audio mux 1 @@ -74,7 +74,7 @@ properties: - description: audio APLL root pd clock-names: - minItems: 34 + minItems: 33 items: - const: infra_sys_audio_clk - const: top_audio_mux1_sel @@ -132,8 +132,10 @@ allOf: then: properties: clocks: - maxItems: 34 + minItems: 33 + maxItems: 33 clock-names: - maxItems: 34 + minItems: 33 + maxItems: 33 additionalProperties: false From 425733d0aea97c677fa335a3f37dc20057cd0e6f Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Tue, 22 Sep 2026 13:31:28 -0500 Subject: [PATCH 297/924] ASoC: dt-bindings: hi6210-i2s: Allow graph ports The HiKey DTS uses the HI6210 I2S controller with an audio graph ports node for the ADV7533 connection. Add the audio graph ports node so the converted schema matches the existing platform description. Assisted-by: LLM Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260922183128.289564-1-robh@kernel.org Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml b/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml index 5171f984630b..b12698264724 100644 --- a/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml +++ b/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml @@ -48,6 +48,10 @@ properties: The dai cell indexes reference the following interfaces: 0: S2 interface + ports: + $ref: audio-graph-port.yaml#/definitions/ports + unevaluatedProperties: false + required: - compatible - reg From 87dd248876d11621c4ed0e9fe865b39d40833535 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Tue, 22 Sep 2026 16:21:21 -0500 Subject: [PATCH 298/924] ASoC: dt-bindings: Fix TAS2563 address constraints Apply the TAS2781 register range only when the compatible is exactly ti,tas2781. This avoids applying the TAS2781 single-address rule to TAS2563 nodes with 0x4c-0x4f addresses. Assisted-by: LLM Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260922212122.1116270-1-robh@kernel.org Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/ti,tas2781.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/ti,tas2781.yaml b/Documentation/devicetree/bindings/sound/ti,tas2781.yaml index aa5a317b5a36..50c7792b24f8 100644 --- a/Documentation/devicetree/bindings/sound/ti,tas2781.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tas2781.yaml @@ -219,9 +219,7 @@ allOf: - if: properties: compatible: - contains: - enum: - - ti,tas2781 + const: ti,tas2781 then: properties: reg: From 3713c74a1b0ad71b416345399d8a706c921cb47f Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:45 +0900 Subject: [PATCH 299/924] ASoC: codecs: cpcap: Fix devm resource leaks across card bind/unbind The ASoC component .probe/.remove are called on card bind/unbind, but component->dev is the underlying platform device. Resources requested with devm_*(component->dev, ...) or request_irq() in the component .probe are only released when the platform device is removed, not on card unbind, leaking on every bind/unbind cycle. The VAUDIO regulator is a pure hardware resource that only depends on the platform device, so acquire it with devm_regulator_get() in the platform probe where its devres lifetime matches the device. The headset/mic-button IRQs genuinely depend on the component, so keep them in cpcap_soc_probe() but request them with the non-devm request_irq() API and free them explicitly in cpcap_soc_remove(). Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-2-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/cpcap.c | 72 ++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index 0262900fbb7d..dda68165b817 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -1622,21 +1622,12 @@ static int cpcap_soc_probe(struct snd_soc_component *component) { struct platform_device *pdev = to_platform_device(component->dev); struct snd_soc_card *card = component->card; - struct cpcap_audio *cpcap; + struct cpcap_audio *cpcap = dev_get_drvdata(component->dev); int err; - cpcap = devm_kzalloc(component->dev, sizeof(*cpcap), GFP_KERNEL); - if (!cpcap) - return -ENOMEM; - snd_soc_component_set_drvdata(component, cpcap); cpcap->component = component; - cpcap->vaudio = devm_regulator_get(component->dev, "VAUDIO"); - if (IS_ERR(cpcap->vaudio)) - return dev_err_probe(component->dev, PTR_ERR(cpcap->vaudio), - "Cannot get VAUDIO regulator\n"); - err = snd_soc_card_jack_new(card, "Headphones", SND_JACK_HEADSET | SND_JACK_BTN_0, &cpcap->jack); @@ -1660,13 +1651,13 @@ static int cpcap_soc_probe(struct snd_soc_component *component) if (cpcap->hsirq < 0) return cpcap->hsirq; - err = devm_request_threaded_irq(component->dev, cpcap->hsirq, NULL, - cpcap_hs_irq_thread, - IRQF_TRIGGER_RISING | - IRQF_TRIGGER_FALLING | - IRQF_ONESHOT, - "cpcap-codec-hs", - component); + err = request_threaded_irq(cpcap->hsirq, NULL, + cpcap_hs_irq_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "cpcap-codec-hs", + component); if (err) { dev_warn(component->dev, "no HS irq%i: %i\n", cpcap->hsirq, err); @@ -1674,25 +1665,27 @@ static int cpcap_soc_probe(struct snd_soc_component *component) } cpcap->mb2irq = platform_get_irq_byname(pdev, "mb2"); - if (cpcap->mb2irq < 0) - return cpcap->mb2irq; + if (cpcap->mb2irq < 0) { + err = cpcap->mb2irq; + goto err_free_hsirq; + } - err = devm_request_threaded_irq(component->dev, cpcap->mb2irq, NULL, - cpcap_mb2_irq_thread, - IRQF_TRIGGER_RISING | - IRQF_TRIGGER_FALLING | - IRQF_ONESHOT, - "cpcap-codec-mb2", - component); + err = request_threaded_irq(cpcap->mb2irq, NULL, + cpcap_mb2_irq_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "cpcap-codec-mb2", + component); if (err) { dev_warn(component->dev, "no MB2 irq%i: %i\n", cpcap->mb2irq, err); - return err; + goto err_free_hsirq; } err = cpcap_audio_reset(component, false); if (err) - return err; + goto err_free_mb2irq; cpcap_hs_irq_thread(cpcap->hsirq, component); @@ -1700,6 +1693,13 @@ static int cpcap_soc_probe(struct snd_soc_component *component) enable_irq_wake(cpcap->mb2irq); return 0; + +err_free_mb2irq: + free_irq(cpcap->mb2irq, component); +err_free_hsirq: + free_irq(cpcap->hsirq, component); + + return err; } static void cpcap_soc_remove(struct snd_soc_component *component) @@ -1708,6 +1708,9 @@ static void cpcap_soc_remove(struct snd_soc_component *component) disable_irq_wake(cpcap->hsirq); disable_irq_wake(cpcap->mb2irq); + + free_irq(cpcap->mb2irq, component); + free_irq(cpcap->hsirq, component); } static int cpcap_set_bias_level(struct snd_soc_component *component, @@ -1754,11 +1757,24 @@ static int cpcap_codec_probe(struct platform_device *pdev) { struct device_node *codec_node = of_get_child_by_name(pdev->dev.parent->of_node, "audio-codec"); + struct cpcap_audio *cpcap; + if (!codec_node) return -ENODEV; pdev->dev.of_node = codec_node; + cpcap = devm_kzalloc(&pdev->dev, sizeof(*cpcap), GFP_KERNEL); + if (!cpcap) + return -ENOMEM; + + cpcap->vaudio = devm_regulator_get(&pdev->dev, "VAUDIO"); + if (IS_ERR(cpcap->vaudio)) + return dev_err_probe(&pdev->dev, PTR_ERR(cpcap->vaudio), + "Cannot get VAUDIO regulator\n"); + + platform_set_drvdata(pdev, cpcap); + return devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_cpcap, cpcap_dai, ARRAY_SIZE(cpcap_dai)); } From 116f1bd60ae33ed794c502f56152a0384a80a2d6 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:46 +0900 Subject: [PATCH 300/924] ASoC: codecs: da7218: Fix devm resource leaks across card bind/unbind component->dev is the underlying I2C device, so resources requested with devm_*(component->dev, ...) in the component .probe are only released when the I2C device is removed, not on card unbind. This leaks the mclk clock, the regulator bulk, the platform data parsed from the device tree and the IRQ on every card bind/unbind cycle. Move the mclk clock and the regulator supplies (pure hardware resources tied to the I2C device) to da7218_i2c_probe() using devm on &i2c->dev. Likewise move the device tree parsing (da7218_of_to_pdata()), which also allocates with devm, to da7218_i2c_probe() and store the result in da7218->pdata; the OF helpers now take a struct device so they no longer depend on the component. Keep the IRQ in the component .probe (it feeds the component) but request it with the non-devm API and free it in da7218_remove(). Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-3-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/da7218.c | 129 +++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 64 deletions(-) diff --git a/sound/soc/codecs/da7218.c b/sound/soc/codecs/da7218.c index 361daf14152e..a538b3dd1635 100644 --- a/sound/soc/codecs/da7218.c +++ b/sound/soc/codecs/da7218.c @@ -2298,7 +2298,7 @@ static const struct of_device_id da7218_of_match[] = { MODULE_DEVICE_TABLE(of, da7218_of_match); static enum da7218_micbias_voltage - da7218_of_micbias_lvl(struct snd_soc_component *component, u32 val) + da7218_of_micbias_lvl(struct device *dev, u32 val) { switch (val) { case 1200: @@ -2320,13 +2320,13 @@ static enum da7218_micbias_voltage case 3000: return DA7218_MICBIAS_3_0V; default: - dev_warn(component->dev, "Invalid micbias level"); + dev_warn(dev, "Invalid micbias level"); return DA7218_MICBIAS_1_6V; } } static enum da7218_mic_amp_in_sel - da7218_of_mic_amp_in_sel(struct snd_soc_component *component, const char *str) + da7218_of_mic_amp_in_sel(struct device *dev, const char *str) { if (!strcmp(str, "diff")) { return DA7218_MIC_AMP_IN_SEL_DIFF; @@ -2335,39 +2335,39 @@ static enum da7218_mic_amp_in_sel } else if (!strcmp(str, "se_n")) { return DA7218_MIC_AMP_IN_SEL_SE_N; } else { - dev_warn(component->dev, "Invalid mic input type selection"); + dev_warn(dev, "Invalid mic input type selection"); return DA7218_MIC_AMP_IN_SEL_DIFF; } } static enum da7218_dmic_data_sel - da7218_of_dmic_data_sel(struct snd_soc_component *component, const char *str) + da7218_of_dmic_data_sel(struct device *dev, const char *str) { if (!strcmp(str, "lrise_rfall")) { return DA7218_DMIC_DATA_LRISE_RFALL; } else if (!strcmp(str, "lfall_rrise")) { return DA7218_DMIC_DATA_LFALL_RRISE; } else { - dev_warn(component->dev, "Invalid DMIC data type selection"); + dev_warn(dev, "Invalid DMIC data type selection"); return DA7218_DMIC_DATA_LRISE_RFALL; } } static enum da7218_dmic_samplephase - da7218_of_dmic_samplephase(struct snd_soc_component *component, const char *str) + da7218_of_dmic_samplephase(struct device *dev, const char *str) { if (!strcmp(str, "on_clkedge")) { return DA7218_DMIC_SAMPLE_ON_CLKEDGE; } else if (!strcmp(str, "between_clkedge")) { return DA7218_DMIC_SAMPLE_BETWEEN_CLKEDGE; } else { - dev_warn(component->dev, "Invalid DMIC sample phase"); + dev_warn(dev, "Invalid DMIC sample phase"); return DA7218_DMIC_SAMPLE_ON_CLKEDGE; } } static enum da7218_dmic_clk_rate - da7218_of_dmic_clkrate(struct snd_soc_component *component, u32 val) + da7218_of_dmic_clkrate(struct device *dev, u32 val) { switch (val) { case 1500000: @@ -2375,13 +2375,13 @@ static enum da7218_dmic_clk_rate case 3000000: return DA7218_DMIC_CLK_3_0MHZ; default: - dev_warn(component->dev, "Invalid DMIC clock rate"); + dev_warn(dev, "Invalid DMIC clock rate"); return DA7218_DMIC_CLK_3_0MHZ; } } static enum da7218_hpldet_jack_rate - da7218_of_jack_rate(struct snd_soc_component *component, u32 val) + da7218_of_jack_rate(struct device *dev, u32 val) { switch (val) { case 5: @@ -2401,13 +2401,13 @@ static enum da7218_hpldet_jack_rate case 640: return DA7218_HPLDET_JACK_RATE_640US; default: - dev_warn(component->dev, "Invalid jack detect rate"); + dev_warn(dev, "Invalid jack detect rate"); return DA7218_HPLDET_JACK_RATE_40US; } } static enum da7218_hpldet_jack_debounce - da7218_of_jack_debounce(struct snd_soc_component *component, u32 val) + da7218_of_jack_debounce(struct device *dev, u32 val) { switch (val) { case 0: @@ -2419,13 +2419,13 @@ static enum da7218_hpldet_jack_debounce case 4: return DA7218_HPLDET_JACK_DEBOUNCE_4; default: - dev_warn(component->dev, "Invalid jack debounce"); + dev_warn(dev, "Invalid jack debounce"); return DA7218_HPLDET_JACK_DEBOUNCE_2; } } static enum da7218_hpldet_jack_thr - da7218_of_jack_thr(struct snd_soc_component *component, u32 val) + da7218_of_jack_thr(struct device *dev, u32 val) { switch (val) { case 84: @@ -2437,76 +2437,76 @@ static enum da7218_hpldet_jack_thr case 96: return DA7218_HPLDET_JACK_THR_96PCT; default: - dev_warn(component->dev, "Invalid jack threshold level"); + dev_warn(dev, "Invalid jack threshold level"); return DA7218_HPLDET_JACK_THR_84PCT; } } -static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *component) +static struct da7218_pdata *da7218_of_to_pdata(struct device *dev, + struct da7218_priv *da7218) { - struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); - struct device_node *np = component->dev->of_node; + struct device_node *np = dev->of_node; struct device_node *hpldet_np; struct da7218_pdata *pdata; struct da7218_hpldet_pdata *hpldet_pdata; const char *of_str; u32 of_val32; - pdata = devm_kzalloc(component->dev, sizeof(*pdata), GFP_KERNEL); + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return NULL; if (of_property_read_u32(np, "dlg,micbias1-lvl-millivolt", &of_val32) >= 0) - pdata->micbias1_lvl = da7218_of_micbias_lvl(component, of_val32); + pdata->micbias1_lvl = da7218_of_micbias_lvl(dev, of_val32); else pdata->micbias1_lvl = DA7218_MICBIAS_1_6V; if (of_property_read_u32(np, "dlg,micbias2-lvl-millivolt", &of_val32) >= 0) - pdata->micbias2_lvl = da7218_of_micbias_lvl(component, of_val32); + pdata->micbias2_lvl = da7218_of_micbias_lvl(dev, of_val32); else pdata->micbias2_lvl = DA7218_MICBIAS_1_6V; if (!of_property_read_string(np, "dlg,mic1-amp-in-sel", &of_str)) pdata->mic1_amp_in_sel = - da7218_of_mic_amp_in_sel(component, of_str); + da7218_of_mic_amp_in_sel(dev, of_str); else pdata->mic1_amp_in_sel = DA7218_MIC_AMP_IN_SEL_DIFF; if (!of_property_read_string(np, "dlg,mic2-amp-in-sel", &of_str)) pdata->mic2_amp_in_sel = - da7218_of_mic_amp_in_sel(component, of_str); + da7218_of_mic_amp_in_sel(dev, of_str); else pdata->mic2_amp_in_sel = DA7218_MIC_AMP_IN_SEL_DIFF; if (!of_property_read_string(np, "dlg,dmic1-data-sel", &of_str)) - pdata->dmic1_data_sel = da7218_of_dmic_data_sel(component, of_str); + pdata->dmic1_data_sel = da7218_of_dmic_data_sel(dev, of_str); else pdata->dmic1_data_sel = DA7218_DMIC_DATA_LRISE_RFALL; if (!of_property_read_string(np, "dlg,dmic1-samplephase", &of_str)) pdata->dmic1_samplephase = - da7218_of_dmic_samplephase(component, of_str); + da7218_of_dmic_samplephase(dev, of_str); else pdata->dmic1_samplephase = DA7218_DMIC_SAMPLE_ON_CLKEDGE; if (of_property_read_u32(np, "dlg,dmic1-clkrate-hz", &of_val32) >= 0) - pdata->dmic1_clk_rate = da7218_of_dmic_clkrate(component, of_val32); + pdata->dmic1_clk_rate = da7218_of_dmic_clkrate(dev, of_val32); else pdata->dmic1_clk_rate = DA7218_DMIC_CLK_3_0MHZ; if (!of_property_read_string(np, "dlg,dmic2-data-sel", &of_str)) - pdata->dmic2_data_sel = da7218_of_dmic_data_sel(component, of_str); + pdata->dmic2_data_sel = da7218_of_dmic_data_sel(dev, of_str); else pdata->dmic2_data_sel = DA7218_DMIC_DATA_LRISE_RFALL; if (!of_property_read_string(np, "dlg,dmic2-samplephase", &of_str)) pdata->dmic2_samplephase = - da7218_of_dmic_samplephase(component, of_str); + da7218_of_dmic_samplephase(dev, of_str); else pdata->dmic2_samplephase = DA7218_DMIC_SAMPLE_ON_CLKEDGE; if (of_property_read_u32(np, "dlg,dmic2-clkrate-hz", &of_val32) >= 0) - pdata->dmic2_clk_rate = da7218_of_dmic_clkrate(component, of_val32); + pdata->dmic2_clk_rate = da7218_of_dmic_clkrate(dev, of_val32); else pdata->dmic2_clk_rate = DA7218_DMIC_CLK_3_0MHZ; @@ -2520,7 +2520,7 @@ static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *compone if (!hpldet_np) return pdata; - hpldet_pdata = devm_kzalloc(component->dev, sizeof(*hpldet_pdata), + hpldet_pdata = devm_kzalloc(dev, sizeof(*hpldet_pdata), GFP_KERNEL); if (!hpldet_pdata) { of_node_put(hpldet_np); @@ -2531,14 +2531,14 @@ static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *compone if (of_property_read_u32(hpldet_np, "dlg,jack-rate-us", &of_val32) >= 0) hpldet_pdata->jack_rate = - da7218_of_jack_rate(component, of_val32); + da7218_of_jack_rate(dev, of_val32); else hpldet_pdata->jack_rate = DA7218_HPLDET_JACK_RATE_40US; if (of_property_read_u32(hpldet_np, "dlg,jack-debounce", &of_val32) >= 0) hpldet_pdata->jack_debounce = - da7218_of_jack_debounce(component, of_val32); + da7218_of_jack_debounce(dev, of_val32); else hpldet_pdata->jack_debounce = DA7218_HPLDET_JACK_DEBOUNCE_2; @@ -2546,7 +2546,7 @@ static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *compone if (of_property_read_u32(hpldet_np, "dlg,jack-threshold-pct", &of_val32) >= 0) hpldet_pdata->jack_thr = - da7218_of_jack_thr(component, of_val32); + da7218_of_jack_thr(dev, of_val32); else hpldet_pdata->jack_thr = DA7218_HPLDET_JACK_THR_84PCT; @@ -2638,18 +2638,7 @@ static int da7218_handle_supplies(struct snd_soc_component *component) struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); struct regulator *vddio; u8 io_voltage_lvl = DA7218_IO_VOLTAGE_LEVEL_2_5V_3_6V; - int i, ret; - - /* Get required supplies */ - for (i = 0; i < DA7218_NUM_SUPPLIES; ++i) - da7218->supplies[i].supply = da7218_supply_names[i]; - - ret = devm_regulator_bulk_get(component->dev, DA7218_NUM_SUPPLIES, - da7218->supplies); - if (ret) { - dev_err(component->dev, "Failed to get supplies\n"); - return ret; - } + int ret; /* Determine VDDIO voltage provided */ vddio = da7218->supplies[DA7218_SUPPLY_VDDIO].consumer; @@ -2887,21 +2876,8 @@ static int da7218_probe(struct snd_soc_component *component) if (ret) return ret; - /* Handle DT/Platform data */ - if (component->dev->of_node) - da7218->pdata = da7218_of_to_pdata(component); - else - da7218->pdata = dev_get_platdata(component->dev); - da7218_handle_pdata(component); - /* Check if MCLK provided, if not the clock is NULL */ - da7218->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(da7218->mclk)) { - ret = PTR_ERR(da7218->mclk); - goto err_disable_reg; - } - /* Default PC to free-running */ snd_soc_component_write(component, DA7218_PC_COUNT, DA7218_PC_FREERUN_MASK); @@ -2965,10 +2941,10 @@ static int da7218_probe(struct snd_soc_component *component) } if (da7218->irq) { - ret = devm_request_threaded_irq(component->dev, da7218->irq, NULL, - da7218_irq_thread, - IRQF_TRIGGER_LOW | IRQF_ONESHOT, - "da7218", component); + ret = request_threaded_irq(da7218->irq, NULL, + da7218_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "da7218", component); if (ret != 0) { dev_err(component->dev, "Failed to request IRQ %d: %d\n", da7218->irq, ret); @@ -2989,6 +2965,9 @@ static void da7218_remove(struct snd_soc_component *component) { struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + if (da7218->irq) + free_irq(da7218->irq, component); + regulator_bulk_disable(DA7218_NUM_SUPPLIES, da7218->supplies); } @@ -3259,7 +3238,7 @@ static const struct regmap_config da7218_regmap_config = { static int da7218_i2c_probe(struct i2c_client *i2c) { struct da7218_priv *da7218; - int ret; + int i, ret; da7218 = devm_kzalloc(&i2c->dev, sizeof(*da7218), GFP_KERNEL); if (!da7218) @@ -3284,6 +3263,28 @@ static int da7218_i2c_probe(struct i2c_client *i2c) return ret; } + /* Get required supplies */ + for (i = 0; i < DA7218_NUM_SUPPLIES; ++i) + da7218->supplies[i].supply = da7218_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, DA7218_NUM_SUPPLIES, + da7218->supplies); + if (ret) { + dev_err(&i2c->dev, "Failed to get supplies\n"); + return ret; + } + + /* Check if MCLK provided, if not the clock is NULL */ + da7218->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(da7218->mclk)) + return PTR_ERR(da7218->mclk); + + /* Handle DT/Platform data */ + if (i2c->dev.of_node) + da7218->pdata = da7218_of_to_pdata(&i2c->dev, da7218); + else + da7218->pdata = dev_get_platdata(&i2c->dev); + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_da7218, &da7218_dai, 1); if (ret < 0) { From ef8caf5d2784dabe090814e01694a57209273ed5 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:47 +0900 Subject: [PATCH 301/924] ASoC: codecs: tlv320aic32x4: Fix clock leak from runtime callbacks component->dev is the underlying I2C/SPI device. The driver acquired its codec clocks with devm_clk_get()/devm_clk_bulk_get(component->dev, ...) from the DAI set_sysclk runtime callback, from set_bias_level and from the component .probe. Because devres on component->dev is only released when the platform device is removed, each card bind/unbind (and, for set_sysclk, each runtime invocation) leaked a fresh clock reference. Acquire all codec clocks once in the bus probe with a single devm_clk_bulk_get() into the private struct, and have the runtime callbacks and both component probes reuse those stored references. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-4-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic32x4.c | 94 +++++++++++++++----------------- 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index af4be5bee723..7fec94768afb 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -48,8 +48,18 @@ struct aic32x4_priv { enum aic32x4_type type; unsigned int fmt; + + struct clk_bulk_data clocks[7]; }; +#define AIC32X4_CLK_CODEC_CLKIN 0 +#define AIC32X4_CLK_PLL 1 +#define AIC32X4_CLK_NADC 2 +#define AIC32X4_CLK_MADC 3 +#define AIC32X4_CLK_NDAC 4 +#define AIC32X4_CLK_MDAC 5 +#define AIC32X4_CLK_BDIV 6 + static int aic32x4_reset_adc(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -591,14 +601,10 @@ static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_component *component = codec_dai->component; + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); struct clk *mclk; - struct clk *pll; - pll = devm_clk_get(component->dev, "pll"); - if (IS_ERR(pll)) - return PTR_ERR(pll); - - mclk = clk_get_parent(pll); + mclk = clk_get_parent(aic32x4->clocks[AIC32X4_CLK_PLL].clk); return clk_set_rate(mclk, freq); } @@ -750,17 +756,7 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, unsigned long adc_clock_rate, dac_clock_rate; int ret; - struct clk_bulk_data clocks[] = { - { .id = "pll" }, - { .id = "nadc" }, - { .id = "madc" }, - { .id = "ndac" }, - { .id = "mdac" }, - { .id = "bdiv" }, - }; - ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); - if (ret) - return ret; + struct clk_bulk_data *clocks = &aic32x4->clocks[AIC32X4_CLK_PLL]; ret = aic32x4_configure_rate(component, sample_rate, &aosr, &adc_resource_class, &dac_resource_class, @@ -876,18 +872,15 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); int ret; struct clk_bulk_data clocks[] = { - { .id = "madc" }, - { .id = "mdac" }, - { .id = "bdiv" }, + aic32x4->clocks[AIC32X4_CLK_MADC], + aic32x4->clocks[AIC32X4_CLK_MDAC], + aic32x4->clocks[AIC32X4_CLK_BDIV], }; - ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); - if (ret) - return ret; - switch (level) { case SND_SOC_BIAS_ON: ret = clk_bulk_prepare_enable(ARRAY_SIZE(clocks), clocks); @@ -970,23 +963,16 @@ static int aic32x4_component_probe(struct snd_soc_component *component) { struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); u32 tmp_reg; - int ret; - struct clk_bulk_data clocks[] = { - { .id = "codec_clkin" }, - { .id = "pll" }, - { .id = "bdiv" }, - { .id = "mdac" }, - }; - - ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); - if (ret) - return ret; + struct clk *codec_clkin = aic32x4->clocks[AIC32X4_CLK_CODEC_CLKIN].clk; + struct clk *pll = aic32x4->clocks[AIC32X4_CLK_PLL].clk; + struct clk *bdiv = aic32x4->clocks[AIC32X4_CLK_BDIV].clk; + struct clk *mdac = aic32x4->clocks[AIC32X4_CLK_MDAC].clk; aic32x4_setup_gpios(component); - clk_set_parent(clocks[0].clk, clocks[1].clk); - clk_set_parent(clocks[2].clk, clocks[3].clk); + clk_set_parent(codec_clkin, pll); + clk_set_parent(bdiv, mdac); /* Power platform configuration */ if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { @@ -1127,23 +1113,16 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) { struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); u32 tmp_reg; - int ret; - struct clk_bulk_data clocks[] = { - { .id = "codec_clkin" }, - { .id = "pll" }, - { .id = "bdiv" }, - { .id = "mdac" }, - }; - - ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); - if (ret) - return ret; + struct clk *codec_clkin = aic32x4->clocks[AIC32X4_CLK_CODEC_CLKIN].clk; + struct clk *pll = aic32x4->clocks[AIC32X4_CLK_PLL].clk; + struct clk *bdiv = aic32x4->clocks[AIC32X4_CLK_BDIV].clk; + struct clk *mdac = aic32x4->clocks[AIC32X4_CLK_MDAC].clk; aic32x4_setup_gpios(component); - clk_set_parent(clocks[0].clk, clocks[1].clk); - clk_set_parent(clocks[2].clk, clocks[3].clk); + clk_set_parent(codec_clkin, pll); + clk_set_parent(bdiv, mdac); /* Power platform configuration */ if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) @@ -1362,6 +1341,21 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, if (ret) goto err_disable_regulators; + aic32x4->clocks[AIC32X4_CLK_CODEC_CLKIN].id = "codec_clkin"; + aic32x4->clocks[AIC32X4_CLK_PLL].id = "pll"; + aic32x4->clocks[AIC32X4_CLK_NADC].id = "nadc"; + aic32x4->clocks[AIC32X4_CLK_MADC].id = "madc"; + aic32x4->clocks[AIC32X4_CLK_NDAC].id = "ndac"; + aic32x4->clocks[AIC32X4_CLK_MDAC].id = "mdac"; + aic32x4->clocks[AIC32X4_CLK_BDIV].id = "bdiv"; + + ret = devm_clk_bulk_get(dev, ARRAY_SIZE(aic32x4->clocks), + aic32x4->clocks); + if (ret) { + dev_err(dev, "Failed to get clocks\n"); + goto err_disable_regulators; + } + switch (aic32x4->type) { case AIC32X4_TYPE_TAS2505: ret = devm_snd_soc_register_component(dev, From 99fff20db8ded3003e4d525d8db34f731316ea92 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:48 +0900 Subject: [PATCH 302/924] ASoC: codecs: twl4030: Acquire board params and hs_extmute GPIO in the platform probe The component .probe/.remove callbacks fire on ASoC card bind/unbind, but component->dev is the underlying platform device whose devres is only released on physical device removal. Resources allocated with devm_*(component->dev, ...) in the component probe are therefore never freed on card unbind, leaking one copy per bind/unbind cycle and leaking the hs_extmute GPIO descriptor (which can also fail to be re-acquired on re-bind). The board parameters and the hs_extmute GPIO are pure hardware/device level resources: they only depend on the physical device and the DT, not on the ASoC component. Acquire them (together with the driver context) in twl4030_codec_probe() using devm on the platform device, and hand the context to the component through drvdata. Their devres lifetime then correctly follows the device rather than the card bind/unbind, so no explicit component .remove is needed. The component probe keeps only the codec register initialisation that genuinely needs the component. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-5-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 63 +++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index b986ece55d5a..442624cf2a31 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -212,21 +212,21 @@ twl4030_get_board_param_values(struct twl4030_board_params *board_params, } static struct twl4030_board_params* -twl4030_get_board_params(struct snd_soc_component *component) +twl4030_get_board_params(struct device *dev) { struct twl4030_board_params *board_params = NULL; struct device_node *twl4030_codec_node = NULL; - twl4030_codec_node = of_get_child_by_name(component->dev->parent->of_node, + twl4030_codec_node = of_get_child_by_name(dev->parent->of_node, "codec"); if (twl4030_codec_node) { - board_params = devm_kzalloc(component->dev, + board_params = devm_kzalloc(dev, sizeof(struct twl4030_board_params), GFP_KERNEL); if (!board_params) { of_node_put(twl4030_codec_node); - return NULL; + return ERR_PTR(-ENOMEM); } twl4030_get_board_param_values(board_params, twl4030_codec_node); of_node_put(twl4030_codec_node); @@ -235,21 +235,22 @@ twl4030_get_board_params(struct snd_soc_component *component) return board_params; } -static int twl4030_init_chip(struct snd_soc_component *component) +static int twl4030_get_hw_params(struct device *dev, + struct twl4030_priv *twl4030) { struct twl4030_board_params *board_params; - struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); - u8 reg, byte; - int i = 0; - board_params = twl4030_get_board_params(component); + board_params = twl4030_get_board_params(dev); + if (IS_ERR(board_params)) + return PTR_ERR(board_params); if (board_params && board_params->hs_extmute) { - board_params->hs_extmute_gpio = devm_gpiod_get_optional(component->dev, + board_params->hs_extmute_gpio = devm_gpiod_get_optional(dev, "ti,hs_extmute", GPIOD_OUT_LOW); if (IS_ERR(board_params->hs_extmute_gpio)) - return dev_err_probe(component->dev, PTR_ERR(board_params->hs_extmute_gpio), + return dev_err_probe(dev, + PTR_ERR(board_params->hs_extmute_gpio), "Failed to get hs_extmute GPIO\n"); if (board_params->hs_extmute_gpio) { @@ -257,7 +258,7 @@ static int twl4030_init_chip(struct snd_soc_component *component) } else { u8 pin_mux; - dev_info(component->dev, "use TWL4030 GPIO6\n"); + dev_info(dev, "use TWL4030 GPIO6\n"); /* Set TWL4030 GPIO6 as EXTMUTE signal */ twl_i2c_read_u8(TWL4030_MODULE_INTBR, &pin_mux, @@ -269,6 +270,18 @@ static int twl4030_init_chip(struct snd_soc_component *component) } } + twl4030->board_params = board_params; + + return 0; +} + +static int twl4030_init_chip(struct snd_soc_component *component) +{ + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + struct twl4030_board_params *board_params = twl4030->board_params; + u8 reg, byte; + int i = 0; + /* Initialize the local ctl register cache */ tw4030_init_ctl_cache(twl4030); @@ -288,8 +301,6 @@ static int twl4030_init_chip(struct snd_soc_component *component) if (!board_params) return 0; - twl4030->board_params = board_params; - reg = twl4030_read(component, TWL4030_REG_HS_POPN_SET); reg &= ~TWL4030_RAMP_DELAY; reg |= (board_params->ramp_delay_value << 2); @@ -2163,15 +2174,9 @@ static struct snd_soc_dai_driver twl4030_dai[] = { static int twl4030_soc_probe(struct snd_soc_component *component) { - struct twl4030_priv *twl4030; + struct twl4030_priv *twl4030 = dev_get_drvdata(component->dev); - twl4030 = devm_kzalloc(component->dev, sizeof(struct twl4030_priv), - GFP_KERNEL); - if (!twl4030) - return -ENOMEM; snd_soc_component_set_drvdata(component, twl4030); - /* Set the defaults, and power up the codec */ - twl4030->sysclk = twl4030_audio_get_mclk() / 1000; return twl4030_init_chip(component); } @@ -2193,6 +2198,22 @@ static const struct snd_soc_component_driver soc_component_dev_twl4030 = { static int twl4030_codec_probe(struct platform_device *pdev) { + struct twl4030_priv *twl4030; + int ret; + + twl4030 = devm_kzalloc(&pdev->dev, sizeof(*twl4030), GFP_KERNEL); + if (!twl4030) + return -ENOMEM; + + /* Set the defaults, and power up the codec */ + twl4030->sysclk = twl4030_audio_get_mclk() / 1000; + + ret = twl4030_get_hw_params(&pdev->dev, twl4030); + if (ret) + return ret; + + platform_set_drvdata(pdev, twl4030); + return devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_twl4030, twl4030_dai, ARRAY_SIZE(twl4030_dai)); From d009170e12addcaba9fb42b6be000ed4b901144e Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:49 +0900 Subject: [PATCH 303/924] ASoC: codecs: es8316: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the mclk with devm_clk_get_optional(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get_optional() to es8316_i2c_probe() so the clk reference is tied to the i2c device lifetime. Signed-off-by: Chancel Liu Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260923061305.4041197-6-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8316.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index 59399476b9d7..5e7bf6f020e0 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -771,14 +771,6 @@ static int es8316_probe(struct snd_soc_component *component) es8316->component = component; - es8316->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(es8316->mclk)) { - dev_err(component->dev, "unable to get mclk\n"); - return PTR_ERR(es8316->mclk); - } - if (!es8316->mclk) - dev_warn(component->dev, "assuming static mclk\n"); - ret = clk_prepare_enable(es8316->mclk); if (ret) { dev_err(component->dev, "unable to enable mclk\n"); @@ -883,6 +875,12 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client) i2c_set_clientdata(i2c_client, es8316); + es8316->mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(es8316->mclk)) + return dev_err_probe(dev, PTR_ERR(es8316->mclk), "unable to get mclk\n"); + if (!es8316->mclk) + dev_warn(dev, "assuming static mclk\n"); + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(es8316_supply_names), es8316_supply_names); if (ret) From be9bc47e2569ac387289ee1cbf3dd93330a663b7 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:50 +0900 Subject: [PATCH 304/924] ASoC: codecs: es8323: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the mclk with devm_clk_get_optional(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get_optional() to es8323_i2c_probe() so the clk reference is tied to the i2c device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-7-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8323.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/sound/soc/codecs/es8323.c b/sound/soc/codecs/es8323.c index 12fcfe017ab2..3f7fbbdb111e 100644 --- a/sound/soc/codecs/es8323.c +++ b/sound/soc/codecs/es8323.c @@ -671,15 +671,6 @@ static int es8323_probe(struct snd_soc_component *component) struct es8323_priv *es8323 = snd_soc_component_get_drvdata(component); int ret; - es8323->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(es8323->mclk)) { - dev_err(component->dev, "unable to get mclk\n"); - return PTR_ERR(es8323->mclk); - } - - if (!es8323->mclk) - dev_warn(component->dev, "assuming static mclk\n"); - ret = clk_prepare_enable(es8323->mclk); if (ret) { dev_err(component->dev, "unable to enable mclk\n"); @@ -789,6 +780,12 @@ static int es8323_i2c_probe(struct i2c_client *i2c_client) i2c_set_clientdata(i2c_client, es8323); + es8323->mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(es8323->mclk)) + return dev_err_probe(dev, PTR_ERR(es8323->mclk), "unable to get mclk\n"); + if (!es8323->mclk) + dev_warn(dev, "assuming static mclk\n"); + es8323->regmap = devm_regmap_init_i2c(i2c_client, &es8323_regmap); if (IS_ERR(es8323->regmap)) return PTR_ERR(es8323->regmap); From 5cc3c8f7264ea83b2c597d3d71d1a4a4a515d65f Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:51 +0900 Subject: [PATCH 305/924] ASoC: codecs: es8311: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the mclk with devm_clk_get_optional(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get_optional() to es8311_i2c_probe() so the clk reference is tied to the i2c device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-8-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8311.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/es8311.c b/sound/soc/codecs/es8311.c index e46b85f11d57..ddbbfc2443b3 100644 --- a/sound/soc/codecs/es8311.c +++ b/sound/soc/codecs/es8311.c @@ -906,12 +906,6 @@ static int es8311_component_probe(struct snd_soc_component *component) es8311 = snd_soc_component_get_drvdata(component); - es8311->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(es8311->mclk)) { - dev_err(component->dev, "invalid mclk\n"); - return PTR_ERR(es8311->mclk); - } - es8311->mclk_freq = clk_get_rate(es8311->mclk); if (es8311->mclk_freq > 0 && es8311->mclk_freq < ES8311_MCLK_MAX_FREQ) es8311_set_sysclk_constraints(es8311->mclk_freq, es8311); @@ -960,6 +954,10 @@ static int es8311_i2c_probe(struct i2c_client *i2c_client) if (es8311 == NULL) return -ENOMEM; + es8311->mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(es8311->mclk)) + return dev_err_probe(dev, PTR_ERR(es8311->mclk), "invalid mclk\n"); + es8311->regmap = devm_regmap_init_i2c(i2c_client, &es8311_regmap_config); if (IS_ERR(es8311->regmap)) From 10a45015665e8cb4f865106015246d472d7d7a3d Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:52 +0900 Subject: [PATCH 306/924] ASoC: codecs: es8328: Move clk acquisition to the bus probe component->dev is the underlying i2c/spi device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clock with devm_clk_get(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get() to es8328_probe() (the shared i2c/spi bus level probe) so the clk reference is tied to the physical device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-9-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8328.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c index 7d52b5d9d061..37fda2f42b0c 100644 --- a/sound/soc/codecs/es8328.c +++ b/sound/soc/codecs/es8328.c @@ -829,14 +829,6 @@ static int es8328_component_probe(struct snd_soc_component *component) return ret; } - /* Setup clocks */ - es8328->clk = devm_clk_get(component->dev, NULL); - if (IS_ERR(es8328->clk)) { - dev_err(component->dev, "codec clock missing or invalid\n"); - ret = PTR_ERR(es8328->clk); - goto clk_fail; - } - ret = clk_prepare_enable(es8328->clk); if (ret) { dev_err(component->dev, "unable to prepare codec clk\n"); @@ -906,6 +898,11 @@ int es8328_probe(struct device *dev, struct regmap *regmap) es8328->regmap = regmap; + es8328->clk = devm_clk_get(dev, NULL); + if (IS_ERR(es8328->clk)) + return dev_err_probe(dev, PTR_ERR(es8328->clk), + "codec clock missing or invalid\n"); + for (i = 0; i < ARRAY_SIZE(es8328->supplies); i++) es8328->supplies[i].supply = supply_names[i]; From e68e66353b8e3eb9e7d88c398a11af0cc4d2915d Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:53 +0900 Subject: [PATCH 307/924] ASoC: codecs: rt5640: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clock with devm_clk_get_optional(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get_optional() to rt5640_i2c_probe() so the clk reference is tied to the physical device lifetime. Signed-off-by: Chancel Liu Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260923061305.4041197-10-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5640.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c index 03d0ac3359f5..d6068bb5cb4c 100644 --- a/sound/soc/codecs/rt5640.c +++ b/sound/soc/codecs/rt5640.c @@ -2667,11 +2667,6 @@ static int rt5640_probe(struct snd_soc_component *component) bool dmic_en = false; u32 val; - /* Check if MCLK provided */ - rt5640->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(rt5640->mclk)) - return PTR_ERR(rt5640->mclk); - rt5640->component = component; snd_soc_dapm_force_bias_level(dapm, SND_SOC_BIAS_OFF); @@ -3012,6 +3007,11 @@ static int rt5640_i2c_probe(struct i2c_client *i2c) return -ENOMEM; i2c_set_clientdata(i2c, rt5640); + /* Check if MCLK provided */ + rt5640->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5640->mclk)) + return PTR_ERR(rt5640->mclk); + rt5640->ldo1_en = devm_gpiod_get_optional(&i2c->dev, "realtek,ldo1-en", GPIOD_OUT_HIGH); From b5c9df40ca561ac61df68834c667400640a24f75 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:54 +0900 Subject: [PATCH 308/924] ASoC: codecs: rt5616: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clock with devm_clk_get_optional(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get_optional() to rt5616_i2c_probe() so the clk reference is tied to the physical device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-11-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5616.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/rt5616.c b/sound/soc/codecs/rt5616.c index 3f9d97d04d48..e30e3b563c90 100644 --- a/sound/soc/codecs/rt5616.c +++ b/sound/soc/codecs/rt5616.c @@ -1222,11 +1222,6 @@ static int rt5616_probe(struct snd_soc_component *component) { struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); - /* Check if MCLK provided */ - rt5616->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(rt5616->mclk)) - return PTR_ERR(rt5616->mclk); - rt5616->component = component; return 0; @@ -1357,6 +1352,11 @@ static int rt5616_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, rt5616); + /* Check if MCLK provided */ + rt5616->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5616->mclk)) + return PTR_ERR(rt5616->mclk); + rt5616->regmap = devm_regmap_init_i2c(i2c, &rt5616_regmap); if (IS_ERR(rt5616->regmap)) { ret = PTR_ERR(rt5616->regmap); From cc2977e802181ce6d239734be1ce02bfdbea2380 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:55 +0900 Subject: [PATCH 309/924] ASoC: codecs: rt5514: Move clk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clocks with devm_clk_get*(component->dev, ...) in the component probe therefore leaks clk references on every card bind/unbind cycle. Move the mclk and dsp_calib_clk acquisition to rt5514_i2c_probe() so the clk references are tied to the physical device lifetime. Signed-off-by: Chancel Liu Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260923061305.4041197-12-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5514.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c index 753143e2d11f..a33fbc9fb047 100644 --- a/sound/soc/codecs/rt5514.c +++ b/sound/soc/codecs/rt5514.c @@ -1099,18 +1099,6 @@ static int rt5514_set_bias_level(struct snd_soc_component *component, static int rt5514_probe(struct snd_soc_component *component) { struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); - struct platform_device *pdev = to_platform_device(component->dev); - - rt5514->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(rt5514->mclk)) - return PTR_ERR(rt5514->mclk); - - if (rt5514->pdata.dsp_calib_clk_name) { - rt5514->dsp_calib_clk = devm_clk_get(&pdev->dev, - rt5514->pdata.dsp_calib_clk_name); - if (PTR_ERR(rt5514->dsp_calib_clk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - } rt5514->component = component; rt5514->pll3_cal_value = 0x0078b000; @@ -1285,6 +1273,17 @@ static int rt5514_i2c_probe(struct i2c_client *i2c) else rt5514_parse_dp(rt5514, &i2c->dev); + rt5514->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5514->mclk)) + return PTR_ERR(rt5514->mclk); + + if (rt5514->pdata.dsp_calib_clk_name) { + rt5514->dsp_calib_clk = devm_clk_get(&i2c->dev, + rt5514->pdata.dsp_calib_clk_name); + if (PTR_ERR(rt5514->dsp_calib_clk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } + rt5514->i2c_regmap = devm_regmap_init_i2c(i2c, &rt5514_i2c_regmap); if (IS_ERR(rt5514->i2c_regmap)) { ret = PTR_ERR(rt5514->i2c_regmap); From c580cd416b684a4411a842010ce88bd114b75340 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:56 +0900 Subject: [PATCH 310/924] ASoC: codecs: rt5682s: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clock with devm_clk_get_optional(component->dev, ...) in the component probe chain therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get_optional() to rt5682s_i2c_probe() so the clk reference is tied to the physical device lifetime. Reviewed-by: Cezary Rojewski Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-13-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5682s.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c index 6dd0d36a7186..0f80e4c32f57 100644 --- a/sound/soc/codecs/rt5682s.c +++ b/sound/soc/codecs/rt5682s.c @@ -2846,11 +2846,6 @@ static int rt5682s_dai_probe_clks(struct snd_soc_component *component) struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); int ret; - /* Check if MCLK provided */ - rt5682s->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(rt5682s->mclk)) - return PTR_ERR(rt5682s->mclk); - /* Register CCF DAI clock control */ ret = rt5682s_register_dai_clks(component); if (ret) @@ -3152,6 +3147,13 @@ static int rt5682s_i2c_probe(struct i2c_client *i2c) return ret; } +#ifdef CONFIG_COMMON_CLK + /* Check if MCLK provided */ + rt5682s->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5682s->mclk)) + return PTR_ERR(rt5682s->mclk); +#endif + for (i = 0; i < ARRAY_SIZE(rt5682s->supplies); i++) rt5682s->supplies[i].supply = rt5682s_supply_names[i]; From bf4379a7195ea1133cb0672b94878c65419aa5b9 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:57 +0900 Subject: [PATCH 311/924] ASoC: codecs: max98090: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clock with devm_clk_get(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get() to max98090_i2c_probe() so the clk reference is tied to the physical device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-14-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/max98090.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c index c31f3d32fa43..4bb5478f4642 100644 --- a/sound/soc/codecs/max98090.c +++ b/sound/soc/codecs/max98090.c @@ -2448,11 +2448,6 @@ static int max98090_probe(struct snd_soc_component *component) dev_dbg(component->dev, "max98090_probe\n"); - max98090->mclk = devm_clk_get(component->dev, "mclk"); - if (IS_ERR(max98090->mclk)) - if (PTR_ERR(max98090->mclk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - max98090->component = component; /* Reset the codec, the DSP core, and disable all interrupts */ @@ -2633,6 +2628,11 @@ static int max98090_i2c_probe(struct i2c_client *i2c) goto err_enable; } + max98090->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(max98090->mclk)) + if (PTR_ERR(max98090->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, max98090_interrupt, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "max98090_interrupt", max98090); From 8635b9033d039bdad19e65faf0450d738d34527f Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:58 +0900 Subject: [PATCH 312/924] ASoC: codecs: max98095: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clock with devm_clk_get(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get() to max98095_i2c_probe() so the clk reference is tied to the physical device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-15-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/max98095.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c index a64a5ba00379..c827fd47a5b0 100644 --- a/sound/soc/codecs/max98095.c +++ b/sound/soc/codecs/max98095.c @@ -2005,11 +2005,6 @@ static int max98095_probe(struct snd_soc_component *component) struct i2c_client *client; int ret = 0; - max98095->mclk = devm_clk_get(component->dev, "mclk"); - if (IS_ERR(max98095->mclk)) - if (PTR_ERR(max98095->mclk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - /* reset the codec, the DSP core, and disable all interrupts */ max98095_reset(component); @@ -2153,6 +2148,11 @@ static int max98095_i2c_probe(struct i2c_client *i2c) return ret; } + max98095->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(max98095->mclk)) + if (PTR_ERR(max98095->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + max98095->devtype = (uintptr_t)i2c_get_match_data(i2c); i2c_set_clientdata(i2c, max98095); max98095->pdata = i2c->dev.platform_data; From fe15e876d9ee7a205aeafa3dbadd358954ffc901 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:59 +0900 Subject: [PATCH 313/924] ASoC: codecs: wm8985: Move regulator acquisition to the bus probe component->dev is the underlying i2c/spi device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the regulator supplies with devm_regulator_bulk_get(component->dev, ...) in the component probe therefore leaks the regulator references on every card bind/unbind cycle. Move the devm_regulator_bulk_get() into a helper called from the i2c and spi probes so the supplies are tied to the physical device lifetime. Signed-off-by: Chancel Liu Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260923061305.4041197-16-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8985.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/sound/soc/codecs/wm8985.c b/sound/soc/codecs/wm8985.c index 77ca9a0feb8a..b5ca4af715c0 100644 --- a/sound/soc/codecs/wm8985.c +++ b/sound/soc/codecs/wm8985.c @@ -1024,6 +1024,23 @@ static int wm8985_set_bias_level(struct snd_soc_component *component, return 0; } +static int wm8985_get_regulators(struct device *dev, + struct wm8985_priv *wm8985) +{ + size_t i; + int ret; + + for (i = 0; i < ARRAY_SIZE(wm8985->supplies); i++) + wm8985->supplies[i].supply = wm8985_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(wm8985->supplies), + wm8985->supplies); + if (ret) + dev_err(dev, "Failed to request supplies: %d\n", ret); + + return ret; +} + static int wm8985_probe(struct snd_soc_component *component) { size_t i; @@ -1032,16 +1049,6 @@ static int wm8985_probe(struct snd_soc_component *component) wm8985 = snd_soc_component_get_drvdata(component); - for (i = 0; i < ARRAY_SIZE(wm8985->supplies); i++) - wm8985->supplies[i].supply = wm8985_supply_names[i]; - - ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(wm8985->supplies), - wm8985->supplies); - if (ret) { - dev_err(component->dev, "Failed to request supplies: %d\n", ret); - return ret; - } - ret = regulator_bulk_enable(ARRAY_SIZE(wm8985->supplies), wm8985->supplies); if (ret) { @@ -1171,6 +1178,10 @@ static int wm8985_spi_probe(struct spi_device *spi) return ret; } + ret = wm8985_get_regulators(&spi->dev, wm8985); + if (ret) + return ret; + ret = devm_snd_soc_register_component(&spi->dev, &soc_component_dev_wm8985, &wm8985_dai, 1); return ret; @@ -1207,6 +1218,10 @@ static int wm8985_i2c_probe(struct i2c_client *i2c) return ret; } + ret = wm8985_get_regulators(&i2c->dev, wm8985); + if (ret) + return ret; + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_wm8985, &wm8985_dai, 1); return ret; From 07bcfafa4719046cca821917aa01e74ed7afef4b Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:13:00 +0900 Subject: [PATCH 314/924] ASoC: codecs: wm8955: Move regulator acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the regulator supplies with devm_regulator_bulk_get(component->dev, ...) in the component probe therefore leaks the regulator references on every card bind/unbind cycle. Move the devm_regulator_bulk_get() to the i2c probe so the supplies are tied to the physical device lifetime. Reviewed-by: Charles Keepax Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-17-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8955.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c index 1d367a957f3e..017ec2e37d1e 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -888,17 +888,7 @@ static int wm8955_probe(struct snd_soc_component *component) struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct wm8955_priv *wm8955 = snd_soc_component_get_drvdata(component); struct wm8955_pdata *pdata = dev_get_platdata(component->dev); - int ret, i; - - for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++) - wm8955->supplies[i].supply = wm8955_supply_names[i]; - - ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(wm8955->supplies), - wm8955->supplies); - if (ret != 0) { - dev_err(component->dev, "Failed to request supplies: %d\n", ret); - return ret; - } + int ret; ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies); @@ -990,7 +980,7 @@ static const struct regmap_config wm8955_regmap = { static int wm8955_i2c_probe(struct i2c_client *i2c) { struct wm8955_priv *wm8955; - int ret; + int i, ret; wm8955 = devm_kzalloc(&i2c->dev, sizeof(struct wm8955_priv), GFP_KERNEL); @@ -1007,6 +997,16 @@ static int wm8955_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, wm8955); + for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++) + wm8955->supplies[i].supply = wm8955_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8955->supplies), + wm8955->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_wm8955, &wm8955_dai, 1); From 5ef6e7ff52db85556ef1fcca393366dfdd6452f9 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:13:01 +0900 Subject: [PATCH 315/924] ASoC: codecs: es8389: Move regulator and mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the core regulator supplies and the mclk with devm_*(component->dev, ...) in the component probe therefore leaks those references on every card bind/unbind cycle. Move the devm_regulator_bulk_get() and devm_clk_get_optional() into the i2c probe so the resources are tied to the physical device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-18-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8389.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c index fe341fe56760..267806b72f87 100644 --- a/sound/soc/codecs/es8389.c +++ b/sound/soc/codecs/es8389.c @@ -1067,7 +1067,7 @@ static int es8389_resume(struct snd_soc_component *component) static int es8389_probe(struct snd_soc_component *component) { - int ret, i; + int ret; struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ret = device_property_read_u8(component->dev, "everest,mclk-src", &es8389->mclk_src); @@ -1076,22 +1076,6 @@ static int es8389_probe(struct snd_soc_component *component) es8389->mclk_src = ES8389_MCLK_SOURCE; } - for (i = 0; i < ARRAY_SIZE(es8389_core_supplies); i++) - es8389->core_supply[i].supply = es8389_core_supplies[i]; - ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(es8389_core_supplies), es8389->core_supply); - if (ret) { - dev_err(component->dev, "Failed to request core supplies %d\n", ret); - return ret; - } - - es8389->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(es8389->mclk)) - return dev_err_probe(component->dev, PTR_ERR(es8389->mclk), - "ES8389 is unable to get mclk\n"); - - if (!es8389->mclk) - dev_err(component->dev, "%s, assuming static mclk\n", __func__); - ret = clk_prepare_enable(es8389->mclk); if (ret) { dev_err(component->dev, "%s, unable to enable mclk\n", __func__); @@ -1179,7 +1163,7 @@ static void es8389_i2c_shutdown(struct i2c_client *i2c) static int es8389_i2c_probe(struct i2c_client *i2c_client) { struct es8389_private *es8389; - int ret; + int ret, i; es8389 = devm_kzalloc(&i2c_client->dev, sizeof(*es8389), GFP_KERNEL); if (es8389 == NULL) @@ -1191,6 +1175,22 @@ static int es8389_i2c_probe(struct i2c_client *i2c_client) return dev_err_probe(&i2c_client->dev, PTR_ERR(es8389->regmap), "regmap_init() failed\n"); + for (i = 0; i < ARRAY_SIZE(es8389_core_supplies); i++) + es8389->core_supply[i].supply = es8389_core_supplies[i]; + ret = devm_regulator_bulk_get(&i2c_client->dev, ARRAY_SIZE(es8389_core_supplies), + es8389->core_supply); + if (ret) + return dev_err_probe(&i2c_client->dev, ret, + "Failed to request core supplies\n"); + + es8389->mclk = devm_clk_get_optional(&i2c_client->dev, "mclk"); + if (IS_ERR(es8389->mclk)) + return dev_err_probe(&i2c_client->dev, PTR_ERR(es8389->mclk), + "ES8389 is unable to get mclk\n"); + + if (!es8389->mclk) + dev_err(&i2c_client->dev, "%s, assuming static mclk\n", __func__); + ret = devm_snd_soc_register_component(&i2c_client->dev, &soc_codec_dev_es8389, &es8389_dai, From 57d1e55c7b5a66a8642e3c944d46e3d3bafb28c8 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:13:02 +0900 Subject: [PATCH 316/924] ASoC: codecs: rt5677-spi: Free the DSP context on component remove component->dev is the underlying SPI bus device, whose devres lifetime follows the physical devices probe/remove rather than the ASoC cards bind/unbind. The rt5677_dsp context allocated in the component probe with devm_kzalloc(component->dev, ...) therefore leaks on every card bind/unbind cycle, and the delayed work initialised there is never cancelled on unbind. Allocate the context with kzalloc() and add a component .remove callback that cancels the copy work, destroys the mutex and frees the context. The exported rt5677_spi_hotword_detected(), called from the rt5677 codec interrupt path, fetches the context via dev_get_drvdata(&g_spi->dev) and dereferences it. Freeing the context on remove would let that callback run concurrently and touch freed memory. Add a dsp_lock that serializes the fetch/use in rt5677_spi_hotword_detected() against clearing the drvdata in the remove callback, so once remove has cleared the pointer the callback either observes NULL and bails out or has already finished. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-19-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677-spi.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c index ebc527115ea5..0b2166082437 100644 --- a/sound/soc/codecs/rt5677-spi.c +++ b/sound/soc/codecs/rt5677-spi.c @@ -58,6 +58,8 @@ static struct spi_device *g_spi; static DEFINE_MUTEX(spi_mutex); +/* Serializes access to the DSP context against the exported hotword callback */ +static DEFINE_MUTEX(dsp_lock); struct rt5677_dsp { struct device *dev; @@ -380,8 +382,7 @@ static int rt5677_spi_pcm_probe(struct snd_soc_component *component) { struct rt5677_dsp *rt5677_dsp; - rt5677_dsp = devm_kzalloc(component->dev, sizeof(*rt5677_dsp), - GFP_KERNEL); + rt5677_dsp = kzalloc_obj(*rt5677_dsp); if (!rt5677_dsp) return -ENOMEM; rt5677_dsp->dev = &g_spi->dev; @@ -392,9 +393,23 @@ static int rt5677_spi_pcm_probe(struct snd_soc_component *component) return 0; } +static void rt5677_spi_pcm_remove(struct snd_soc_component *component) +{ + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + + scoped_guard(mutex, &dsp_lock) + snd_soc_component_set_drvdata(component, NULL); + + cancel_delayed_work_sync(&rt5677_dsp->copy_work); + mutex_destroy(&rt5677_dsp->dma_lock); + kfree(rt5677_dsp); +} + static const struct snd_soc_component_driver rt5677_spi_dai_component = { .name = DRV_NAME, .probe = rt5677_spi_pcm_probe, + .remove = rt5677_spi_pcm_remove, .open = rt5677_spi_pcm_open, .close = rt5677_spi_pcm_close, .hw_params = rt5677_spi_hw_params, @@ -579,6 +594,8 @@ void rt5677_spi_hotword_detected(void) if (!g_spi) return; + guard(mutex)(&dsp_lock); + rt5677_dsp = dev_get_drvdata(&g_spi->dev); if (!rt5677_dsp) { dev_err(&g_spi->dev, "Can't get rt5677_dsp\n"); From 48f1dfbfe4e5f314ddc3e7cbe13f0bb60a1b4f7f Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:13:03 +0900 Subject: [PATCH 317/924] ASoC: codecs: rt1011: Free the bq/drc coefficient arrays on component remove component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. The bq_drc_params arrays allocated in the component probe with devm_kcalloc(component->dev, ...) therefore leak on every card bind/unbind cycle. Allocate the arrays with kcalloc() and free them in the component remove callback. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-20-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1011.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/rt1011.c b/sound/soc/codecs/rt1011.c index d47b0370dd6b..c140c079838d 100644 --- a/sound/soc/codecs/rt1011.c +++ b/sound/soc/codecs/rt1011.c @@ -2053,29 +2053,45 @@ static int rt1011_probe(struct snd_soc_component *component) schedule_work(&rt1011->cali_work); rt1011->i2s_ref = 0; - rt1011->bq_drc_params = devm_kcalloc(component->dev, - RT1011_ADVMODE_NUM, sizeof(struct rt1011_bq_drc_params *), - GFP_KERNEL); + rt1011->bq_drc_params = kcalloc(RT1011_ADVMODE_NUM, + sizeof(struct rt1011_bq_drc_params *), + GFP_KERNEL); if (!rt1011->bq_drc_params) return -ENOMEM; for (i = 0; i < RT1011_ADVMODE_NUM; i++) { - rt1011->bq_drc_params[i] = devm_kcalloc(component->dev, - RT1011_BQ_DRC_NUM, sizeof(struct rt1011_bq_drc_params), - GFP_KERNEL); + rt1011->bq_drc_params[i] = kcalloc(RT1011_BQ_DRC_NUM, + sizeof(struct rt1011_bq_drc_params), + GFP_KERNEL); if (!rt1011->bq_drc_params[i]) - return -ENOMEM; + goto err; } return 0; + +err: + while (i--) + kfree(rt1011->bq_drc_params[i]); + kfree(rt1011->bq_drc_params); + rt1011->bq_drc_params = NULL; + + return -ENOMEM; } static void rt1011_remove(struct snd_soc_component *component) { struct rt1011_priv *rt1011 = snd_soc_component_get_drvdata(component); + int i; cancel_work_sync(&rt1011->cali_work); rt1011_reset(rt1011->regmap); + + if (rt1011->bq_drc_params) { + for (i = 0; i < RT1011_ADVMODE_NUM; i++) + kfree(rt1011->bq_drc_params[i]); + kfree(rt1011->bq_drc_params); + rt1011->bq_drc_params = NULL; + } } #ifdef CONFIG_PM From 6e7f4c5b30a3b0d47c44be2303d8370e0adfa361 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:13:04 +0900 Subject: [PATCH 318/924] ASoC: codecs: rt5645: Free the hardware EQ parameters on component remove component->dev is the underlying I2C bus device, whose devres lifetime follows the physical device's probe/remove rather than the ASoC card's bind/unbind. The eq_param array was allocated in rt5645_probe() via devm_kcalloc(component->dev, ...) but never released on component remove, so it leaked one allocation on every card bind/unbind cycle. Allocate eq_param with plain kcalloc() in rt5645_probe() and free it explicitly in rt5645_remove() to keep its lifetime tied to the component. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-21-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5645.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index bb448254275f..15d21097ed0c 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3487,10 +3487,8 @@ static int rt5645_probe(struct snd_soc_component *component) if (rt5645->pdata.long_name) component->card->long_name = rt5645->pdata.long_name; - rt5645->eq_param = devm_kcalloc(component->dev, - RT5645_HWEQ_NUM, sizeof(struct rt5645_eq_param_s), - GFP_KERNEL); - + rt5645->eq_param = kcalloc(RT5645_HWEQ_NUM, + sizeof(struct rt5645_eq_param_s), GFP_KERNEL); if (!rt5645->eq_param) return -ENOMEM; @@ -3503,7 +3501,12 @@ static int rt5645_probe(struct snd_soc_component *component) static void rt5645_remove(struct snd_soc_component *component) { + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + rt5645_reset(component); + + kfree(rt5645->eq_param); + rt5645->eq_param = NULL; } #ifdef CONFIG_PM From 3943b84e063f036ce919effa834a51d582812b9f Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:13:05 +0900 Subject: [PATCH 319/924] ASoC: codecs: rt5514-spi: Free the DSP context on component remove component->dev is the underlying SPI bus device, whose devres lifetime follows the physical device's probe/remove rather than the ASoC card's bind/unbind. The rt5514_dsp context was allocated in rt5514_spi_pcm_probe() via devm_kzalloc(component->dev, ...) and its delayed work and wakeup source were never torn down on component remove, so the context leaked and the pending copy work / wakeup source were left dangling on every card bind/unbind cycle. Allocate the context with plain kzalloc() and add a component .remove callback that cancels the copy work, undoes device_init_wakeup(), destroys the mutex and frees the context. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-22-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5514-spi.c | 37 +++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c index 91290bfe8daa..bcc2ff28b125 100644 --- a/sound/soc/codecs/rt5514-spi.c +++ b/sound/soc/codecs/rt5514-spi.c @@ -43,6 +43,7 @@ struct rt5514_dsp { struct snd_pcm_substream *substream; unsigned int buf_base, buf_limit, buf_rp; size_t buf_size, get_size, dma_offset; + int irq; }; static const struct snd_pcm_hardware rt5514_spi_pcm_hardware = { @@ -257,8 +258,7 @@ static int rt5514_spi_pcm_probe(struct snd_soc_component *component) struct rt5514_dsp *rt5514_dsp; int ret; - rt5514_dsp = devm_kzalloc(component->dev, sizeof(*rt5514_dsp), - GFP_KERNEL); + rt5514_dsp = kzalloc_obj(*rt5514_dsp); if (!rt5514_dsp) return -ENOMEM; @@ -268,21 +268,41 @@ static int rt5514_spi_pcm_probe(struct snd_soc_component *component) snd_soc_component_set_drvdata(component, rt5514_dsp); if (rt5514_spi->irq) { - ret = devm_request_threaded_irq(&rt5514_spi->dev, - rt5514_spi->irq, NULL, rt5514_spi_irq, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, "rt5514-spi", - rt5514_dsp); - if (ret) + ret = request_threaded_irq(rt5514_spi->irq, NULL, + rt5514_spi_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "rt5514-spi", rt5514_dsp); + if (ret) { dev_err(&rt5514_spi->dev, "%s Failed to request IRQ: %d\n", __func__, ret); - else + } else { + rt5514_dsp->irq = rt5514_spi->irq; device_init_wakeup(rt5514_dsp->dev, true); + } } return 0; } +static void rt5514_spi_pcm_remove(struct snd_soc_component *component) +{ + struct rt5514_dsp *rt5514_dsp = + snd_soc_component_get_drvdata(component); + + snd_soc_component_set_drvdata(component, NULL); + + if (rt5514_dsp->irq) { + free_irq(rt5514_dsp->irq, rt5514_dsp); + device_init_wakeup(rt5514_dsp->dev, false); + } + + cancel_delayed_work_sync(&rt5514_dsp->copy_work); + + mutex_destroy(&rt5514_dsp->dma_lock); + kfree(rt5514_dsp); +} + static int rt5514_spi_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { @@ -294,6 +314,7 @@ static int rt5514_spi_pcm_new(struct snd_soc_component *component, static const struct snd_soc_component_driver rt5514_spi_component = { .name = DRV_NAME, .probe = rt5514_spi_pcm_probe, + .remove = rt5514_spi_pcm_remove, .open = rt5514_spi_pcm_open, .hw_params = rt5514_spi_hw_params, .hw_free = rt5514_spi_hw_free, From 83701fdb2d8d644c547773791da48d6ecb3a5e1d Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Wed, 23 Sep 2026 01:40:17 -0400 Subject: [PATCH 320/924] ASoC: fsl_xcvr: free IRQ before canceling reset work irq0_isr() schedules work_rst on a preamble error, and reset_rx_work() touches regmap. remove() cancels that work while the IRQ is still registered, so the handler can queue it again during teardown. The IRQ is also requested before INIT_WORK() and spin_lock_init(). Probe failure does not call remove(), so freeing the IRQ leaves queued work running, and an earlier interrupt schedules uninitialized work. Initialize the lock and devm_work_autocancel() before the IRQ. Failed probe then frees the IRQ and cancels the work. Unbind frees the IRQ before cancel_work_sync() and pm_runtime_disable(). Fixes: 1e5d0f106164 ("ASoC: fsl_xcvr: reset RX dpath after wrong preamble") Cc: stable@vger.kernel.org # 6.13+ Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20260923054017.93553-1-mhun512@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_xcvr.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c index 982827204351..29929dfcce2a 100644 --- a/sound/soc/fsl/fsl_xcvr.c +++ b/sound/soc/fsl/fsl_xcvr.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -57,6 +58,7 @@ struct fsl_xcvr { struct snd_aes_iec958 tx_iec958; u8 cap_ds[FSL_XCVR_CAPDS_SIZE]; struct work_struct work_rst; + int irq; spinlock_t lock; /* Protect hw_reset and trigger */ struct snd_pcm_hw_constraint_list spdif_constr_rates; u32 spdif_constr_rates_list[SPDIF_NUM_RATES]; @@ -1617,7 +1619,7 @@ static int fsl_xcvr_probe(struct platform_device *pdev) struct fsl_xcvr *xcvr; struct resource *rx_res, *tx_res; void __iomem *regs; - int ret, irq; + int ret; xcvr = devm_kzalloc(dev, sizeof(*xcvr), GFP_KERNEL); if (!xcvr) @@ -1705,12 +1707,22 @@ static int fsl_xcvr_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(xcvr->reset), "failed to get XCVR reset control\n"); - /* get IRQs */ - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + /* + * irq0_isr() schedules work_rst. Prepare the work and its lock + * before the IRQ, and register the cancel action first so a failed + * probe frees the IRQ and then cancels any queued work. + */ + spin_lock_init(&xcvr->lock); + ret = devm_work_autocancel(dev, &xcvr->work_rst, reset_rx_work); + if (ret) + return ret; - ret = devm_request_irq(dev, irq, irq0_isr, 0, pdev->name, xcvr); + /* get IRQs */ + xcvr->irq = platform_get_irq(pdev, 0); + if (xcvr->irq < 0) + return xcvr->irq; + + ret = devm_request_irq(dev, xcvr->irq, irq0_isr, 0, pdev->name, xcvr); if (ret) return dev_err_probe(dev, ret, "failed to claim IRQ0\n"); @@ -1751,8 +1763,6 @@ static int fsl_xcvr_probe(struct platform_device *pdev) fsl_xcvr_comp.name); } - INIT_WORK(&xcvr->work_rst, reset_rx_work); - spin_lock_init(&xcvr->lock); return ret; } @@ -1760,6 +1770,8 @@ static void fsl_xcvr_remove(struct platform_device *pdev) { struct fsl_xcvr *xcvr = dev_get_drvdata(&pdev->dev); + /* Free the IRQ first so irq0_isr() cannot requeue work_rst. */ + devm_free_irq(&pdev->dev, xcvr->irq, xcvr); cancel_work_sync(&xcvr->work_rst); pm_runtime_disable(&pdev->dev); } From 3246de5077648a427eeb69ec970348774da5df68 Mon Sep 17 00:00:00 2001 From: Diogo Ivo Date: Wed, 23 Sep 2026 17:45:10 +0100 Subject: [PATCH 321/924] ASoC: dt-bindings: realtek,rt5677: Add MCLK1 clock property Add a clocks property so that boards can describe the clock feeding the codec's MCLK1 input. Signed-off-by: Diogo Ivo Link: https://patch.msgid.link/20260923-rt5677-mclk-v2-1-6ca31e8f68a4@tecnico.ulisboa.pt Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/realtek,rt5677.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/realtek,rt5677.yaml b/Documentation/devicetree/bindings/sound/realtek,rt5677.yaml index ae27ae78b1b2..f76aad0d3016 100644 --- a/Documentation/devicetree/bindings/sound/realtek,rt5677.yaml +++ b/Documentation/devicetree/bindings/sound/realtek,rt5677.yaml @@ -44,6 +44,14 @@ properties: '#gpio-cells': const: 2 + clocks: + items: + - description: phandle and clock specifier for the codec MCLK1 input. + + clock-names: + items: + - const: mclk1 + realtek,pow-ldo2-gpio: maxItems: 1 description: CODEC's POW_LDO2 pin. @@ -127,6 +135,8 @@ examples: interrupts = <3 IRQ_TYPE_LEVEL_HIGH>; gpio-controller; #gpio-cells = <2>; + clocks = <&osc>; + clock-names = "mclk1"; realtek,pow-ldo2-gpio = <&gpio 3 GPIO_ACTIVE_HIGH>; realtek,reset-gpio = <&gpio 3 GPIO_ACTIVE_LOW>; realtek,in1-differential; From db1761980ccc3b792dbd6e106c237d06108ea897 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Thu, 24 Sep 2026 13:40:15 +0800 Subject: [PATCH 322/924] ASoC: Intel: sof-function-topology-lib: fix sscanf type Change %d to %u as ssp_port is unsigned int. Fixes: 6736426f31e8 ("ASoC: Intel: sof-function-topology-lib: add I2S support for sof_sdw_get_tplg_files") Signed-off-by: Bard Liao Link: https://patch.msgid.link/20260924054015.2600784-1-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/common/sof-function-topology-lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/intel/common/sof-function-topology-lib.c b/sound/soc/intel/common/sof-function-topology-lib.c index 8f4fdf82d00f..57e089f8e721 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.c +++ b/sound/soc/intel/common/sof-function-topology-lib.c @@ -121,7 +121,7 @@ static int get_ssp_tplg_dev(struct device *dev, struct snd_soc_dai_link *dai_lin { unsigned int ssp_port; - if (sscanf(dai_link->name, "SSP%d", &ssp_port) != 1) { + if (sscanf(dai_link->name, "SSP%u", &ssp_port) != 1) { dev_err(dev, "Can't get SSP port from dai_link->name %s\n", dai_link->name); return -EINVAL; } From 92871abc1302b852b88d86114115f8fd6b50b9b9 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 16 Sep 2026 17:40:42 +0800 Subject: [PATCH 323/924] ASoC: fsl_asrc: use regmap_write_bits to update clock source registers After a warm reboot the REG_ASRCSR register may not be in its hardware reset state. regmap caches the register value as 0 (the assumed default), so when the desired clock source index is also 0, regmap_update_bits() skips the actual hardware write because it believes the register already holds the correct value. This leaves a stale clock source in the hardware. Replace regmap_update_bits() with regmap_write_bits() so that the masked fields are always written to hardware regardless of the cached value. Fixes: 3117bb3109dc ("ASoC: fsl_asrc: Add ASRC ASoC CPU DAI and platform drivers") Signed-off-by: Shengjiu Wang Reviewed-by: Chancel Liu Link: https://patch.msgid.link/20260916094043.1496709-1-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index ae682bf450f6..cc304abbc8bd 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -521,10 +521,10 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair, bool use_ideal_rate) ASRCTR_USR(index)); /* Set the input and output clock sources */ - regmap_update_bits(asrc->regmap, REG_ASRCSR, - ASRCSR_AICSi_MASK(index) | ASRCSR_AOCSi_MASK(index), - ASRCSR_AICS(index, clk_index[IN]) | - ASRCSR_AOCS(index, clk_index[OUT])); + regmap_write_bits(asrc->regmap, REG_ASRCSR, + ASRCSR_AICSi_MASK(index) | ASRCSR_AOCSi_MASK(index), + ASRCSR_AICS(index, clk_index[IN]) | + ASRCSR_AOCS(index, clk_index[OUT])); /* Calculate the input clock divisors */ indiv = fsl_asrc_cal_asrck_divisor(pair, div[IN]); From eb169cc54b87fc2c633c2343c6060c5955efb69d Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Thu, 24 Sep 2026 10:05:10 +0800 Subject: [PATCH 324/924] ASoC: rt1320: make SRAM registers readable to stop being cached rt1320_volatile_register() marks the SRAM ranges 0x10000000-0x10008fff and 0x1000c000-0x1000dfff as volatile, but regmap only honors that flag for a readable register and these ranges were missing from rt1320_readable_register(). They were therefore cached at probe and replayed over SoundWire on every runtime resume, delaying the first PCM open after autosuspend by about a second. Make the ranges readable so the volatile flag takes effect and the SRAM is no longer cached. Exclude 0x1000d000-0x1000d7ff from both callbacks, since rt1320_rae_load() deliberately caches that tuning-tool window. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20260924020510.3551163-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1320-sdw.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index b13622e54ae0..fa89db096651 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -890,8 +890,9 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) case 0xf01e: case 0xf717 ... 0xf719: case 0xf720 ... 0xf723: - case 0x1000cd91 ... 0x1000cd96: - case RT1321_PATCH_MAIN_VER ... RT1321_PATCH_BETA_VER: + case 0x10000000 ... 0x10008fff: + case 0x1000c000 ... 0x1000cfff: + case 0x1000d800 ... 0x1000dfff: case 0x1000f008: case 0x1000f021: case 0x20003000 ... 0x2000300f: @@ -996,7 +997,8 @@ static bool rt1320_volatile_register(struct device *dev, unsigned int reg) case 0xf717 ... 0xf719: case 0xf720 ... 0xf723: case 0x10000000 ... 0x10008fff: - case 0x1000c000 ... 0x1000dfff: + case 0x1000c000 ... 0x1000cfff: + case 0x1000d800 ... 0x1000dfff: case 0x1000f008: case 0x1000f021: case 0x2000300e ... 0x2000300f: From 7cb06353a20337598eff3e645b5454ee71d81937 Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Fri, 18 Sep 2026 19:01:12 +0530 Subject: [PATCH 325/924] ASoC: dt-bindings: qcom: add QAIF AIF MI2S and TDM dai ids Add new dai ids entries for Qualcomm Audio Interface (QAIF) AIF MI2S and TDM audio lines. Co-developed-by: Harendra Gautam Signed-off-by: Harendra Gautam Signed-off-by: Mohammad Rafi Shaik Reviewed-by: Prasad Kumpatla Tested-by: Prasad Kumpatla Link: https://patch.msgid.link/20260918-qaif_dai_id_support-v1-1-ed863c0e5e45@oss.qualcomm.com Signed-off-by: Mark Brown --- .../sound/qcom,q6dsp-lpass-ports.yaml | 16 ++++-- .../sound/qcom,q6dsp-lpass-ports.h | 52 +++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml b/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml index 3b03e2acd67e..63f53837bd9f 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml @@ -108,10 +108,15 @@ patternProperties: properties: reg: contains: - # TDM DAI ID range from PRIMARY_TDM_RX_0 - QUINARY_TDM_TX_7 + # TDM DAI ID range from PRIMARY_TDM_RX_0 - QUINARY_TDM_TX_7 and + # AIF_TDM_RX_0 - AIF_TDM_TX_12 items: - minimum: 24 - maximum: 103 + oneOf: + - minimum: 24 + maximum: 103 + - minimum: 179 + maximum: 204 + then: required: - qcom,tdm-sync-mode @@ -127,7 +132,8 @@ patternProperties: contains: # MI2S DAI ID range PRIMARY_MI2S_RX - QUATERNARY_MI2S_TX and # QUINARY_MI2S_RX - QUINARY_MI2S_TX and - # LPI_MI2S_RX_0 - LPI_MI2S_TX_6 + # LPI_MI2S_RX_0 - LPI_MI2S_TX_6 and + # AIF_MI2S_RX_0 - AIF_MI2S_TX_12 items: oneOf: - minimum: 16 @@ -136,6 +142,8 @@ patternProperties: maximum: 128 - minimum: 137 maximum: 152 + - minimum: 153 + maximum: 178 then: required: - qcom,sd-lines diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h index 3a99703dbc4a..e6c792d9312f 100644 --- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h @@ -156,6 +156,58 @@ #define LPI_MI2S_TX_5 150 #define LPI_MI2S_RX_6 151 #define LPI_MI2S_TX_6 152 +#define AIF_MI2S_RX_0 153 +#define AIF_MI2S_TX_0 154 +#define AIF_MI2S_RX_1 155 +#define AIF_MI2S_TX_1 156 +#define AIF_MI2S_RX_2 157 +#define AIF_MI2S_TX_2 158 +#define AIF_MI2S_RX_3 159 +#define AIF_MI2S_TX_3 160 +#define AIF_MI2S_RX_4 161 +#define AIF_MI2S_TX_4 162 +#define AIF_MI2S_RX_5 163 +#define AIF_MI2S_TX_5 164 +#define AIF_MI2S_RX_6 165 +#define AIF_MI2S_TX_6 166 +#define AIF_MI2S_RX_7 167 +#define AIF_MI2S_TX_7 168 +#define AIF_MI2S_RX_8 169 +#define AIF_MI2S_TX_8 170 +#define AIF_MI2S_RX_9 171 +#define AIF_MI2S_TX_9 172 +#define AIF_MI2S_RX_10 173 +#define AIF_MI2S_TX_10 174 +#define AIF_MI2S_RX_11 175 +#define AIF_MI2S_TX_11 176 +#define AIF_MI2S_RX_12 177 +#define AIF_MI2S_TX_12 178 +#define AIF_TDM_RX_0 179 +#define AIF_TDM_TX_0 180 +#define AIF_TDM_RX_1 181 +#define AIF_TDM_TX_1 182 +#define AIF_TDM_RX_2 183 +#define AIF_TDM_TX_2 184 +#define AIF_TDM_RX_3 185 +#define AIF_TDM_TX_3 186 +#define AIF_TDM_RX_4 187 +#define AIF_TDM_TX_4 188 +#define AIF_TDM_RX_5 189 +#define AIF_TDM_TX_5 190 +#define AIF_TDM_RX_6 191 +#define AIF_TDM_TX_6 192 +#define AIF_TDM_RX_7 193 +#define AIF_TDM_TX_7 194 +#define AIF_TDM_RX_8 195 +#define AIF_TDM_TX_8 196 +#define AIF_TDM_RX_9 197 +#define AIF_TDM_TX_9 198 +#define AIF_TDM_RX_10 199 +#define AIF_TDM_TX_10 200 +#define AIF_TDM_RX_11 201 +#define AIF_TDM_TX_11 202 +#define AIF_TDM_RX_12 203 +#define AIF_TDM_TX_12 204 #define LPASS_CLK_ID_PRI_MI2S_IBIT 1 #define LPASS_CLK_ID_PRI_MI2S_EBIT 2 From 897be15f03b18d63ed5614b7b0ff1b379681e61a Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Fri, 18 Sep 2026 19:01:13 +0530 Subject: [PATCH 326/924] ASoC: qcom: qdsp6: lpass-ports: add support for QAIF AIF MI2S and TDM dais Add support for Qualcomm Audio Interface (QAIF) AIF MI2S and TDM dais in the dai-driver, these dais are used in Shikra, Hawi and Nord based Qualcomm platform devices. Signed-off-by: Mohammad Rafi Shaik Reviewed-by: Prasad Kumpatla Tested-by: Prasad Kumpatla Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260918-qaif_dai_id_support-v1-2-ed863c0e5e45@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-lpass-dais.c | 2 + sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 54 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index e204fd59e512..83f2711bceea 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -386,6 +386,7 @@ static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai, switch (dai->id) { case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + case AIF_TDM_RX_0 ... AIF_TDM_TX_12: slot_mask = (dai->id & 0x1) ? tx_mask : rx_mask; if (slot_mask & ~cap_mask) { dev_err(dai->dev, "%s: invalid slot mask 0x%x for %d slots\n", @@ -474,6 +475,7 @@ static int of_q6apm_parse_dai_data(struct device *dev, case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: case SENARY_MI2S_RX ... SENARY_MI2S_TX: case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + case AIF_MI2S_RX_0 ... AIF_TDM_TX_12: priv = &data->priv[id]; priv->mclk = of_clk_get_by_name(node, "mclk"); if (IS_ERR(priv->mclk)) { diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c index c3d8116ad503..f422346a50c6 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c @@ -548,6 +548,32 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { .id = SENARY_MI2S_TX, .name = "SEN_MI2S_TX", }, + Q6AFE_MI2S_RX_DAI("AIF RX0", AIF_MI2S_RX_0), + Q6AFE_MI2S_RX_DAI("AIF RX1", AIF_MI2S_RX_1), + Q6AFE_MI2S_RX_DAI("AIF RX2", AIF_MI2S_RX_2), + Q6AFE_MI2S_RX_DAI("AIF RX3", AIF_MI2S_RX_3), + Q6AFE_MI2S_RX_DAI("AIF RX4", AIF_MI2S_RX_4), + Q6AFE_MI2S_RX_DAI("AIF RX5", AIF_MI2S_RX_5), + Q6AFE_MI2S_RX_DAI("AIF RX6", AIF_MI2S_RX_6), + Q6AFE_MI2S_RX_DAI("AIF RX7", AIF_MI2S_RX_7), + Q6AFE_MI2S_RX_DAI("AIF RX8", AIF_MI2S_RX_8), + Q6AFE_MI2S_RX_DAI("AIF RX9", AIF_MI2S_RX_9), + Q6AFE_MI2S_RX_DAI("AIF RX10", AIF_MI2S_RX_10), + Q6AFE_MI2S_RX_DAI("AIF RX11", AIF_MI2S_RX_11), + Q6AFE_MI2S_RX_DAI("AIF RX12", AIF_MI2S_RX_12), + Q6AFE_MI2S_TX_DAI("AIF TX0", AIF_MI2S_TX_0), + Q6AFE_MI2S_TX_DAI("AIF TX1", AIF_MI2S_TX_1), + Q6AFE_MI2S_TX_DAI("AIF TX2", AIF_MI2S_TX_2), + Q6AFE_MI2S_TX_DAI("AIF TX3", AIF_MI2S_TX_3), + Q6AFE_MI2S_TX_DAI("AIF TX4", AIF_MI2S_TX_4), + Q6AFE_MI2S_TX_DAI("AIF TX5", AIF_MI2S_TX_5), + Q6AFE_MI2S_TX_DAI("AIF TX6", AIF_MI2S_TX_6), + Q6AFE_MI2S_TX_DAI("AIF TX7", AIF_MI2S_TX_7), + Q6AFE_MI2S_TX_DAI("AIF TX8", AIF_MI2S_TX_8), + Q6AFE_MI2S_TX_DAI("AIF TX9", AIF_MI2S_TX_9), + Q6AFE_MI2S_TX_DAI("AIF TX10", AIF_MI2S_TX_10), + Q6AFE_MI2S_TX_DAI("AIF TX11", AIF_MI2S_TX_11), + Q6AFE_MI2S_TX_DAI("AIF TX12", AIF_MI2S_TX_12), Q6AFE_MI2S_RX_DAI("LPI RX0", LPI_MI2S_RX_0), Q6AFE_MI2S_RX_DAI("LPI RX1", LPI_MI2S_RX_1), Q6AFE_MI2S_RX_DAI("LPI RX2", LPI_MI2S_RX_2), @@ -562,6 +588,32 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { Q6AFE_MI2S_TX_DAI("LPI TX4", LPI_MI2S_TX_4), Q6AFE_MI2S_TX_DAI("LPI TX5", LPI_MI2S_TX_5), Q6AFE_MI2S_TX_DAI("LPI TX6", LPI_MI2S_TX_6), + Q6AFE_TDM_PB_DAI("AIF RX0", 0, AIF_TDM_RX_0), + Q6AFE_TDM_PB_DAI("AIF RX1", 1, AIF_TDM_RX_1), + Q6AFE_TDM_PB_DAI("AIF RX2", 2, AIF_TDM_RX_2), + Q6AFE_TDM_PB_DAI("AIF RX3", 3, AIF_TDM_RX_3), + Q6AFE_TDM_PB_DAI("AIF RX4", 4, AIF_TDM_RX_4), + Q6AFE_TDM_PB_DAI("AIF RX5", 5, AIF_TDM_RX_5), + Q6AFE_TDM_PB_DAI("AIF RX6", 6, AIF_TDM_RX_6), + Q6AFE_TDM_PB_DAI("AIF RX7", 7, AIF_TDM_RX_7), + Q6AFE_TDM_PB_DAI("AIF RX8", 8, AIF_TDM_RX_8), + Q6AFE_TDM_PB_DAI("AIF RX9", 9, AIF_TDM_RX_9), + Q6AFE_TDM_PB_DAI("AIF RX10", 10, AIF_TDM_RX_10), + Q6AFE_TDM_PB_DAI("AIF RX11", 11, AIF_TDM_RX_11), + Q6AFE_TDM_PB_DAI("AIF RX12", 12, AIF_TDM_RX_12), + Q6AFE_TDM_CAP_DAI("AIF TX0", 0, AIF_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("AIF TX1", 1, AIF_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("AIF TX2", 2, AIF_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("AIF TX3", 3, AIF_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("AIF TX4", 4, AIF_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("AIF TX5", 5, AIF_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("AIF TX6", 6, AIF_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("AIF TX7", 7, AIF_TDM_TX_7), + Q6AFE_TDM_CAP_DAI("AIF TX8", 8, AIF_TDM_TX_8), + Q6AFE_TDM_CAP_DAI("AIF TX9", 9, AIF_TDM_TX_9), + Q6AFE_TDM_CAP_DAI("AIF TX10", 10, AIF_TDM_TX_10), + Q6AFE_TDM_CAP_DAI("AIF TX11", 11, AIF_TDM_TX_11), + Q6AFE_TDM_CAP_DAI("AIF TX12", 12, AIF_TDM_TX_12), Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0), Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1), Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2), @@ -717,9 +769,11 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + case AIF_MI2S_RX_0 ... AIF_MI2S_TX_12: q6dsp_audio_fe_dais[i].ops = cfg->q6i2s_ops; break; case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + case AIF_TDM_RX_0 ... AIF_TDM_TX_12: q6dsp_audio_fe_dais[i].ops = cfg->q6tdm_ops; break; case WSA_CODEC_DMA_RX_0 ... RX_CODEC_DMA_RX_7: From 654d53e6901f0610580239ce88730a406422981d Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Fri, 18 Sep 2026 19:01:14 +0530 Subject: [PATCH 327/924] ASoC: qcom: sc8280xp: Handle AIF MI2S and TDM interfaces Handle AIF MI2S and TDM DAI IDs in the SC8280XP machine driver and extend LPASS_MAX_PORT to accommodate the additional audio interfaces. Signed-off-by: Mohammad Rafi Shaik Reviewed-by: Prasad Kumpatla Tested-by: Prasad Kumpatla Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260918-qaif_dai_id_support-v1-3-ed863c0e5e45@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/common.h | 2 +- sound/soc/qcom/sc8280xp.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h index c1deac109f24..18171656408c 100644 --- a/sound/soc/qcom/common.h +++ b/sound/soc/qcom/common.h @@ -7,7 +7,7 @@ #include #include -#define LPASS_MAX_PORT (LPI_MI2S_TX_6 + 1) +#define LPASS_MAX_PORT (AIF_TDM_TX_12 + 1) struct qcom_snd_tdm_slot_cfg { unsigned int tx_mask; diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 4d48e1012cd4..0876b9be136d 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -260,6 +260,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: case SENARY_MI2S_RX ... SENARY_MI2S_TX: case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: + case AIF_MI2S_RX_0 ... AIF_MI2S_TX_12: ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); if (ret && ret != -ENOTSUPP) return ret; @@ -296,6 +297,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, } break; case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + case AIF_TDM_RX_0 ... AIF_TDM_TX_12: return sc8280xp_tdm_hw_params(substream, params); default: break; From dcc36d766dc82bc57cd730915d484119befcfca8 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Thu, 24 Sep 2026 21:49:32 +0800 Subject: [PATCH 328/924] ASoC: Intel: atom: Fix platform device leak on probe failure sst_acpi_probe() registers platform devices for the SST platform and machine board using platform_device_register_data(), but does not unregister them when later probe steps fail. Fixes: caf94ed8629a ("ASoC: Intel: bytcr_rt5640: fixup DAI codec_name with HID") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260924134932.1590967-1-lgs201920130244@gmail.com Signed-off-by: Mark Brown --- sound/soc/intel/atom/sst/sst_acpi.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c index 73624e1b138a..de1ad168008b 100644 --- a/sound/soc/intel/atom/sst/sst_acpi.c +++ b/sound/soc/intel/atom/sst/sst_acpi.c @@ -254,6 +254,11 @@ static int sst_platform_get_resources(struct intel_sst_drv *ctx) return 0; } +static void sst_unregister_platform_device(void *data) +{ + platform_device_unregister(data); +} + static int sst_acpi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -342,7 +347,10 @@ static int sst_acpi_probe(struct platform_device *pdev) pdata->platform); return PTR_ERR(plat_dev); } - + ret = devm_add_action_or_reset(dev, sst_unregister_platform_device, + plat_dev); + if (ret) + return ret; /* * Create platform device for sst machine driver, * pass machine info as pdata @@ -355,6 +363,10 @@ static int sst_acpi_probe(struct platform_device *pdev) return PTR_ERR(mdev); } + ret = devm_add_action_or_reset(dev, sst_unregister_platform_device, + mdev); + if (ret) + return ret; /* Fill sst platform data */ ctx->pdata = pdata; strscpy(ctx->firmware_name, mach->fw_filename); From 8eb2b0c882866c4c3f5622366a57481bb2dc494c Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 24 Sep 2026 13:53:03 +0800 Subject: [PATCH 329/924] ASoC: sdw_utils: Add rt766/rt767 support This patch adds the codec_info and route-related settings. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260924-rt766-v2-2-e0b63606a5e8@realtek.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c | 6 + .../sdw_utils/soc_sdw_rt_sdca_jack_common.c | 8 + sound/soc/sdw_utils/soc_sdw_utils.c | 172 ++++++++++++++++++ 3 files changed, 186 insertions(+) diff --git a/sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c b/sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c index 5bf3627a97a0..6e5fa93b5fc2 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c +++ b/sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c @@ -33,6 +33,11 @@ static const struct snd_soc_dapm_route rt722_spk_map[] = { { "Speaker", NULL, "rt722 SPK" }, }; +static const struct snd_soc_dapm_route rt766_spk_map[] = { + { "Speaker", NULL, "rt766 SPOL" }, + { "Speaker", NULL, "rt766 SPOR" }, +}; + /* Structure to map codec names to respective route arrays and sizes */ struct codec_route_map { const char *codec_name; @@ -45,6 +50,7 @@ static const struct codec_route_map codec_routes[] = { { "rt712", rt712_spk_map, ARRAY_SIZE(rt712_spk_map) }, { "rt721", rt721_spk_map, ARRAY_SIZE(rt721_spk_map) }, { "rt722", rt722_spk_map, ARRAY_SIZE(rt722_spk_map) }, + { "rt766", rt766_spk_map, ARRAY_SIZE(rt766_spk_map) }, }; static const struct codec_route_map *get_codec_route_map(const char *codec_name) diff --git a/sound/soc/sdw_utils/soc_sdw_rt_sdca_jack_common.c b/sound/soc/sdw_utils/soc_sdw_rt_sdca_jack_common.c index 2547b5b3fdd7..b4e427d55130 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt_sdca_jack_common.c +++ b/sound/soc/sdw_utils/soc_sdw_rt_sdca_jack_common.c @@ -70,6 +70,11 @@ static const struct snd_soc_dapm_route rt722_sdca_map[] = { { "rt722 MIC2", NULL, "Headset Mic" }, }; +static const struct snd_soc_dapm_route rt766_sdca_map[] = { + { "Headphone", NULL, "rt766 HP" }, + { "rt766 MIC2", NULL, "Headset Mic" }, +}; + static struct snd_soc_jack_pin rt_sdca_jack_pins[] = { { .pin = "Headphone", @@ -133,6 +138,9 @@ int asoc_sdw_rt_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_s } else if (strstr(component->name_prefix, "rt722")) { ret = snd_soc_dapm_add_routes(dapm, rt722_sdca_map, ARRAY_SIZE(rt722_sdca_map)); + } else if (strstr(component->name_prefix, "rt766")) { + ret = snd_soc_dapm_add_routes(dapm, rt766_sdca_map, + ARRAY_SIZE(rt766_sdca_map)); } else { dev_err(card->dev, "%s is not supported\n", component->name_prefix); return -EINVAL; diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index f0f0822d8ed6..14aee10b40ba 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -779,6 +779,178 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, .dai_num = 3, }, + { + .vendor_id = 0x025d, + .part_id = 0x767, + .name_prefix = "rt766", + .version_id = 3, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt766-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt766-sdca-aif2", + .component_name = "rt766", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt766-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x025d, + .part_id = 0x767, + .name_prefix = "rt766", + .version_id = 4, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt766-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt766-sdca-aif2", + .component_name = "rt766", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt766-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x025d, + .part_id = 0x766, + .name_prefix = "rt766", + .version_id = 3, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt766-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt766-sdca-aif2", + .component_name = "rt766", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt766-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x025d, + .part_id = 0x766, + .name_prefix = "rt766", + .version_id = 4, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt766-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt766-sdca-aif2", + .component_name = "rt766", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt766-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, + }, + .dai_num = 3, + }, { .vendor_id = 0x019f, .part_id = 0x8373, From be190d999369fad147e94124cbab0b28583496e7 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 24 Sep 2026 13:53:04 +0800 Subject: [PATCH 330/924] ASoC: rt766: avoid re-initialize the blind write We only need to check the function_status to determine whether the system has undergone a cold or warm reboot. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260924-rt766-v2-3-e0b63606a5e8@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt766-sdca.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c index 5a1c1e10e6d7..28b4118e05c9 100644 --- a/sound/soc/codecs/rt766-sdca.c +++ b/sound/soc/codecs/rt766-sdca.c @@ -1297,7 +1297,7 @@ static int rt766_func_initialize(struct rt766_sdca_priv *rt766, struct sdca_func regmap_read(rt766->regmap, func_status_reg, &func_status); dev_dbg(dev, "%s, %s func_status=0x%x\n", __func__, func_data->desc->name, func_status); - if ((func_status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) || (!rt766->first_hw_init)) { + if ((func_status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION)) { ret = sdca_regmap_write_init(dev, rt766->regmap, func_data); if (ret) { dev_err(dev, "%s initialization table update failed\n", func_data->desc->name); From d06607bafb36abc14ed34c8adfb1239bebcd83d1 Mon Sep 17 00:00:00 2001 From: Syed Saba Kareem Date: Thu, 24 Sep 2026 18:11:52 +0530 Subject: [PATCH 331/924] ASoC: amd: acp: fix TAS2783 SoundWire codec unmet dependencies SND_SOC_AMD_LEGACY_SDW_MACH unconditionally selects SND_SOC_TAS2783_SDW, but that codec depends on SND_SOC_SDCA and EFI in addition to SOUNDWIRE. An unconditional select force-enables the codec even when those dependencies are not met, which the kernel test robot reported as unmet direct dependencies when EFI=n: WARNING: unmet direct dependencies detected for SND_SOC_TAS2783_SDW Depends on [n]: SOUND [=y] && SND [=y] && SND_SOC [=y] && SOUNDWIRE [=y] && SND_SOC_SDCA [=y] && EFI [=n] Selected by [y]: - SND_SOC_AMD_LEGACY_SDW_MACH [=y] && ... The other SoundWire codecs selected by this machine driver only depend on SOUNDWIRE, which the driver already depends on, so they select cleanly. SND_SOC_TAS2783_SDW is the only one that additionally needs the SDCA core and EFI (it reads speaker calibration data from EFI variables). SND_SOC_SDCA cannot simply be selected here, as that introduces a recursive Kconfig dependency (SOUNDWIRE depends on SND_SOC_SDCA_OPTIONAL, whose default pulls in SND_SOC_SDCA), and EFI is a platform feature that must not be selected by a driver. Instead, guard the select with the missing dependencies so the codec is only (and always) built when they are satisfied: select SND_SOC_TAS2783_SDW if SND_SOC_SDCA && EFI This keeps the TAS2783 SoundWire codec built together with the machine driver on platforms that can use it, while avoiding the unmet dependency when SND_SOC_SDCA or EFI is disabled. Fixes: 28114992dd2f ("ASoC: amd: acp: enable TAS2783 and add RT712-VB SoundWire machine") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609182147.gfofWIXW-lkp@intel.com/ Signed-off-by: Syed Saba Kareem Link: https://patch.msgid.link/20260924124205.3376083-1-syed.sabakareem@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig index e880e44b9763..6433876430f0 100644 --- a/sound/soc/amd/acp/Kconfig +++ b/sound/soc/amd/acp/Kconfig @@ -181,7 +181,7 @@ config SND_SOC_AMD_LEGACY_SDW_MACH select SND_SOC_RT715_SDW select SND_SOC_RT715_SDCA_SDW select SND_SOC_RT722_SDCA_SDW - select SND_SOC_TAS2783_SDW + select SND_SOC_TAS2783_SDW if SND_SOC_SDCA && EFI help This option enables Legacy(No DSP) sound card support for SoundWire enabled AMD platforms along with ACP PDM controller. From 0bc42544a4a06e8d76eb412d2b1414db910a311d Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Sun, 20 Sep 2026 10:42:32 +0800 Subject: [PATCH 332/924] ASoC: rt712-sdca-dmic: fix drvdata type in the gain controls rt712_sdca_dmic_set_gain_get() and rt712_sdca_dmic_set_gain_put() take the component drvdata as struct rt712_sdca_priv, but this driver stores a struct rt712_sdca_dmic_priv. regmap and mbq_regmap are at the same offsets in both structs, so the register accesses work. slave is not: rt712_sdca_priv has dmic_component in front of it, so its slave overlays rt712_sdca_dmic_priv's params, which this driver never writes. When regmap_write() fails in the put handler, dev_err() is called with &NULL->dev (RSI below): rt712-sdca-dmic sdw:0:3:025d:1713:01: Defer on undeferrable control: 40800f13 BUG: kernel NULL pointer dereference, address: 0000000000000058 RIP: 0010:__dev_printk+0x10/0x70 RDX: ffffcdac45f83cd0 RSI: 0000000000000008 RDI: ffffffffa09cb5cb Call Trace: _dev_err+0x7f/0x99 rt712_sdca_dmic_set_gain_put.cold+0x20/0x25 [snd_soc_rt712_sdca_dmic] snd_ctl_elem_write+0x19a/0x1f0 [snd] snd_ctl_ioctl+0x658/0x8a0 [snd] Seen on a Dell Precision 5690 running 7.2.5-200.fc44, with alsactl writing the control during boot. Compile-tested only. Fixes: 63a511284c9e ("ASoC: rt712-sdca: Add RT712 SDCA driver for Mic topology") Closes: https://bugzilla.redhat.com/show_bug.cgi?id=2532834 Cc: stable@vger.kernel.org Signed-off-by: Junjie Cao Link: https://patch.msgid.link/20260920024232.710189-1-junjie.cao@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca-dmic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c index a9f3aa4e143a..882390890738 100644 --- a/sound/soc/codecs/rt712-sdca-dmic.c +++ b/sound/soc/codecs/rt712-sdca-dmic.c @@ -246,7 +246,7 @@ static int rt712_sdca_dmic_set_gain_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component); + struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component); struct rt712_sdca_dmic_kctrl_priv *p = (struct rt712_sdca_dmic_kctrl_priv *)kcontrol->private_value; unsigned int regvalue, ctl, i; @@ -277,7 +277,7 @@ static int rt712_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol, struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct rt712_sdca_dmic_kctrl_priv *p = (struct rt712_sdca_dmic_kctrl_priv *)kcontrol->private_value; - struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component); + struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component); unsigned int gain_val[4]; unsigned int i, adc_vol_flag = 0, changed = 0; unsigned int regvalue[4]; From 55c9c04e847fafbf319e7053993959eb3c3a9f13 Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Fri, 4 Sep 2026 18:02:25 +0800 Subject: [PATCH 333/924] ASoC: dt-bindings: es8316: Document jack detect inversion The ES8316 driver supports the everest,jack-detect-inverted property to account for boards which invert the codec jack-detect signal. However, the property is not described by the binding, so a valid user of the driver fails schema validation because the binding rejects unknown properties. Document the flag so boards can describe the jack-detect polarity used by their wiring. Acked-by: Krzysztof Kozlowski Signed-off-by: Hongyang Zhao Link: https://patch.msgid.link/20260904-rubikpi-next-20260605-v3-1-f49146d85af3@thundersoft.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/everest,es8316.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/everest,es8316.yaml b/Documentation/devicetree/bindings/sound/everest,es8316.yaml index f4ff23120c5b..9060358a9080 100644 --- a/Documentation/devicetree/bindings/sound/everest,es8316.yaml +++ b/Documentation/devicetree/bindings/sound/everest,es8316.yaml @@ -84,6 +84,11 @@ properties: "#sound-dai-cells": const: 0 + everest,jack-detect-inverted: + $ref: /schemas/types.yaml#/definitions/flag + description: + Defined to invert the jack detection. + required: - compatible - reg From f6a447b32a3995c09ffca38a9b3adb9bf833cf50 Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Fri, 4 Sep 2026 18:02:26 +0800 Subject: [PATCH 334/924] ASoC: dt-bindings: qcom,sm8250: Add RubikPi 3 sound card The QCS6490-based Thundercomm RubikPi 3 routes primary MI2S to an external ES8316 headset codec and quaternary MI2S to the LT9611 HDMI bridge. This requires board-specific DAI clocking and jack setup in the sc8280xp machine driver. The existing QCM6490 and QCS6490 compatibles select machine data for boards using different codec and audio routing arrangements, so they cannot be used as compatible fallbacks for RubikPi 3. Add a dedicated compatible to select the RubikPi 3 machine data. Signed-off-by: Hongyang Zhao Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260904-rubikpi-next-20260605-v3-2-f49146d85af3@thundersoft.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/qcom,sm8250.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml index 1536fcd96d68..fc7bccdace0f 100644 --- a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml @@ -54,6 +54,7 @@ properties: - qcom,sm8250-sndcard - qcom,sm8450-sndcard - qcom,x1e80100-sndcard + - thundercomm,qcs6490-rubikpi3-sndcard audio-routing: $ref: /schemas/types.yaml#/definitions/non-unique-string-array From 7f8dd07370f8c17f8f98f0fa98691635828b0dac Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Fri, 4 Sep 2026 18:02:27 +0800 Subject: [PATCH 335/924] ASoC: qcom: common: Add generic headset jack helpers qcom_snd_wcd_jack_setup() combines creation of the card-level headset jack with the WCD-specific operation of attaching jack detection to codecs on the TX codec DMA links. External codecs connected over MI2S also provide component jack detection, but cannot use the WCD-specific DAI filtering. Factor the common jack allocation, DAPM pin registration and headset button mappings into a private initializer. Reuse it from the existing WCD path and add a generic setup helper which attaches the jack to every codec component in a runtime. Treat -ENOTSUPP as a no-op because snd_soc_component_set_jack() uses it for components which do not provide a set_jack callback. Add a matching cleanup helper so machine drivers can detach component jack detection when the DAI link exits. The WCD setup behavior remains unchanged. Signed-off-by: Hongyang Zhao Link: https://patch.msgid.link/20260904-rubikpi-next-20260605-v3-3-f49146d85af3@thundersoft.com Signed-off-by: Mark Brown --- sound/soc/qcom/common.c | 61 +++++++++++++++++++++++++++++++++++------ sound/soc/qcom/common.h | 3 ++ 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index d9f256d51973..83b745f617a8 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -339,13 +339,11 @@ static struct snd_soc_jack_pin qcom_headset_jack_pins[] = { }, }; -int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, - struct snd_soc_jack *jack, bool *jack_setup) +static int qcom_snd_headset_jack_init(struct snd_soc_card *card, + struct snd_soc_jack *jack, + bool *jack_setup) { - 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 snd_soc_card *card = rtd->card; - int rval, i; + int rval; if (!*jack_setup) { rval = snd_soc_card_jack_new_pins(card, "Headset Jack", @@ -369,6 +367,55 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, *jack_setup = true; } + return 0; +} + +int qcom_snd_headset_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup) +{ + struct snd_soc_dai *codec_dai; + struct snd_soc_card *card = rtd->card; + int rval, i; + + rval = qcom_snd_headset_jack_init(card, jack, jack_setup); + if (rval) + return rval; + + 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); + qcom_snd_headset_jack_cleanup(rtd); + return rval; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_snd_headset_jack_setup); + +void qcom_snd_headset_jack_cleanup(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai; + int i; + + for_each_rtd_codec_dais(rtd, i, codec_dai) + snd_soc_component_set_jack(codec_dai->component, NULL, NULL); +} +EXPORT_SYMBOL_GPL(qcom_snd_headset_jack_cleanup); + +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 = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + int rval, i; + + rval = qcom_snd_headset_jack_init(card, jack, jack_setup); + if (rval) + return rval; + switch (cpu_dai->id) { case LPI_MI2S_RX_0: case TX_CODEC_DMA_TX_0: @@ -388,8 +435,6 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, default: break; } - - return 0; } EXPORT_SYMBOL_GPL(qcom_snd_wcd_jack_setup); diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h index 18171656408c..37b185a4968f 100644 --- a/sound/soc/qcom/common.h +++ b/sound/soc/qcom/common.h @@ -24,6 +24,9 @@ int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, const struct qcom_snd_tdm_slot_cfg *cpu_cfg, const struct qcom_snd_tdm_slot_cfg *codec_cfg); int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd); +int qcom_snd_headset_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup); +void qcom_snd_headset_jack_cleanup(struct snd_soc_pcm_runtime *rtd); int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, struct snd_soc_jack *jack, bool *jack_setup); int qcom_snd_dp_jack_setup(struct snd_soc_pcm_runtime *rtd, From ea6e98472c65a713421ac9bdaa13834073437844 Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Fri, 4 Sep 2026 18:02:28 +0800 Subject: [PATCH 336/924] ASoC: qcom: sc8280xp: Add per-DAI board configuration The board data currently applies codec sysclk, MI2S clock and jack setup policy to every backend DAI on a sound card. This cannot describe a card whose codecs have different clock requirements on different MI2S interfaces. Add an optional per-DAI configuration table indexed by CPU DAI ID. Each entry can override the MCLK rate, CPU MCLK and BCLK programming, codec sysclk setup and jack setup path. Keep the codec DAI format card-wide. Cards without a matching per-DAI entry continue to use the existing card-wide settings, keeping all current board data unchanged. Signed-off-by: Hongyang Zhao Link: https://patch.msgid.link/20260904-rubikpi-next-20260605-v3-4-f49146d85af3@thundersoft.com Signed-off-by: Mark Brown --- sound/soc/qcom/sc8280xp.c | 107 ++++++++++++++++++++++++++++++++++---- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 0876b9be136d..58b4feb2ef3e 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -58,6 +58,25 @@ static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = { SND_SOC_DAPM_SPK("Speaker", NULL), }; +enum sc8280xp_jack_setup { + SC8280XP_JACK_SETUP_NONE, + SC8280XP_JACK_SETUP_CODEC, + SC8280XP_JACK_SETUP_WCD, +}; + +struct sc8280xp_dai_data { + unsigned int id; + unsigned int mclk_rate; + bool codec_sysclk_set; + bool mi2s_mclk_enable; + bool mi2s_bclk_enable; + enum sc8280xp_jack_setup jack_setup; +}; + +#define SC8280XP_DAI_DATA(...) \ + .dai_data = (const struct sc8280xp_dai_data[]) { __VA_ARGS__ }, \ + .num_dai_data = ARRAY_SIZE(((const struct sc8280xp_dai_data[]) { __VA_ARGS__ })) + struct qcom_snd_soc_common { const char *driver_name; const struct snd_soc_dapm_widget *dapm_widgets; @@ -71,6 +90,8 @@ struct qcom_snd_soc_common { bool mi2s_mclk_enable; bool mi2s_bclk_enable; bool wcd_jack; + const struct sc8280xp_dai_data *dai_data; + size_t num_dai_data; int (*snd_prepare)(struct snd_pcm_substream *substream); }; @@ -83,6 +104,20 @@ struct sc8280xp_snd_data { bool jack_setup; }; +static const struct sc8280xp_dai_data * +sc8280xp_get_dai_data(const struct qcom_snd_soc_common *common, + unsigned int id) +{ + size_t i; + + for (i = 0; i < common->num_dai_data; i++) { + if (common->dai_data[i].id == id) + return &common->dai_data[i]; + } + + return NULL; +} + static inline int sc8280xp_get_mclk_freq(struct snd_pcm_hw_params *params) { int rate = params_rate(params); @@ -112,13 +147,22 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(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); + const struct sc8280xp_dai_data *dai_data; struct snd_soc_dai *codec_dai; struct qcom_snd_tdm_slot_cfg cpu_cfg; struct qcom_snd_tdm_slot_cfg codec_cfg; + bool codec_sysclk_set = data->priv->codec_sysclk_set; + bool mi2s_bclk_enable = data->priv->mi2s_bclk_enable; int bclk_freq; int ret; int i; + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + if (dai_data) { + codec_sysclk_set = dai_data->codec_sysclk_set; + mi2s_bclk_enable = dai_data->mi2s_bclk_enable; + } + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); if (ret) return ret == -ENOENT ? 0 : ret; @@ -147,7 +191,7 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, if (bclk_freq <= 0) return -EINVAL; - if (data->priv->mi2s_bclk_enable) { + if (mi2s_bclk_enable) { ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, SND_SOC_CLOCK_IN); if (ret && ret != -ENOTSUPP) { @@ -157,7 +201,7 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, } } - if (data->priv->codec_sysclk_set) { + if (codec_sysclk_set) { for_each_rtd_codec_dais(rtd, i, codec_dai) { ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, SND_SOC_CLOCK_IN); @@ -176,10 +220,13 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) { 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); + const struct sc8280xp_dai_data *dai_data; struct snd_soc_card *card = rtd->card; struct snd_soc_jack *dp_jack = NULL; int dp_pcm_id = 0; + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + switch (cpu_dai->id) { case WSA_CODEC_DMA_RX_0: case WSA_CODEC_DMA_RX_1: @@ -209,10 +256,24 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) if (dp_jack) return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id); - if (data->priv->wcd_jack) - return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); + if (!dai_data) { + if (data->priv->wcd_jack) + return qcom_snd_wcd_jack_setup(rtd, &data->jack, + &data->jack_setup); - return 0; + return 0; + } + + switch (dai_data->jack_setup) { + case SC8280XP_JACK_SETUP_CODEC: + return qcom_snd_headset_jack_setup(rtd, &data->jack, + &data->jack_setup); + case SC8280XP_JACK_SETUP_WCD: + return qcom_snd_wcd_jack_setup(rtd, &data->jack, + &data->jack_setup); + default: + return 0; + } } static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, @@ -251,10 +312,24 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *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 sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); - int mclk_freq = sc8280xp_get_mclk_freq(params); + const struct sc8280xp_dai_data *dai_data; + bool codec_sysclk_set = data->priv->codec_sysclk_set; + bool mi2s_mclk_enable = data->priv->mi2s_mclk_enable; + bool mi2s_bclk_enable = data->priv->mi2s_bclk_enable; + int mclk_freq; int bclk_freq = sc8280xp_get_bclk_freq(params); int ret; + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + mclk_freq = sc8280xp_get_mclk_freq(params); + if (dai_data) { + codec_sysclk_set = dai_data->codec_sysclk_set; + mi2s_mclk_enable = dai_data->mi2s_mclk_enable; + mi2s_bclk_enable = dai_data->mi2s_bclk_enable; + if (dai_data->mclk_rate) + mclk_freq = dai_data->mclk_rate; + } + switch (cpu_dai->id) { case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: @@ -272,7 +347,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, return ret; } - if (data->priv->mi2s_mclk_enable) { + if (mi2s_mclk_enable) { ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_MCLK, mclk_freq, SND_SOC_CLOCK_OUT); @@ -280,7 +355,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, return ret; } - if (data->priv->mi2s_bclk_enable) { + if (mi2s_bclk_enable) { ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, SND_SOC_CLOCK_OUT); @@ -288,7 +363,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, return ret; } - if (data->priv->codec_sysclk_set) { + if (codec_sysclk_set) { ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk_freq, SND_SOC_CLOCK_IN); @@ -370,6 +445,19 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream) return qcom_snd_sdw_hw_free(substream, &data->stream_prepared[cpu_dai->id]); } +static void sc8280xp_snd_exit(struct snd_soc_pcm_runtime *rtd) +{ + 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); + const struct sc8280xp_dai_data *dai_data; + + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + if (dai_data && dai_data->jack_setup == SC8280XP_JACK_SETUP_CODEC) { + qcom_snd_headset_jack_cleanup(rtd); + data->jack_setup = false; + } +} + static const struct snd_soc_ops sc8280xp_be_ops = { .startup = qcom_snd_sdw_startup, .shutdown = qcom_snd_sdw_shutdown, @@ -386,6 +474,7 @@ static void sc8280xp_add_be_ops(struct snd_soc_card *card) for_each_card_prelinks(card, i, link) { if (link->no_pcm == 1) { link->init = sc8280xp_snd_init; + link->exit = sc8280xp_snd_exit; link->be_hw_params_fixup = sc8280xp_be_hw_params_fixup; link->ops = &sc8280xp_be_ops; } From c82a336b186409bb24c194b1c3626736d8b6f68b Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Fri, 4 Sep 2026 18:02:29 +0800 Subject: [PATCH 337/924] ASoC: qcom: sc8280xp: Add RubikPi 3 sound card support RubikPi 3 connects an ES8316 codec to primary MI2S for headset playback and capture, and an LT9611 bridge to quaternary MI2S for HDMI audio. Add per-DAI data for the primary playback and capture links. Configure the codecs for I2S with normal clock polarity and as bit and frame clock consumers. Program the primary MI2S BCLK, configure the ES8316 for its fixed 19.2 MHz MCLK and enable component jack detection on the playback link. The HDMI link inherits the card-wide I2S format but does not request codec sysclk programming because the LT9611 codec DAI does not implement set_sysclk(). Select this configuration through the RubikPi 3 sound-card compatible. Signed-off-by: Hongyang Zhao Link: https://patch.msgid.link/20260904-rubikpi-next-20260605-v3-5-f49146d85af3@thundersoft.com Signed-off-by: Mark Brown --- sound/soc/qcom/sc8280xp.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 58b4feb2ef3e..b5d367486d56 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -579,6 +579,28 @@ static const struct qcom_snd_soc_common qcs6490_priv_data = { .wcd_jack = true, }; +static const struct qcom_snd_soc_common rubikpi3_priv_data = { + .driver_name = "qcs6490", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .codec_dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_BC_FC, + SC8280XP_DAI_DATA({ + .id = PRIMARY_MI2S_RX, + .mclk_rate = 19200000, + .codec_sysclk_set = true, + .mi2s_bclk_enable = true, + .jack_setup = SC8280XP_JACK_SETUP_CODEC, + }, { + .id = PRIMARY_MI2S_TX, + .mclk_rate = 19200000, + .codec_sysclk_set = true, + .mi2s_bclk_enable = true, + } + ), +}; + static const struct qcom_snd_soc_common qcs8275_priv_data = { .driver_name = "qcs8300", .dapm_widgets = max98090_dapm_widgets, @@ -658,6 +680,7 @@ static const struct of_device_id snd_sc8280xp_dt_match[] = { { .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 = "thundercomm,qcs6490-rubikpi3-sndcard", .data = &rubikpi3_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 }, From b4136c0d69ead737197af741e20edd1c3574f6b4 Mon Sep 17 00:00:00 2001 From: Slavin Liu Date: Sun, 13 Sep 2026 20:51:47 +0800 Subject: [PATCH 338/924] ASoC: fsl_asrc: check the second front-end DMA channel The temporary Front-End DMA request can fail independently of the persistent channel acquired earlier in fsl_asrc_dma_hw_params(). The returned NULL pointer is immediately used to read its private data. Check the temporary channel and release the previously acquired persistent Front-End channel on failure. Clear its slot so a later hw_free cannot release it twice. ASoC marks a component's hw_params only after success and skips unmarked components during rollback, so returning an error alone would leak the earlier channel. Detected by static analysis and reviewed with AI-assisted source auditing. Fixes: 3117bb3109dc ("ASoC: fsl_asrc: Add ASRC ASoC CPU DAI and platform drivers") Assisted-by: LLM Signed-off-by: Slavin Liu Link: https://patch.msgid.link/20260913125147.109920-1-bolin.liu@seu.edu.cn Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc_dma.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index 86489f70e262..0aa5db8973d6 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -251,6 +251,11 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, /* Get DMA request of Front-End */ tmp_chan = asrc->get_dma_channel(pair, dir); + if (!tmp_chan) { + dma_release_channel(pair->dma_chan[!dir]); + pair->dma_chan[!dir] = NULL; + return -EINVAL; + } tmp_data = tmp_chan->private; pair->dma_data.dma_request2 = tmp_data->dma_request; pair->dma_data.peripheral_type = tmp_data->peripheral_type; From 8ca84987c11d11e0ba60f202e3199b3b80c87721 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:37 +0800 Subject: [PATCH 339/924] ASoC: adau17x1: return cache sync errors from resume adau17x1_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The optional switch-mode callback remains ordered before the cache replay. The issue was found by a tool we developed. Fixes: 4101866c743a ("ASoC: Add ADAU1X61 and ADAU1X81 CODECs common code") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-2-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/adau17x1.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c index 26d7eb437ad1..00efaff44e92 100644 --- a/sound/soc/codecs/adau17x1.c +++ b/sound/soc/codecs/adau17x1.c @@ -989,9 +989,7 @@ int adau17x1_resume(struct snd_soc_component *component) if (adau->switch_mode) adau->switch_mode(component->dev); - regcache_sync(adau->regmap); - - return 0; + return regcache_sync(adau->regmap); } EXPORT_SYMBOL_GPL(adau17x1_resume); From 7142c34ed92fddca7038787b8695d22c661dc6ec Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:38 +0800 Subject: [PATCH 340/924] ASoC: ak4642: return cache sync errors from resume ak4642_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: a3a83d9a7cb0 ("ASoC: Add ak4642/ak4643 codec support") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-3-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/ak4642.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 08ec2035b270..37ac298835d6 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -533,8 +533,7 @@ static int ak4642_resume(struct snd_soc_component *component) struct regmap *regmap = dev_get_regmap(component->dev, NULL); regcache_cache_only(regmap, false); - regcache_sync(regmap); - return 0; + return regcache_sync(regmap); } static int ak4642_probe(struct snd_soc_component *component) { From 243d0c47e57a6af89d95aa451ef5fbf453983b1c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:39 +0800 Subject: [PATCH 341/924] ASoC: es8316: return cache sync errors from resume es8316_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: 4bac47a7b2f9 ("ASoC: codecs: add suspend and resume for ES8316") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-4-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/es8316.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index 24ec8b211cdb..4c33a243c1e1 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -810,9 +810,7 @@ static int es8316_resume(struct snd_soc_component *component) struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component); regcache_cache_only(es8316->regmap, false); - regcache_sync(es8316->regmap); - - return 0; + return regcache_sync(es8316->regmap); } static int es8316_suspend(struct snd_soc_component *component) From b7c3f95915a9e791fc8721ce1ecc037f7a55d58c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:40 +0800 Subject: [PATCH 342/924] ASoC: es8323: return cache sync errors from resume es8323_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: b97391a604b9 ("ASoC: codecs: Add support for ES8323") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-5-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/es8323.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/es8323.c b/sound/soc/codecs/es8323.c index b926340256be..03d516d70c43 100644 --- a/sound/soc/codecs/es8323.c +++ b/sound/soc/codecs/es8323.c @@ -733,9 +733,7 @@ static int es8323_resume(struct snd_soc_component *component) struct es8323_priv *es8323 = snd_soc_component_get_drvdata(component); regcache_cache_only(es8323->regmap, false); - regcache_sync(es8323->regmap); - - return 0; + return regcache_sync(es8323->regmap); } static const struct snd_soc_component_driver soc_component_dev_es8323 = { From 0bf2b3411537e8e4d9c03702646a9fc87198d4b1 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:41 +0800 Subject: [PATCH 343/924] ASoC: nau8540: return cache sync errors from resume nau8540_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: c1644e3de45d ("ASoC: nau8540: new codec driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-6-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/nau8540.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/nau8540.c b/sound/soc/codecs/nau8540.c index fefbd5722c00..6c421e2386aa 100644 --- a/sound/soc/codecs/nau8540.c +++ b/sound/soc/codecs/nau8540.c @@ -897,9 +897,7 @@ static int __maybe_unused nau8540_resume(struct snd_soc_component *component) struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); regcache_cache_only(nau8540->regmap, false); - regcache_sync(nau8540->regmap); - - return 0; + return regcache_sync(nau8540->regmap); } static const struct snd_soc_component_driver nau8540_component_driver = { From 432734a00bfae17baeff1a83b61bfc9c2ab20c12 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:42 +0800 Subject: [PATCH 344/924] ASoC: rt1011: return cache sync errors from resume rt1011_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: d6e65bb7ff0d ("ASoC: rt1011: Add RT1011 amplifier driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-7-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1011.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt1011.c b/sound/soc/codecs/rt1011.c index 15488a059283..6e783c3d14ae 100644 --- a/sound/soc/codecs/rt1011.c +++ b/sound/soc/codecs/rt1011.c @@ -2094,9 +2094,7 @@ static int rt1011_resume(struct snd_soc_component *component) struct rt1011_priv *rt1011 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1011->regmap, false); - regcache_sync(rt1011->regmap); - - return 0; + return regcache_sync(rt1011->regmap); } #else #define rt1011_suspend NULL From 98ace05793c7b80a9f49d92b78fcc88e0970f8e4 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:43 +0800 Subject: [PATCH 345/924] ASoC: rt1016: return cache sync errors from resume rt1016_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: 79a4b670b4b4 ("ASoC: rt1016: Add the rt1016 support") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-8-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1016.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt1016.c b/sound/soc/codecs/rt1016.c index 11c8c45574a1..12b97c04d033 100644 --- a/sound/soc/codecs/rt1016.c +++ b/sound/soc/codecs/rt1016.c @@ -570,9 +570,7 @@ static int rt1016_resume(struct snd_soc_component *component) struct rt1016_priv *rt1016 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1016->regmap, false); - regcache_sync(rt1016->regmap); - - return 0; + return regcache_sync(rt1016->regmap); } #else #define rt1016_suspend NULL From 30858325aa90380c6bcdde605214f1ebdbec568c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:44 +0800 Subject: [PATCH 346/924] ASoC: rt1305: return cache sync errors from resume rt1305_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: 29bc643ddd7e ("ASoC: rt1305: Add RT1305/RT1306 amplifier driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-9-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1305.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt1305.c b/sound/soc/codecs/rt1305.c index 2d5fad76cf52..601cbf9eb1e2 100644 --- a/sound/soc/codecs/rt1305.c +++ b/sound/soc/codecs/rt1305.c @@ -896,9 +896,7 @@ static int rt1305_resume(struct snd_soc_component *component) struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1305->regmap, false); - regcache_sync(rt1305->regmap); - - return 0; + return regcache_sync(rt1305->regmap); } #else #define rt1305_suspend NULL From 3db3289178b94056753f7a8c6ee4ab4b232ee6a5 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:45 +0800 Subject: [PATCH 347/924] ASoC: rt1308: return cache sync errors from resume rt1308_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: 2b9def8c0d92 ("ASoC: rt1308: Add RT1308 amplifier driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-10-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1308.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt1308.c b/sound/soc/codecs/rt1308.c index 630946cd7cdf..df95cfbd76d6 100644 --- a/sound/soc/codecs/rt1308.c +++ b/sound/soc/codecs/rt1308.c @@ -715,9 +715,7 @@ static int rt1308_resume(struct snd_soc_component *component) struct rt1308_priv *rt1308 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1308->regmap, false); - regcache_sync(rt1308->regmap); - - return 0; + return regcache_sync(rt1308->regmap); } #else #define rt1308_suspend NULL From a5735b341c6e8cff5f5f9480295012a5ceebb665 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:46 +0800 Subject: [PATCH 348/924] ASoC: rt1318: return cache sync errors from resume rt1318_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: fe1ff61487ac ("ASoC: rt1318: Add RT1318 audio amplifier driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-11-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1318.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt1318.c b/sound/soc/codecs/rt1318.c index 55607f1b9a1e..3dcec7564b82 100644 --- a/sound/soc/codecs/rt1318.c +++ b/sound/soc/codecs/rt1318.c @@ -1075,8 +1075,7 @@ static int rt1318_resume(struct snd_soc_component *component) struct rt1318_priv *rt1318 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1318->regmap, false); - regcache_sync(rt1318->regmap); - return 0; + return regcache_sync(rt1318->regmap); } #else #define rt1318_suspend NULL From 8fc9d353d5371a946d9a9952278b96c09070374c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:47 +0800 Subject: [PATCH 349/924] ASoC: rt5616: return cache sync errors from resume rt5616_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: b1d15059957d ("ASoC: rt5616: add rt5616 codec driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-12-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt5616.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt5616.c b/sound/soc/codecs/rt5616.c index 46ee412dc81d..73a3d1d008f4 100644 --- a/sound/soc/codecs/rt5616.c +++ b/sound/soc/codecs/rt5616.c @@ -1248,8 +1248,7 @@ static int rt5616_resume(struct snd_soc_component *component) struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt5616->regmap, false); - regcache_sync(rt5616->regmap); - return 0; + return regcache_sync(rt5616->regmap); } #else #define rt5616_suspend NULL From 3a9b5bce85a1baff95463a2bb021efac64bcf83f Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:48 +0800 Subject: [PATCH 350/924] ASoC: rt5659: return cache sync errors from resume rt5659_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: d3cb2de2479b ("ASoC: rt5659: add rt5659 codec driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-13-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt5659.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt5659.c b/sound/soc/codecs/rt5659.c index 3097ee6d4e89..915b6b4b2630 100644 --- a/sound/soc/codecs/rt5659.c +++ b/sound/soc/codecs/rt5659.c @@ -3703,9 +3703,7 @@ static int rt5659_resume(struct snd_soc_component *component) struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt5659->regmap, false); - regcache_sync(rt5659->regmap); - - return 0; + return regcache_sync(rt5659->regmap); } #else #define rt5659_suspend NULL From 70fe8997b03078ff8253d985a161fef310f7c346 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:49 +0800 Subject: [PATCH 351/924] ASoC: rt5660: return cache sync errors from resume rt5660_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The power-off resume delay remains ordered before the cache replay. The issue was found by a tool we developed. Fixes: 2b26dd4c1fc5 ("ASoC: rt5660: add rt5660 codec driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-14-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt5660.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt5660.c b/sound/soc/codecs/rt5660.c index edf2e3e346e4..830498c947e0 100644 --- a/sound/soc/codecs/rt5660.c +++ b/sound/soc/codecs/rt5660.c @@ -1148,9 +1148,7 @@ static int rt5660_resume(struct snd_soc_component *component) msleep(350); regcache_cache_only(rt5660->regmap, false); - regcache_sync(rt5660->regmap); - - return 0; + return regcache_sync(rt5660->regmap); } #else #define rt5660_suspend NULL From 9f2933d9a437302d5ae32eb9022efef049d8ab22 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:50 +0800 Subject: [PATCH 352/924] ASoC: rt5665: return cache sync errors from resume rt5665_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: 33ada14a26c8 ("ASoC: add rt5665 codec driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-15-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt5665.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c index 1d987472ba42..6e4967ed12c3 100644 --- a/sound/soc/codecs/rt5665.c +++ b/sound/soc/codecs/rt5665.c @@ -4388,9 +4388,7 @@ static int rt5665_resume(struct snd_soc_component *component) struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt5665->regmap, false); - regcache_sync(rt5665->regmap); - - return 0; + return regcache_sync(rt5665->regmap); } #else #define rt5665_suspend NULL From 691c4aac5ae5a99537785659d25d49071e2c6502 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:04 +0700 Subject: [PATCH 353/924] ASoC: mediatek: mt8188: mt8188-afe-clk: Propagate clock initialization errors Use dev_err_probe() to handle clock lookup errors without printing redundant messages for deferred probe. Propagate the original error from tuner initialization instead of returning -EINVAL. Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-clk.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c index fc6cb3f0469e..42878c8a6529 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c @@ -416,12 +416,9 @@ int mt8188_afe_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT8188_CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "%s(), devm_clk_get %s fail, ret %ld\n", - __func__, aud_clks[i], - PTR_ERR(afe_priv->clk[i])); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } /* initial tuner */ @@ -430,7 +427,7 @@ int mt8188_afe_init_clock(struct mtk_base_afe *afe) if (ret) { dev_info(afe->dev, "%s(), init apll_tuner%d failed", __func__, (i + 1)); - return -EINVAL; + return ret; } } From a2c3ee3349a7da91e563b9a21a355a28fa754bc7 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:05 +0700 Subject: [PATCH 354/924] ASoC: mediatek: mt8188: mt8188-afe-clk: Handle tuner clock enable errors Clock enable errors are currently ignored when enabling the APLL and tuner clocks. Check the return values and roll back the APLL clock if the tuner clock fails to enable. Fixes: f6b026479b13 ("ASoC: mediatek: mt8188: support audio clock control") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-clk.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c index 42878c8a6529..ecba13eda440 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c @@ -260,15 +260,28 @@ static int mt8188_afe_enable_tuner_clk(struct mtk_base_afe *afe, unsigned int id) { struct mt8188_afe_private *afe_priv = afe->platform_priv; + int ret; switch (id) { case MT8188_AUD_PLL1: - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL1_TUNER]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL]); + if (ret) + return ret; + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL1_TUNER]); + if (ret) { + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL]); + return ret; + } break; case MT8188_AUD_PLL2: - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2_TUNER]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2]); + if (ret) + return ret; + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2_TUNER]); + if (ret) { + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2]); + return ret; + } break; default: return -EINVAL; From 60a8112a088aee38d728457a14fa8bfb50f4c87e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:06 +0700 Subject: [PATCH 355/924] ASoC: mediatek: mt8188: mt8188-afe-clk: Propagate regmap update errors The return values from regmap_update_bits() are currently ignored by the clock and AFE control functions. Propagate the errors to allow callers to handle regmap update failures. Fixes: f6b026479b13 ("ASoC: mediatek: mt8188: support audio clock control") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-clk.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c index ecba13eda440..77511f6fba41 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c @@ -564,9 +564,7 @@ static int mt8188_afe_enable_top_cg(struct mtk_base_afe *afe, unsigned int cg_ty unsigned int mask = get_top_cg_mask(cg_type); unsigned int val = get_top_cg_on_val(cg_type); - regmap_update_bits(afe->regmap, reg, mask, val); - - return 0; + return regmap_update_bits(afe->regmap, reg, mask, val); } static int mt8188_afe_disable_top_cg(struct mtk_base_afe *afe, unsigned int cg_type) @@ -575,9 +573,7 @@ static int mt8188_afe_disable_top_cg(struct mtk_base_afe *afe, unsigned int cg_t unsigned int mask = get_top_cg_mask(cg_type); unsigned int val = get_top_cg_off_val(cg_type); - regmap_update_bits(afe->regmap, reg, mask, val); - - return 0; + return regmap_update_bits(afe->regmap, reg, mask, val); } int mt8188_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) @@ -617,14 +613,12 @@ int mt8188_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) static int mt8188_afe_enable_afe_on(struct mtk_base_afe *afe) { - regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x1); - return 0; + return regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x1); } static int mt8188_afe_disable_afe_on(struct mtk_base_afe *afe) { - regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x0); - return 0; + return regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x0); } static int mt8188_afe_enable_a1sys(struct mtk_base_afe *afe) From 1b65c13d1af99170e2a855ca9212a03daa3e7f04 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:07 +0700 Subject: [PATCH 356/924] ASoC: mediatek: mt8188: mt8188-afe-clk: Handle clock enable errors Clock enable errors are currently ignored by several AFE clock control functions. Check the return values and roll back previously enabled clocks when a subsequent clock enable fails. Fixes: f6b026479b13 ("ASoC: mediatek: mt8188: support audio clock control") Fixes: 9be0213a6858 ("ASoC: mediatek: mt8188: refine APLL control") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-clk.c | 69 ++++++++++++++++++---- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c index 77511f6fba41..8b6dca22c8ed 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c @@ -579,22 +579,47 @@ static int mt8188_afe_disable_top_cg(struct mtk_base_afe *afe, unsigned int cg_t int mt8188_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) { struct mt8188_afe_private *afe_priv = afe->platform_priv; - + int ret; /* bus clock for AFE external access, like DRAM */ - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUDIO_LOCAL_BUS_SEL]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUDIO_LOCAL_BUS_SEL]); + if (ret) + goto err_local_bus; /* bus clock for AFE internal access, like AFE SRAM */ - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUD_INTBUS_SEL]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUD_INTBUS_SEL]); + if (ret) + goto err_intbus; /* audio 26m clock source */ - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_ADSP_AUDIO_26M]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_ADSP_AUDIO_26M]); + if (ret) + goto err_26m; /* AFE hw clock */ - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_AFE]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS_HP]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_AFE]); + if (ret) + goto err_afe; + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS_HP]); + if (ret) + goto err_a1sys_hp; + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); + if (ret) + goto err_a1sys; return 0; + +err_a1sys: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS_HP]); +err_a1sys_hp: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_AFE]); +err_afe: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_ADSP_AUDIO_26M]); +err_26m: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUD_INTBUS_SEL]); +err_intbus: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUDIO_LOCAL_BUS_SEL]); +err_local_bus: + return ret; } int mt8188_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) @@ -630,7 +655,13 @@ static int mt8188_afe_enable_a1sys(struct mtk_base_afe *afe) if (ret) return ret; - return mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A1SYS_TIMING); + ret = mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A1SYS_TIMING); + if (ret) { + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); + return ret; + } + + return 0; } static int mt8188_afe_disable_a1sys(struct mtk_base_afe *afe) @@ -651,7 +682,13 @@ static int mt8188_afe_enable_a2sys(struct mtk_base_afe *afe) if (ret) return ret; - return mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A2SYS_TIMING); + ret = mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A2SYS_TIMING); + if (ret) { + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A2SYS]); + return ret; + } + + return 0; } static int mt8188_afe_disable_a2sys(struct mtk_base_afe *afe) @@ -739,8 +776,18 @@ int mt8188_apll2_disable(struct mtk_base_afe *afe) int mt8188_afe_enable_main_clock(struct mtk_base_afe *afe) { - mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); - mt8188_afe_enable_afe_on(afe); + int ret; + + ret = mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); + if (ret) + return ret; + + ret = mt8188_afe_enable_afe_on(afe); + if (ret) { + mt8188_afe_disable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); + return ret; + } + return 0; } From f5f6941d92972ba66829a99999fa825e63da5162 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:08 +0700 Subject: [PATCH 357/924] ASoC: mediatek: mt8188: mt8188-afe-pcm: Handle runtime resume errors Errors from clock enable and regcache synchronization are currently ignored during runtime resume. Check the return values from mt8188_afe_enable_reg_rw_clk(), regcache_sync(), and mt8188_afe_enable_main_clock(), and abort the runtime resume if any of them fails. On failure, disable the previously enabled reg_rw clocks and restore the regmap to cache-only mode. Fixes: bf106bf09376 ("ASoC: mediatek: mt8188: add platform driver") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-pcm.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c index 7b1f5d05f4d6..f8cbe7bac36d 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c @@ -3030,22 +3030,33 @@ static int mt8188_afe_runtime_resume(struct device *dev) struct mtk_base_afe *afe = dev_get_drvdata(dev); struct mt8188_afe_private *afe_priv = afe->platform_priv; struct arm_smccc_res res; + int ret; arm_smccc_smc(MTK_SIP_AUDIO_CONTROL, MTK_AUDIO_SMC_OP_DOMAIN_SIDEBANDS, 0, 0, 0, 0, 0, 0, &res); - mt8188_afe_enable_reg_rw_clk(afe); + ret = mt8188_afe_enable_reg_rw_clk(afe); + if (ret) + return ret; if (!afe->regmap || afe_priv->pm_runtime_bypass_reg_ctl) - goto skip_regmap; + return 0; regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) + goto err; + + ret = mt8188_afe_enable_main_clock(afe); + if (ret) + goto err; - mt8188_afe_enable_main_clock(afe); -skip_regmap: return 0; +err: + mt8188_afe_disable_reg_rw_clk(afe); + regcache_cache_only(afe->regmap, true); + return ret; } static int init_memif_priv_data(struct mtk_base_afe *afe) From a79f2432ee89cc825b83b92383cc47ea80683dd2 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:09 +0700 Subject: [PATCH 358/924] ASoC: mediatek: mt8188: mt8188-afe-pcm: Drop redundant probe error messages Several probe error paths print error messages that are already reported by the called functions. Return the original error directly for devm_platform_ioremap_resource(), mt8188_afe_init_clock(), platform_get_irq(), and devm_request_irq() instead of wrapping the errors with dev_err_probe(). Also remove the redundant "err_platform" warning from the component registration error path. Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-pcm.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c index f8cbe7bac36d..efad0af977f4 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c @@ -3232,8 +3232,7 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) afe->base_addr = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(afe->base_addr)) - return dev_err_probe(dev, PTR_ERR(afe->base_addr), - "AFE base_addr not found\n"); + return PTR_ERR(afe->base_addr); infra_ao = syscon_regmap_lookup_by_phandle(dev->of_node, "mediatek,infracfg"); @@ -3269,7 +3268,7 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt8188_afe_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "init clock error"); + return ret; spin_lock_init(&afe_priv->afe_ctrl_lock); @@ -3302,12 +3301,12 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); if (irq_id < 0) - return dev_err_probe(dev, irq_id, "no irq found"); + return irq_id; ret = devm_request_irq(dev, irq_id, mt8188_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for asys-isr\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); @@ -3363,10 +3362,8 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) /* register component */ ret = devm_snd_soc_register_component(dev, &mtk_afe_pcm_platform, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_warn(dev, "err_platform\n"); + if (ret) goto err_pm_put; - } mt8188_afe_init_registers(afe); From 6cddf6e56d2539fd13c0ee9dd817c4e06a967173 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:10 +0700 Subject: [PATCH 359/924] ASoC: mediatek: mt8188: fix clk leak on error in audsys_clk_register devm_add_action_or_reset() is called after the loop that registers gate clocks. If kzalloc() fails mid-loop, the function returns -ENOMEM before that call, so cleanup is never registered and all previously registered clocks leak permanently. Move devm_add_action_or_reset() before the loop so cleanup is always scheduled. The clock from the current (failing) iteration is not yet stored in afe_priv->lookup[i], so it still needs an explicit clk_unregister_gate() call. Fixes: fd67a7a1a22c ("ASoC: mediatek: mt8188: fix use-after-free in driver remove path") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-audsys-clk.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-audsys-clk.c b/sound/soc/mediatek/mt8188/mt8188-audsys-clk.c index 972f097a13ca..9f3b3a777577 100644 --- a/sound/soc/mediatek/mt8188/mt8188-audsys-clk.c +++ b/sound/soc/mediatek/mt8188/mt8188-audsys-clk.c @@ -170,7 +170,7 @@ int mt8188_audsys_clk_register(struct mtk_base_afe *afe) struct mt8188_afe_private *afe_priv = afe->platform_priv; struct clk *clk; struct clk_lookup *cl; - int i; + int i, ret; afe_priv->lookup = devm_kcalloc(afe->dev, CLK_AUD_NR_CLK, sizeof(*afe_priv->lookup), @@ -179,6 +179,10 @@ int mt8188_audsys_clk_register(struct mtk_base_afe *afe) if (!afe_priv->lookup) return -ENOMEM; + ret = devm_add_action_or_reset(afe->dev, mt8188_audsys_clk_unregister, afe); + if (ret) + return ret; + for (i = 0; i < ARRAY_SIZE(aud_clks); i++) { const struct afe_gate *gate = &aud_clks[i]; @@ -194,8 +198,10 @@ int mt8188_audsys_clk_register(struct mtk_base_afe *afe) /* add clk_lookup for devm_clk_get(SND_SOC_DAPM_CLOCK_SUPPLY) */ cl = kzalloc_obj(*cl); - if (!cl) + if (!cl) { + clk_unregister_gate(clk); return -ENOMEM; + } cl->clk = clk; cl->con_id = gate->name; @@ -206,5 +212,5 @@ int mt8188_audsys_clk_register(struct mtk_base_afe *afe) afe_priv->lookup[i] = cl; } - return devm_add_action_or_reset(afe->dev, mt8188_audsys_clk_unregister, afe); + return 0; } From 5626653a4bdf32812d66b970860677cd9d7a2190 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 17 Sep 2026 19:11:36 +0700 Subject: [PATCH 360/924] ASoC: fsl_asrc_m2m: fix pm_runtime usage counter leak on error pm_runtime_get_sync() leaves the runtime PM usage counter incremented even when it fails, but the error path in fsl_asrc_m2m_comp_open() does not call pm_runtime_put_noidle() to balance it, leaking a reference each time resume fails. Use pm_runtime_resume_and_get() instead, which automatically drops the usage counter on failure, fixing the leak. Fixes: 24a01710f627 ("ASoC: fsl_asrc_m2m: Add memory to memory function") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260917121136.32527-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc_m2m.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index e7b91196d35b..a5e3670349f9 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -320,7 +320,7 @@ static int fsl_asrc_m2m_comp_open(struct snd_compr_stream *stream) if (ret) goto error_alloc_out_buf; - ret = pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0) { dev_err(dev, "Failed to power up asrc\n"); goto err_pm_runtime; From 286ece639c3753ff929b219452391134e611823a Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 16:34:12 +0800 Subject: [PATCH 361/924] ASoC: use regmap_assign_bits() for conditional set/clear Replace if/else blocks using regmap_set_bits()/regmap_clear_bits() with the simpler regmap_assign_bits() calls in the cs35l45 codec and the mediatek mt8188 ADDA and DMIC DAI drivers. Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260924083420.461715-1-peng.fan@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l45.c | 6 ++---- sound/soc/mediatek/mt8188/mt8188-dai-adda.c | 5 +---- sound/soc/mediatek/mt8188/mt8188-dai-dmic.c | 10 ++-------- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index 0ab76824f00f..9eef7c8b1837 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -224,10 +224,8 @@ static int cs35l45_sync_en_put(struct snd_kcontrol *kcontrol, return 0; } - if ((bool)ucontrol->value.integer.value[0]) - regmap_set_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES2, CS35L45_SYNC_EN_MASK); - else - regmap_clear_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES2, CS35L45_SYNC_EN_MASK); + regmap_assign_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES2, CS35L45_SYNC_EN_MASK, + (bool)ucontrol->value.integer.value[0]); cs35l45->sync_en = (bool)ucontrol->value.integer.value[0]; diff --git a/sound/soc/mediatek/mt8188/mt8188-dai-adda.c b/sound/soc/mediatek/mt8188/mt8188-dai-adda.c index ac547fc864a6..395f4ab972f8 100644 --- a/sound/soc/mediatek/mt8188/mt8188-dai-adda.c +++ b/sound/soc/mediatek/mt8188/mt8188-dai-adda.c @@ -123,10 +123,7 @@ static void mtk_adda_ul_mictype(struct mtk_base_afe *afe, bool dmic) UL_MODE_3P25M_CH2_CTL); /* turn on dmic, ch1, ch2 */ - if (dmic) - regmap_set_bits(afe->regmap, reg, val); - else - regmap_clear_bits(afe->regmap, reg, val); + regmap_assign_bits(afe->regmap, reg, val, dmic); } static int mtk_adda_ul_event(struct snd_soc_dapm_widget *w, diff --git a/sound/soc/mediatek/mt8188/mt8188-dai-dmic.c b/sound/soc/mediatek/mt8188/mt8188-dai-dmic.c index a9515d7fb70a..7ac847a5925c 100644 --- a/sound/soc/mediatek/mt8188/mt8188-dai-dmic.c +++ b/sound/soc/mediatek/mt8188/mt8188-dai-dmic.c @@ -143,10 +143,7 @@ static void mtk_dai_dmic_hw_gain_bypass(struct mtk_base_afe *afe, return; } - if (bypass) - regmap_set_bits(afe->regmap, reg->bypass, msk); - else - regmap_clear_bits(afe->regmap, reg->bypass, msk); + regmap_assign_bits(afe->regmap, reg->bypass, msk, bypass); } static void mtk_dai_dmic_hw_gain_on(struct mtk_base_afe *afe, unsigned int id, @@ -157,10 +154,7 @@ static void mtk_dai_dmic_hw_gain_on(struct mtk_base_afe *afe, unsigned int id, if (!reg) return; - if (on) - regmap_set_bits(afe->regmap, reg->con0, DMIC_GAIN_CON0_GAIN_ON); - else - regmap_clear_bits(afe->regmap, reg->con0, DMIC_GAIN_CON0_GAIN_ON); + regmap_assign_bits(afe->regmap, reg->con0, DMIC_GAIN_CON0_GAIN_ON, on); } static const struct reg_sequence mtk_dai_dmic_iir_coeff_reg_defaults[] = { From 090991b292c69029570521fc97e97dc0c3bcda87 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Thu, 24 Sep 2026 15:24:06 +0200 Subject: [PATCH 362/924] ASoC: qcom: sdm845: Demystify TDM masks a bit Describe the mask with the bits used for each RX/TX. Signed-off-by: David Heidelberg Link: https://patch.msgid.link/20260924-pixel3-audio-v4-1-85b5d133aa5e@ixit.cz Signed-off-by: Mark Brown --- sound/soc/qcom/sdm845.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c index 6843ab8ba017..0ce8265ab1c1 100644 --- a/sound/soc/qcom/sdm845.c +++ b/sound/soc/qcom/sdm845.c @@ -23,9 +23,14 @@ #define DEFAULT_MCLK_RATE 24576000 #define TDM_BCLK_RATE 6144000 #define MI2S_BCLK_RATE 1536000 -#define LEFT_SPK_TDM_TX_MASK 0x30 -#define RIGHT_SPK_TDM_TX_MASK 0xC0 -#define SPK_TDM_RX_MASK 0x03 +#define LEFT_SPK_TDM_RX_MASK BIT(0) +#define RIGHT_SPK_TDM_RX_MASK BIT(1) +#define SPK_TDM_RX_MASK (LEFT_SPK_TDM_RX_MASK | RIGHT_SPK_TDM_RX_MASK) +#define MIC1_TDM_RX_MASK BIT(2) +#define MIC2_TDM_RX_MASK BIT(3) +#define MIC_TDM_RX_MASK (MIC1_TDM_RX_MASK | MIC2_TDM_RX_MASK) +#define LEFT_SPK_TDM_TX_MASK (BIT(4) | BIT(5)) +#define RIGHT_SPK_TDM_TX_MASK (BIT(6) | BIT(7)) #define NUM_TDM_SLOTS 8 #define SLIM_MAX_TX_PORTS 16 #define SLIM_MAX_RX_PORTS 13 @@ -112,8 +117,8 @@ static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, channels = params_channels(params); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0x3, - 8, slot_width); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, SPK_TDM_RX_MASK, + NUM_TDM_SLOTS, slot_width); if (ret < 0) { dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n", __func__, ret); @@ -128,8 +133,10 @@ static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, goto end; } } else { - ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0xf, 0, - 8, slot_width); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + SPK_TDM_RX_MASK | + MIC_TDM_RX_MASK, 0, + NUM_TDM_SLOTS, slot_width); if (ret < 0) { dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n", __func__, ret); From ed41c80271e6fc9b8d6aea01f2e6f3ced0f534b2 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Thu, 24 Sep 2026 15:24:07 +0200 Subject: [PATCH 363/924] ASoC: qcom: sdm845: use DSP_A format for TDM codec DAIs Before the DSP_B only worked because the only close-to-mainline consumer cs35l36 codec was patched to map both DSP_A and DSP_B to the same hardware register value (asp_fmt = 0), which is inherently DSP_A timing. Use the right codec (DSP_A) which works as expected. Signed-off-by: David Heidelberg Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260924-pixel3-audio-v4-2-85b5d133aa5e@ixit.cz Signed-off-by: Mark Brown --- sound/soc/qcom/sdm845.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c index 0ce8265ab1c1..d1cc825fc5cd 100644 --- a/sound/soc/qcom/sdm845.c +++ b/sound/soc/qcom/sdm845.c @@ -387,7 +387,7 @@ static int sdm845_snd_startup(struct snd_pcm_substream *substream) TDM_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); } - codec_dai_fmt |= SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_DSP_B; + codec_dai_fmt |= SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_DSP_A; for_each_rtd_codec_dais(rtd, j, codec_dai) { From 84c3a8e05ce8c9d0c6678b33964cd31c4ab569a3 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Thu, 24 Sep 2026 15:24:08 +0200 Subject: [PATCH 364/924] ASoC: qcom: sdm845: Use per-speaker RX masks for TDM slot assignment Both Left and Right codec DAIs were passing the same SPK_TDM_RX_MASK, both speakers ended up on slot 0, breaking the one speaker in configuration such as on Pixel 3. Split SPK_TDM_RX_MASK into per-speaker masks so that the Left codec gets slot 0 (rx_mask=0x01) and the Right codec gets slot 1 (rx_mask=0x02). This commit is here, so later CS35L36 receives correct slot for right and left speakers. Assisted-by: Claude:claude-4.6-opus Signed-off-by: David Heidelberg Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260924-pixel3-audio-v4-3-85b5d133aa5e@ixit.cz Signed-off-by: Mark Brown --- sound/soc/qcom/sdm845.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c index d1cc825fc5cd..59cfa3b26cd4 100644 --- a/sound/soc/qcom/sdm845.c +++ b/sound/soc/qcom/sdm845.c @@ -157,7 +157,7 @@ static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, if (!strcmp(codec_dai->component->name_prefix, "Left")) { ret = snd_soc_dai_set_tdm_slot( codec_dai, LEFT_SPK_TDM_TX_MASK, - SPK_TDM_RX_MASK, NUM_TDM_SLOTS, + LEFT_SPK_TDM_RX_MASK, NUM_TDM_SLOTS, slot_width); if (ret < 0) { dev_err(rtd->dev, @@ -169,7 +169,7 @@ static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, if (!strcmp(codec_dai->component->name_prefix, "Right")) { ret = snd_soc_dai_set_tdm_slot( codec_dai, RIGHT_SPK_TDM_TX_MASK, - SPK_TDM_RX_MASK, NUM_TDM_SLOTS, + RIGHT_SPK_TDM_RX_MASK, NUM_TDM_SLOTS, slot_width); if (ret < 0) { dev_err(rtd->dev, From 5e9f324509a76e377db813839d27012084081dfb Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Thu, 24 Sep 2026 15:24:09 +0200 Subject: [PATCH 365/924] ASoC: qcom: sdm845: Set codec dai and component sysclk during startup The cs35l36 codec needs the codec dai and component sysclk to be set during TDM startup. Set these for all codec DAIs on the QUATERNARY_TDM path, gracefully handling codecs that don't support sysclk by ignoring -ENOTSUPP returns. Based on work of Joel Selvaraj. Signed-off-by: David Heidelberg Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260924-pixel3-audio-v4-4-85b5d133aa5e@ixit.cz Signed-off-by: Mark Brown --- sound/soc/qcom/sdm845.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c index 59cfa3b26cd4..f0fbec9c8df2 100644 --- a/sound/soc/qcom/sdm845.c +++ b/sound/soc/qcom/sdm845.c @@ -412,6 +412,28 @@ static int sdm845_snd_startup(struct snd_pcm_substream *substream) return ret; } } + + /* Set codec sysclk needed by codecs like cs35l36. */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + TDM_BCLK_RATE, + SND_SOC_CLOCK_IN); + if (ret < 0 && ret != -ENOTSUPP) { + dev_err(codec_dai->dev, + "Failed to set codec dai sysclk: %d\n", + ret); + return ret; + } + + ret = snd_soc_component_set_sysclk(codec_dai->component, + 0, 0, + TDM_BCLK_RATE, + SND_SOC_CLOCK_IN); + if (ret < 0 && ret != -ENOTSUPP) { + dev_err(codec_dai->dev, + "Failed to set codec component sysclk: %d\n", + ret); + return ret; + } } break; case SLIMBUS_0_RX...SLIMBUS_6_TX: From f390794e6354de783a1443f2b935d9e2b4361354 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Thu, 24 Sep 2026 15:24:10 +0200 Subject: [PATCH 366/924] ASoC: cs35l36: Implement set_tdm_slot to program RX and TX slots Program the ASP RX and TX slot registers from the TDM masks passed by the machine driver. Each set bit in a mask names a slot; codec channels are assigned to those slots in order, ASPRX1 taking the first RX slot and ASPTX1..TX8 the first eight TX slots, with a warning if the mask names more slots than the device has channels. Passing slots == 0 or an empty mask restores the hardware defaults, ASPRX1 in slot 0 and ASPTX1..TX8 in slots 0..7. This lets a machine driver with several amplifiers on one bus, such as sdm845 with two CS35L36, put each amplifier on its own RX slot and keep their TX slots from colliding. Assisted-by: Claude:claude-4.6-opus Reviewed-by: Charles Keepax Signed-off-by: David Heidelberg Link: https://patch.msgid.link/20260924-pixel3-audio-v4-5-85b5d133aa5e@ixit.cz Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l36.c | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/sound/soc/codecs/cs35l36.c b/sound/soc/codecs/cs35l36.c index 170588be4dbe..5fc8ec292e3c 100644 --- a/sound/soc/codecs/cs35l36.c +++ b/sound/soc/codecs/cs35l36.c @@ -947,6 +947,49 @@ static const struct cs35l36_pll_config *cs35l36_get_clk_config( return NULL; } +static void cs35l36_mask_to_slots(struct cs35l36_private *cs35l36, + unsigned long mask, unsigned int base_reg, + unsigned int nchan) +{ + unsigned int chan = 0, shift; + int slot; + + /* Two 6-bit slot fields per register, at bits 0 and 16 */ + for_each_set_bit(slot, &mask, BITS_PER_TYPE(mask)) { + if (chan == nchan) { + dev_warn(cs35l36->dev, + "Too many slots in TDM mask: %lx\n", mask); + return; + } + + shift = (chan % 2) * CS35L36_ASP_TX2_SLOT_SHIFT; + regmap_update_bits(cs35l36->regmap, base_reg + (chan / 2) * 4, + CS35L36_ASP_RX1_SLOT_MASK << shift, + slot << shift); + chan++; + } +} + +static int cs35l36_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct cs35l36_private *cs35l36 = + snd_soc_component_get_drvdata(dai->component); + + /* Note: rx/tx is from point of view of the CPU end */ + if (!slots || !rx_mask) + rx_mask = BIT(0); /* ASPRX1 in slot 0 */ + + if (!slots || !tx_mask) + tx_mask = GENMASK(7, 0); /* ASPTX1..8 in slots 0..7 */ + + cs35l36_mask_to_slots(cs35l36, rx_mask, CS35L36_ASP_RX1_SLOT, 1); + cs35l36_mask_to_slots(cs35l36, tx_mask, CS35L36_ASP_TX1_TX2_SLOT, 8); + + return 0; +} + static const u64 cs35l36_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_I2S | SND_SOC_POSSIBLE_DAIFMT_DSP_A | @@ -963,6 +1006,7 @@ static const struct snd_soc_dai_ops cs35l36_ops = { .set_sysclk = cs35l36_dai_set_sysclk, .auto_selectable_formats = &cs35l36_selectable_formats, .num_auto_selectable_formats = 1, + .set_tdm_slot = cs35l36_set_tdm_slot, }; #define CS35L36_RATES ( \ From 4d2b92b82ee82f0008179cd795b02256144064e1 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 25 Sep 2026 15:11:27 +0200 Subject: [PATCH 367/924] ASoC: amd: acp: add EFI dependency for selecting TAS2783 The TAS2783 codec requires EFI support, so it cannot be selected unless that is also enabled: WARNING: unmet direct dependencies detected for SND_SOC_TAS2783_SDW Depends on [n]: SOUND [=m] && SND [=m] && SND_SOC [=m] && SOUNDWIRE [=m] && SND_SOC_SDCA [=m] && EFI [=n] Selected by [m]: - SND_SOC_AMD_LEGACY_SDW_MACH [=m] && SOUND [=m] && SND [=m] && SND_SOC [=m] && SND_SOC_AMD_ACP_COMMON [=m] && X86 [=y] && PCI [=y] && ACPI [=y] && SOUNDWIRE [=m] Add this as a dependency for SND_SOC_AMD_LEGACY_SDW_MACH, assuming that this is only needed on modern x86 machines that require EFI support already. It would likely be possible to do something more complex to still allow SND_SOC_AMD_LEGACY_SDW_MACH to be used without TAS2783 when EFI is disabled, but I cannot see a usecase for that. Fixes: 28114992dd2f ("ASoC: amd: acp: enable TAS2783 and add RT712-VB SoundWire machine") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260925131137.146638-1-arnd@kernel.org Signed-off-by: Mark Brown --- sound/soc/amd/acp/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig index 6433876430f0..b7b7b4f4a55c 100644 --- a/sound/soc/amd/acp/Kconfig +++ b/sound/soc/amd/acp/Kconfig @@ -167,7 +167,7 @@ config SND_SOC_AMD_SOF_SDW_MACH config SND_SOC_AMD_LEGACY_SDW_MACH tristate "AMD Legacy(No DSP) Soundwire Machine Driver Support" - depends on X86 && PCI && ACPI + depends on X86 && PCI && ACPI && EFI depends on SOUNDWIRE select SND_SOC_AMD_SDW_MACH_COMMON select SND_SOC_SDW_UTILS From 7c7df141dcabaa5370b5e52a9d64e2c1ea13a338 Mon Sep 17 00:00:00 2001 From: David Cemin Date: Sat, 12 Sep 2026 11:21:31 -0700 Subject: [PATCH 368/924] ASoC: sdw_utils: clear stale RT711 device reference on exit asoc_sdw_rt711_exit() drops the reference held in ctx->headset_codec_dev but leaves the pointer populated. If the card cleanup path reaches the exit hook more than once after a failed or deferred probe, a later invocation reuses the stale pointer and calls put_device() again, underflowing the refcount and leading to oopses when later probe, driver bind, or suspend paths walk the SoundWire bus after the device reference was released. Observed as boot- and suspend-time oopses on an arm64 platform with an RT712 headset codec, reproducible deterministically by repeated machine-driver probe cycling with the codec drivers unloaded. Clear the pointer after put_device() so repeated cleanup becomes a no-op, which also makes the existing NULL check at the top of the exit hook effective. This matches what commit 046173b98de3 ("ASoC: sdw_utils: fix double put_device() on aggregated amps") did for the aggregated amp references. Fixes: 811648340707 ("ASoC: Intel: sof_sdw: avoid oops in error handling") Cc: stable@vger.kernel.org Signed-off-by: David Cemin Link: https://patch.msgid.link/20260912182131.1156475-1-dcemin@nvidia.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_rt711.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sdw_utils/soc_sdw_rt711.c b/sound/soc/sdw_utils/soc_sdw_rt711.c index 3a3a66b4b737..4707190e160c 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt711.c +++ b/sound/soc/sdw_utils/soc_sdw_rt711.c @@ -124,6 +124,7 @@ int asoc_sdw_rt711_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_ device_remove_software_node(ctx->headset_codec_dev); put_device(ctx->headset_codec_dev); + ctx->headset_codec_dev = NULL; return 0; } From cc466c77059ea6ce734f909ff4b156b036334cc3 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Fri, 25 Sep 2026 18:59:17 +0200 Subject: [PATCH 369/924] ASoC: airoha: Fix sparse warning for AN7581 AFE PCM Fix sparse warning reported by kernel bot: sparse warnings: (new ones prefixed by >>) >> sound/soc/mediatek/an7581/an7581-afe-pcm.c:144:30: sparse: sparse: symbol 'an7581_afe_fe_ops' was not declared. Should it be static? >> sound/soc/mediatek/an7581/an7581-afe-pcm.c:419:14: sparse: sparse: incorrect type in assignment (different address spaces) @@ expected void *base @@ got void [noderef] __iomem * @@ sound/soc/mediatek/an7581/an7581-afe-pcm.c:419:14: sparse: expected void *base sound/soc/mediatek/an7581/an7581-afe-pcm.c:419:14: sparse: got void [noderef] __iomem * >> sound/soc/mediatek/an7581/an7581-afe-pcm.c:439:42: sparse: sparse: incorrect type in argument 3 (different address spaces) @@ expected void [noderef] __iomem *regs @@ got void *base @@ sound/soc/mediatek/an7581/an7581-afe-pcm.c:439:42: sparse: expected void [noderef] __iomem *regs Make an7581_afe_fe_ops static and add __iomem for the base from devm_platform_ioremap_resource. Fixes: 4974ffa0d6c2 ("ASoC: airoha: Add AFE driver for Airoha AN7581") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609242059.t4FlJzOz-lkp@intel.com/ Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260925165919.5407-1-ansuelsmth@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/an7581/an7581-afe-pcm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/an7581/an7581-afe-pcm.c b/sound/soc/mediatek/an7581/an7581-afe-pcm.c index a1d742362538..d47edb630452 100644 --- a/sound/soc/mediatek/an7581/an7581-afe-pcm.c +++ b/sound/soc/mediatek/an7581/an7581-afe-pcm.c @@ -141,7 +141,7 @@ static int an7581_afe_fe_startup(struct snd_pcm_substream *substream, struct snd return ret; } -const struct snd_soc_dai_ops an7581_afe_fe_ops = { +static const struct snd_soc_dai_ops an7581_afe_fe_ops = { .startup = an7581_afe_fe_startup, .shutdown = mtk_afe_fe_shutdown, .hw_params = mtk_afe_fe_hw_params, @@ -375,7 +375,7 @@ static int an7581_afe_pcm_dev_probe(struct platform_device *pdev) struct reset_control *reset; struct mtk_base_afe *afe; int i, irq_id, ret; - void *base; + void __iomem *base; afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); if (!afe) From f0cc352be29ba616e0682f66f551d3f3029886fd Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Mon, 14 Sep 2026 17:50:09 +0800 Subject: [PATCH 370/924] ASoC: wcd9335: Fix device reference leak in wcd9335_slim_status() wcd9335_slim_status() obtains the SLIM interface device with of_slim_get_device(), which uses device_find_child() internally and returns the device with a reference held. As the device_find_child() documentation says: you will need to drop the reference with put_device() after use. The reference is stored in wcd->slim_ifc_dev and never dropped, so the SLIM interface device cannot be released. Fix it by using devm_add_action_or_reset() or put_device() respectively. Found by code review. Fixes: 20aedafdf492 ("ASoC: wcd9335: add support to wcd9335 codec") Cc: stable@vger.kernel.org Signed-off-by: Ma Ke Link: https://patch.msgid.link/20260914095009.12049-1-make_ruc2021@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/wcd9335.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index 40de279a9750..9332082092c7 100644 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -5096,6 +5096,13 @@ static int wcd9335_slim_probe(struct slim_device *slim) return 0; } +static void wcd9335_put_device_action(void *data) +{ + struct device *dev = data; + + put_device(dev); +} + static int wcd9335_slim_status(struct slim_device *sdev, enum slim_device_status status) { @@ -5120,6 +5127,11 @@ static int wcd9335_slim_status(struct slim_device *sdev, return -EINVAL; } + ret = devm_add_action_or_reset(dev, wcd9335_put_device_action, + &wcd->slim_ifc_dev->dev); + if (ret) + return ret; + slim_get_logical_addr(wcd->slim_ifc_dev); wcd->regmap = regmap_init_slimbus(sdev, &wcd9335_regmap_config); From 7f05596bd4047e4623eeb1f3c6921d6f71481c3b Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 11:51:59 +0700 Subject: [PATCH 371/924] ASoC: mediatek: mt8183: Fix wrong clock cleanup on clk_set_parent() failure In mt8183_afe_enable_clock(), when clk_set_parent() fails, the current error path incorrectly cleans up the previously enabled clock instead of the clock used by clk_set_parent(). Fix the error path to clean up the correct clock when clk_set_parent() fails. Fixes: a94aec035a12 ("ASoC: mediatek: mt8183: add platform driver") Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903045203.175337-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8183/mt8183-afe-clk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c index cc4f8f4d3dab..6ab0734ad136 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c @@ -137,7 +137,7 @@ int mt8183_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s(), clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIO], aud_clks[CLK_CLK26M], ret); - goto CLK_MUX_AUDIO_ERR; + goto CLK_MUX_AUDIO_INTBUS_ERR; } ret = clk_prepare_enable(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); @@ -153,7 +153,7 @@ int mt8183_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s(), clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIOINTBUS], aud_clks[CLK_TOP_SYSPLL_D2_D4], ret); - goto CLK_MUX_AUDIO_INTBUS_ERR; + goto CLK_AFE_ERR; } ret = clk_prepare_enable(afe_priv->clk[CLK_AFE]); From 4a0f9566ae0c3ad32620254342b266f37f4757de Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 11:52:00 +0700 Subject: [PATCH 372/924] ASoC: mediatek: mt8183: Fix clock handling in mux disable path In the disable path of the appl*_mux_setting() functions, clk_disable_unprepare() is called after clk_set_parent(). If clk_set_parent() fails, clk_disable_unprepare() is skipped, potentially leaving one of the clocks enabled. Remove the goto exit paths so that all teardown steps are attempted and any errors are only reported. Fixes: a94aec035a12 ("ASoC: mediatek: mt8183: add platform driver") Suggested-by: Cezary Rojewski Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260903045203.175337-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8183/mt8183-afe-clk.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c index 6ab0734ad136..dd1a5925af96 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c @@ -271,7 +271,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); @@ -281,7 +280,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); } @@ -298,7 +296,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) afe_priv->clk[CLK_CLK26M]); clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); ERR_ENABLE_CLK_TOP_MUX_AUD_1: -EXIT: return ret; } @@ -345,7 +342,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); @@ -355,7 +351,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); } @@ -372,7 +367,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) afe_priv->clk[CLK_CLK26M]); clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); ERR_ENABLE_CLK_TOP_MUX_AUD_2: -EXIT: return ret; } From 30cf48383006579d7cd35380cc6e766260b72bff Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 11:52:01 +0700 Subject: [PATCH 373/924] ASoC: mediatek: mt8183: Fix APLL enable error handling Currently, the mt8183_apll*_enable() functions call mux_setting(afe, true) but do not check its return value to handle failures. In addition, the cleanup paths of mt8183_apll*_enable() do not call mux_setting(afe, false) when the enable operation fails, while the mt8183_apll*_disable() functions do. Add error handling for apll*_mux_setting() and call mux_setting(afe, false) in the cleanup paths when mt8183_apll*_enable() fails. Fixes: a94aec035a12 ("ASoC: mediatek: mt8183: add platform driver") Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903045203.175337-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8183/mt8183-afe-clk.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c index dd1a5925af96..06556689976d 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c @@ -376,7 +376,9 @@ int mt8183_apll1_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll1_mux_setting(afe, true); + ret = apll1_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL22M]); if (ret) { @@ -405,6 +407,8 @@ int mt8183_apll1_enable(struct mtk_base_afe *afe) ERR_CLK_APLL1_TUNER: clk_disable_unprepare(afe_priv->clk[CLK_APLL22M]); ERR_CLK_APLL22M: + apll1_mux_setting(afe, false); + return ret; } @@ -430,7 +434,9 @@ int mt8183_apll2_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll2_mux_setting(afe, true); + ret = apll2_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL24M]); if (ret) { @@ -459,6 +465,8 @@ int mt8183_apll2_enable(struct mtk_base_afe *afe) ERR_CLK_APLL2_TUNER: clk_disable_unprepare(afe_priv->clk[CLK_APLL24M]); ERR_CLK_APLL24M: + apll2_mux_setting(afe, false); + return ret; } From 21cc0c50d395b8b8311c0b6c713038d9d9607063 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 11:52:02 +0700 Subject: [PATCH 374/924] ASoC: mediatek: mt8183: Use dev_err_probe() for error handling Replace dev_err() with dev_err_probe() to prevent log spam when probe returns -EPROBE_DEFER. Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903045203.175337-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8183/mt8183-afe-clk.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c index 06556689976d..40f9afcdaa16 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c @@ -101,12 +101,9 @@ int mt8183_init_clock(struct mtk_base_afe *afe) for (i = 0; i < CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "%s(), devm_clk_get %s fail, ret %ld\n", - __func__, aud_clks[i], - PTR_ERR(afe_priv->clk[i])); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } return 0; From f7bcb8c94ccb8a8b231a353c628b0104951ab6d0 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 11:52:03 +0700 Subject: [PATCH 375/924] ASoC: mediatek: mt8183: Drop redundant probe error messages The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() calls are redundant and can be removed. Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903045203.175337-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8183/mt8183-afe-pcm.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c index 2634699534db..46b7a2bb6aaa 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c @@ -809,10 +809,8 @@ static int mt8183_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt8183_init_clock(afe); - if (ret) { - dev_err(dev, "init clock error\n"); + if (ret) return ret; - } pm_runtime_enable(dev); @@ -903,10 +901,8 @@ static int mt8183_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt8183_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); - if (ret) { - dev_err(dev, "could not request_irq for asys-isr\n"); + if (ret) goto err_pm_disable; - } /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); From b25a036348e945a1ddb106609fc9fa52205cc936 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 24 Sep 2026 18:48:12 +0200 Subject: [PATCH 376/924] ASoC: sprd: Shadow errors from dma_request_chan() For some reason the SPDR PCM support code requests DMA resources in the IOCTL. This might lead to the Linux error codes, such as EPROBE_DEFER, to be leaked to the user space. Prevent that from happening by shadowing errors from dma_request_chan() as it was done previously (see Fixes tag). Fixes: 08d77e31df21 ("ASoC: sprd: Replace dma_request_slave_channel() by dma_request_chan()") Reported-by: Sashiko Closes: https://lore.kernel.org/r/20260918115229.CC8AC1F000FF@smtp.kernel.org Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260924164812.816743-1-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sprd/sprd-pcm-compress.c | 2 +- sound/soc/sprd/sprd-pcm-dma.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/sprd/sprd-pcm-compress.c b/sound/soc/sprd/sprd-pcm-compress.c index f394121d417a..f790b959cd4e 100644 --- a/sound/soc/sprd/sprd-pcm-compress.c +++ b/sound/soc/sprd/sprd-pcm-compress.c @@ -157,7 +157,7 @@ static int sprd_platform_compr_dma_config(struct snd_soc_component *component, chan = dma_request_chan(dev, dma_params->chan_name[channel]); if (IS_ERR(chan)) { dev_err(dev, "failed to request dma channel\n"); - return PTR_ERR(chan); + return -ENODEV; } dma->chan = chan; diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c index 2a89dc7616bf..bbd7516f8195 100644 --- a/sound/soc/sprd/sprd-pcm-dma.c +++ b/sound/soc/sprd/sprd-pcm-dma.c @@ -177,7 +177,7 @@ static int sprd_pcm_request_dma_channel(struct snd_soc_component *component, dev_err(dev, "failed to request dma channel:%s\n", dma_params->chan_name[i]); sprd_pcm_release_dma_channel(substream); - return PTR_ERR(chan); + return -ENODEV; } data->chan = chan; } From 20b2f7f7562307abc70f932fe97147d175005935 Mon Sep 17 00:00:00 2001 From: Zain Aboobacker Date: Sat, 26 Sep 2026 12:25:33 -0400 Subject: [PATCH 377/924] ASoC: codecs: nau8825: fix typos in comments Fix various spelling mistakes in comments found by codespell. Assisted-by: Claude:claude-opus-5-5 codespell Signed-off-by: Zain Aboobacker Link: https://patch.msgid.link/20260926162533.25395-1-zainaboobacker33@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8825.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index 2eb0efc983a0..63d927f31409 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -348,7 +348,7 @@ static u32 nau8825_intlog10_dec3(u32 value) * @sig_org: original signal level * @sig_cros: cross talk signal level * - * The original and cross talk signal vlues need to be characterized. + * The original and cross talk signal values need to be characterized. * Once these values have been characterized, this sidetone value * can be converted to decibel with the equation below. * sidetone = 20 * log (original signal level / crosstalk signal level) @@ -725,7 +725,7 @@ static void nau8825_xtalk_measure(struct nau8825 *nau8825) break; case NAU8825_XTALK_IMM: /* In impedance measure state, the original and cross talk - * signal level vlues are ready. The side tone gain is deter- + * signal level values are ready. The side tone gain is deter- * mined with these signal level. After all, restore codec * configuration. */ @@ -771,7 +771,7 @@ static void nau8825_xtalk_work(struct work_struct *work) static void nau8825_xtalk_cancel(struct nau8825 *nau8825) { - /* If the crosstalk is eanbled and the process is on going, + /* If the crosstalk is enabled and the process is on going, * the driver forces to cancel the crosstalk task and * restores the configuration to original status. */ @@ -1303,7 +1303,7 @@ static int nau8825_hw_params(struct snd_pcm_substream *substream, NAU8825_CLK_ADC_SRC_MASK, osr->clk_src << NAU8825_CLK_ADC_SRC_SFT); - /* make BCLK and LRC divde configuration if the codec as master. */ + /* make BCLK and LRC divide configuration if the codec as master. */ regmap_read(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, &ctrl_val); if (ctrl_val & NAU8825_I2S_MS_MASTER) { /* get the bclk and fs ratio */ @@ -2897,7 +2897,7 @@ static int nau8825_i2c_probe(struct i2c_client *i2c) nau8825->dev = dev; nau8825->irq = i2c->irq; /* Initiate parameters, semaphore and work queue which are needed in - * cross talk suppression measurment function. + * cross talk suppression measurement function. */ nau8825->xtalk_state = NAU8825_XTALK_DONE; nau8825->xtalk_protect = false; From a5af82f2077dd1af7cfebd718eb5c77902f032c4 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 12 Sep 2026 14:49:45 +0200 Subject: [PATCH 378/924] ALSA: seq: Mark OSS sequencer emulation as deprecated OSS sequencer API emulation has been implemented as a client of ALSA sequencer, decades ago, at the time where OSS was still use. But the API itself became very niche, and almost noone using it for a real purpose any longer -- only exceptions are AI and fuzzers, who have been causing significant maintenance burden for issues they hit & found. Meanwhile, the most features of OSS sequencer API emulation can be achieved in user-space, either by ALSA-OSS layer (aoss) or oss-seq-emu program: https://github.com/alsa-project/alsa-oss https://github.com/tiwai/oss-seq-emu where the former is LD_PRELOAD wrapper and the latter is a CUSE-based daemon. So we can drop the stuff from the kernel sooner or later. Now, let's mark the OSS sequencer emulation as deprecated and give a warning when loaded, so that we can drop it later actually. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260912124951.416761-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/Kconfig | 7 +++++-- sound/core/seq/oss/seq_oss.c | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig index e4f58cb985d4..0d606159103e 100644 --- a/sound/core/seq/Kconfig +++ b/sound/core/seq/Kconfig @@ -27,14 +27,17 @@ config SND_SEQ_DUMMY will be called snd-seq-dummy. config SND_SEQUENCER_OSS - tristate "OSS Sequencer API" + tristate "OSS Sequencer API (deprecated)" depends on SND_OSSEMUL select SND_SEQ_MIDI_EVENT help Say Y here to enable OSS sequencer emulation (both /dev/sequencer and /dev/music interfaces). - Many programs still use the OSS API, so say Y. + The OSS sequencer API is almost nowhere used, hence this option will + be dropped soon. + Use user-space wrapper (alsa-oss) or CUSE daemon (oss-seq-emu) + instead of the kernel-side emulation. To compile this driver as a module, choose M here: the module will be called snd-seq-oss. diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c index 2835576040ed..64a7e62029cc 100644 --- a/sound/core/seq/oss/seq_oss.c +++ b/sound/core/seq/oss/seq_oss.c @@ -67,6 +67,9 @@ static int __init alsa_seq_oss_init(void) { int rc; + pr_warn("seq-oss: kernel OSS sequencer emulation is deprecated\n"); + pr_warn("seq-oss: Use user-space solution (alsa-oss or oss-seq-emu) instead.\n"); + rc = register_device(); if (rc < 0) goto error; From 3109ede2c311a822277c8a7bf9c253f7c65fcbea Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:16:10 +0200 Subject: [PATCH 379/924] ALSA: hda: Fix struct hdac_bus documentation Spelling fix so the description makes sense. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260918111610.3589324-1-cezary.rojewski@intel.com Signed-off-by: Takashi Iwai --- sound/hda/core/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/core/bus.c b/sound/hda/core/bus.c index 81498f1e413e..20fe1c4a2977 100644 --- a/sound/hda/core/bus.c +++ b/sound/hda/core/bus.c @@ -21,7 +21,7 @@ static const struct hdac_bus_ops default_ops = { }; /** - * snd_hdac_bus_init - initialize a HD-audio bas bus + * snd_hdac_bus_init - initialize a HD-audio bus device * @bus: the pointer to bus object * @dev: device pointer * @ops: bus verb operators From fc6b1d74fff198568251e48f77a0db6b888f2695 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 18 Sep 2026 22:15:20 +0800 Subject: [PATCH 380/924] ALSA/ASoC: use assign_bit() where applicable Convert open-coded if/else with set_bit/clear_bit and their non-atomic __set_bit/__clear_bit variants to the assign_bit/__assign_bit API. Done with Coccinelle semantic patch: // set_bit -> clear_bit => assign_bit @@ expression cond, bit, addr; @@ -if (cond) - set_bit(bit, addr); -else - clear_bit(bit, addr); +assign_bit(bit, addr, cond); // clear_bit -> set_bit => assign_bit @@ expression cond, bit, addr; @@ -if (cond) - clear_bit(bit, addr); -else - set_bit(bit, addr); +assign_bit(bit, addr, !cond); // __set_bit -> __clear_bit => __assign_bit @@ expression cond, bit, addr; @@ -if (cond) - __set_bit(bit, addr); -else - __clear_bit(bit, addr); +__assign_bit(bit, addr, cond); // __clear_bit -> __set_bit => __assign_bit @@ expression cond, bit, addr; @@ -if (cond) - __clear_bit(bit, addr); -else - __set_bit(bit, addr); +__assign_bit(bit, addr, !cond); Signed-off-by: Peng Fan Acked-by: Mark Brown Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260918141532.3022761-1-peng.fan@oss.nxp.com --- sound/drivers/serial-generic.c | 10 ++-------- sound/hda/core/component.c | 5 +---- sound/hda/core/controller.c | 5 +---- sound/soc/codecs/tas675x.c | 10 ++-------- sound/usb/midi.c | 5 +---- 5 files changed, 7 insertions(+), 28 deletions(-) diff --git a/sound/drivers/serial-generic.c b/sound/drivers/serial-generic.c index 766206c6ca75..8ba5e35af4a1 100644 --- a/sound/drivers/serial-generic.c +++ b/sound/drivers/serial-generic.c @@ -183,10 +183,7 @@ static void snd_serial_generic_input_trigger(struct snd_rawmidi_substream *subst { struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; - if (up) - set_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode); - else - clear_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode); + assign_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode, up); } static int snd_serial_generic_output_open(struct snd_rawmidi_substream *substream) @@ -230,10 +227,7 @@ static void snd_serial_generic_output_trigger(struct snd_rawmidi_substream *subs { struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; - if (up) - set_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode); - else - clear_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode); + assign_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode, up); if (up) snd_serial_generic_tx_wakeup(drvdata); diff --git a/sound/hda/core/component.c b/sound/hda/core/component.c index 04755903880e..83b31186b027 100644 --- a/sound/hda/core/component.c +++ b/sound/hda/core/component.c @@ -70,10 +70,7 @@ void snd_hdac_display_power(struct hdac_bus *bus, unsigned int idx, bool enable) dev_dbg(bus->dev, "display power %s\n", str_enable_disable(enable)); guard(mutex)(&bus->lock); - if (enable) - set_bit(idx, &bus->display_power_status); - else - clear_bit(idx, &bus->display_power_status); + assign_bit(idx, &bus->display_power_status, enable); if (!acomp || !acomp->ops) return; diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c index 78855ac357c6..e8d40c24ed00 100644 --- a/sound/hda/core/controller.c +++ b/sound/hda/core/controller.c @@ -781,9 +781,6 @@ EXPORT_SYMBOL_GPL(snd_hdac_bus_free_stream_pages); */ void snd_hdac_bus_link_power(struct hdac_device *codec, bool enable) { - if (enable) - set_bit(codec->addr, &codec->bus->codec_powered); - else - clear_bit(codec->addr, &codec->bus->codec_powered); + assign_bit(codec->addr, &codec->bus->codec_powered, enable); } EXPORT_SYMBOL_GPL(snd_hdac_bus_link_power); diff --git a/sound/soc/codecs/tas675x.c b/sound/soc/codecs/tas675x.c index 404706b62156..82eeaceb1dac 100644 --- a/sound/soc/codecs/tas675x.c +++ b/sound/soc/codecs/tas675x.c @@ -1379,10 +1379,7 @@ static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction) int ret; if (direction == SNDRV_PCM_STREAM_CAPTURE) { - if (mute) - clear_bit(dai->id, &tas->active_capture_dais); - else - set_bit(dai->id, &tas->active_capture_dais); + assign_bit(dai->id, &tas->active_capture_dais, !mute); return 0; } @@ -1391,10 +1388,7 @@ static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction) * The TAS675x has two playback DAIs (main audio and LLP). * Only transition to SLEEP when ALL are muted. */ - if (mute) - clear_bit(dai->id, &tas->active_playback_dais); - else - set_bit(dai->id, &tas->active_playback_dais); + assign_bit(dai->id, &tas->active_playback_dais, !mute); /* Last playback stream */ if (mute && !READ_ONCE(tas->active_playback_dais)) { diff --git a/sound/usb/midi.c b/sound/usb/midi.c index be2b178d13d6..be29379cfcb9 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -1280,10 +1280,7 @@ static void snd_usbmidi_input_trigger(struct snd_rawmidi_substream *substream, { struct snd_usb_midi *umidi = substream->rmidi->private_data; - if (up) - set_bit(substream->number, &umidi->input_triggered); - else - clear_bit(substream->number, &umidi->input_triggered); + assign_bit(substream->number, &umidi->input_triggered, up); } static const struct snd_rawmidi_ops snd_usbmidi_output_ops = { From 3e71ea6dca1d585692b645aaf3b8cc58f6e5eb35 Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Mon, 28 Sep 2026 04:45:13 +0000 Subject: [PATCH 381/924] firmware: cs_dsp: Annotate struct cs_dsp_coeff_ctl with __counted_by_ptr Annotate the 'cache' pointer member of 'struct cs_dsp_coeff_ctl' with the '__counted_by_ptr' attribute. This allows the compiler and KASAN to perform run-time bounds checking on accesses to the 'cache' buffer, preventing potential out-of-bounds reads or writes. The 'cache' pointer points to a buffer of size 'len' bytes, allocated to hold the cached value of a DSP coefficient control. The 'cache' and 'len' are initialized in 'cs_dsp_create_control()'. Every subsequent access to 'ctl->cache' is strictly validated to ensure that it lies within the bounds of 'ctl->len'. Cc: codemender-patching+linux@google.com Assisted-by: LLM Signed-off-by: Bill Wendling Reviewed-by: Richard Fitzgerald Link: https://patch.msgid.link/20260928044513.1330392-1-morbo@google.com Signed-off-by: Mark Brown --- include/linux/firmware/cirrus/cs_dsp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/firmware/cirrus/cs_dsp.h b/include/linux/firmware/cirrus/cs_dsp.h index 4e3baa557068..6aa1e1b2b4a5 100644 --- a/include/linux/firmware/cirrus/cs_dsp.h +++ b/include/linux/firmware/cirrus/cs_dsp.h @@ -96,7 +96,7 @@ struct cs_dsp_alg_region { struct cs_dsp_coeff_ctl { struct list_head list; struct cs_dsp *dsp; - void *cache; + void *cache __counted_by_ptr(len); const char *fw_name; /* Subname is needed to match with firmware */ const char *subname; From c257e3bcb36f4597d6dbb6fbc546e466ef53e385 Mon Sep 17 00:00:00 2001 From: Yibo Tan Date: Wed, 23 Sep 2026 02:50:56 +0800 Subject: [PATCH 382/924] ASoC: amd: acp-es8336: Use an owned codec device reference acpi_get_first_physical_node() returns a borrowed device pointer. If the pa-enable GPIO lookup fails, st_es8336_late_probe() puts that pointer despite not owning a reference. A later physical-node teardown can then release the device while device_del() is still using it. This was reproduced on current mainline with the real static late-probe callback and normal platform-device unregister. The GPIO lookup returned -EPROBE_DEFER and KASAN reported a slab-use-after-free in device_del(), with the object freed by acpi_unbind_one(). Use acpi_bus_get_primary_device(), which obtains a stable device reference under the physical-node lock, and release it at callback exit with scoped cleanup. Keep the reference callback-local because late probe can be retried; registering one devres action per attempt would accumulate references. The same KASAN guest with this change reached the same -EPROBE_DEFER and unregister path without KASAN, WARNING, Oops or panic. The test directly invoked the production callback and did not emulate a complete ASoC card or physical AMD hardware. Fixes: 02527c3f2300 ("ASoC: amd: add Machine driver for Jadeite platform") Signed-off-by: Yibo Tan Link: https://patch.msgid.link/20260922185057.954150-2-lhfff@tju.edu.cn Signed-off-by: Mark Brown --- sound/soc/amd/acp-es8336.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sound/soc/amd/acp-es8336.c b/sound/soc/amd/acp-es8336.c index 9f3f11256788..0cb0ee76191d 100644 --- a/sound/soc/amd/acp-es8336.c +++ b/sound/soc/amd/acp-es8336.c @@ -30,7 +30,6 @@ static unsigned long acp2x_machine_id; static struct snd_soc_jack st_jack; -static struct device *codec_dev; static struct gpio_desc *gpio_pa; static int sof_es8316_speaker_power_event(struct snd_soc_dapm_widget *w, @@ -191,6 +190,7 @@ static const struct acpi_gpio_mapping acpi_es8336_gpios[] = { static int st_es8336_late_probe(struct snd_soc_card *card) { + struct device *codec_dev __free(put_device) = NULL; struct acpi_device *adev; int ret; @@ -198,7 +198,7 @@ static int st_es8336_late_probe(struct snd_soc_card *card) if (!adev) return -ENODEV; - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) { dev_err(card->dev, "can not find codec dev\n"); @@ -213,7 +213,6 @@ static int st_es8336_late_probe(struct snd_soc_card *card) if (IS_ERR(gpio_pa)) { ret = dev_err_probe(card->dev, PTR_ERR(gpio_pa), "could not get pa-enable GPIO\n"); - put_device(codec_dev); return ret; } return 0; From 095025078dc3a1b4f0fcdc8b8d9dcb4d192a2a14 Mon Sep 17 00:00:00 2001 From: Yibo Tan Date: Wed, 23 Sep 2026 02:50:57 +0800 Subject: [PATCH 383/924] ASoC: amd: acp3x-es83xx: Keep an owned codec device reference acpi_get_first_physical_node() returns a borrowed device pointer. The private data allocation failure path in acp3x_es83xx_probe() puts that pointer despite not owning a reference. The successful path also saves the borrowed pointer for later card operations. This was reproduced on current mainline with failslab restricted to the real static callback. The rejected devm_kzalloc() returned -ENOMEM, and normal codec platform-device unregister then produced a KASAN slab-use-after-free in device_del(), with allocation in acpi_create_platform_device() and release in acpi_unbind_one(). Use acpi_bus_get_primary_device() to acquire the reference while the physical-node lock is held. After allocating private data, register a devres put action before publishing the pointer. This balances action allocation and later probe failures, keeps the saved pointer alive during card use, and drops the reference after ASoC card unregister during successful teardown. The existing OOM put now correctly balances the owned lookup. The same filtered KASAN guest with this change reached the same -ENOMEM and unregister path without KASAN, WARNING, Oops or panic. The test directly invoked the production callback and did not emulate a complete ACP/ASoC card or physical Huawei hardware. Fixes: 54fcd9dd44b2 ("ASoC: amd: acp: Add machine driver that enables sound for systems with a ES8336 codec") Signed-off-by: Yibo Tan Link: https://patch.msgid.link/20260922185057.954150-3-lhfff@tju.edu.cn Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c index 3a640e652314..eb0e9a6af6ef 100644 --- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,11 @@ struct acp3x_es83xx_private { struct snd_soc_dapm_route mic_map[2]; }; +static void acp3x_es83xx_put_codec_device(void *data) +{ + put_device(data); +} + static const unsigned int channels[] = { DUAL_CHANNEL, }; @@ -428,7 +434,7 @@ static int acp3x_es83xx_probe(struct snd_soc_card *card) return -ENXIO; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) { dev_warn(dev, "Error cannot find codec device, will defer probe\n"); @@ -441,6 +447,12 @@ static int acp3x_es83xx_probe(struct snd_soc_card *card) return -ENOMEM; } + ret = devm_add_action_or_reset(dev, + acp3x_es83xx_put_codec_device, + codec_dev); + if (ret) + return ret; + priv->codec_dev = codec_dev; priv->quirk = (unsigned long)dmi_id->driver_data; acp_drvdata->mach_priv = priv; From 3e9c6464dadd52334abc22bc089cc5307d46314a Mon Sep 17 00:00:00 2001 From: Alireza Feyzabadi Farahani Date: Sun, 27 Sep 2026 06:44:30 +0000 Subject: [PATCH 384/924] selftests: ALSA: Skip timer tests when device is unavailable When CONFIG_SND is disabled, opening /dev/snd/timer fails and the userspace timer tests report failures even though the test environment does not support ALSA timers. Skip the affected tests when opening the timer device fails because the device is unavailable. Continue to report unexpected open failures as test failures. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=218479 Signed-off-by: Alireza Feyzabadi Farahani Reviewed-by: Mark Brown Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260927064430.2112688-1-arfa79lg@gmail.com --- tools/testing/selftests/alsa/utimer-test.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/testing/selftests/alsa/utimer-test.c b/tools/testing/selftests/alsa/utimer-test.c index 1a9ff010cb11..07aeaa777649 100644 --- a/tools/testing/selftests/alsa/utimer-test.c +++ b/tools/testing/selftests/alsa/utimer-test.c @@ -51,6 +51,9 @@ FIXTURE_SETUP(timer_f) { self->utimer_info->resolution = (NANO / FRAME_RATE * PERIOD_SIZE); timer_dev_fd = open("/dev/snd/timer", O_RDONLY); + if (timer_dev_fd < 0 && + (errno == ENOENT || errno == ENODEV || errno == ENXIO)) + SKIP(return, "ALSA timer device unavailable"); ASSERT_GE(timer_dev_fd, 0); if (ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info) < 0) { @@ -156,6 +159,9 @@ TEST(wrong_timers_test) { }; timer_dev_fd = open("/dev/snd/timer", O_RDONLY); + if (timer_dev_fd < 0 && + (errno == ENOENT || errno == ENODEV || errno == ENXIO)) + SKIP(return, "ALSA timer device unavailable"); ASSERT_GE(timer_dev_fd, 0); utimer_fd = ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, &wrong_timer); From 9332af5220d42341bb1446a62c545c0518e990ef Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Sun, 27 Sep 2026 17:38:09 +0800 Subject: [PATCH 385/924] ALSA: mpu401: shut down timer before freeing MPU When an MPU401 interface has no usable IRQ, the driver polls raw-MIDI input and output with a timer. During raw-MIDI teardown, the timer callback can already be running when snd_mpu401_uart_remove_timer() clears the last mode bit and calls timer_delete(). That API neither waits for a running callback nor prevents it from rearming the timer, and free_irq() does not synchronize timer callbacks. snd_mpu401_uart_free() can therefore release mpu while the timer callback still uses it. Initialize the timer once when the MPU object is created and only rearm it while a timer mode is active. Shut it down synchronously after freeing the IRQ so an in-flight callback completes before mpu and its resources are released. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260927093809.681726-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/drivers/mpu401/mpu401_uart.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/drivers/mpu401/mpu401_uart.c b/sound/drivers/mpu401/mpu401_uart.c index 6de03d72a908..fcf2b686f9f5 100644 --- a/sound/drivers/mpu401/mpu401_uart.c +++ b/sound/drivers/mpu401/mpu401_uart.c @@ -155,20 +155,20 @@ static void snd_mpu401_uart_timer(struct timer_list *t) scoped_guard(spinlock_irqsave, &mpu->timer_lock) { /*mpu->mode |= MPU401_MODE_TIMER;*/ - mod_timer(&mpu->timer, 1 + jiffies); + if (mpu->timer_invoked) + mod_timer(&mpu->timer, 1 + jiffies); } if (mpu->rmidi) _snd_mpu401_uart_interrupt(mpu); } /* - * initialize the timer callback if not programmed yet + * arm the timer if it is not already active */ static void snd_mpu401_uart_add_timer (struct snd_mpu401 *mpu, int input) { guard(spinlock_irqsave)(&mpu->timer_lock); if (mpu->timer_invoked == 0) { - timer_setup(&mpu->timer, snd_mpu401_uart_timer, 0); mod_timer(&mpu->timer, 1 + jiffies); } mpu->timer_invoked |= input ? MPU401_MODE_INPUT_TIMER : @@ -474,6 +474,7 @@ static void snd_mpu401_uart_free(struct snd_rawmidi *rmidi) struct snd_mpu401 *mpu = rmidi->private_data; if (mpu->irq >= 0) free_irq(mpu->irq, (void *) mpu); + timer_shutdown_sync(&mpu->timer); release_and_free_resource(mpu->res); kfree(mpu); } @@ -528,6 +529,7 @@ int snd_mpu401_uart_new(struct snd_card *card, int device, spin_lock_init(&mpu->input_lock); spin_lock_init(&mpu->output_lock); spin_lock_init(&mpu->timer_lock); + timer_setup(&mpu->timer, snd_mpu401_uart_timer, 0); mpu->hardware = hardware; mpu->irq = -1; mpu->rmidi = rmidi; From 5bdc232c3094304a7e17df5ddb66dcdacbead6bf Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Sun, 27 Sep 2026 20:17:09 +0800 Subject: [PATCH 386/924] ALSA: oxygen: use helper to activate the SPDIF PCM control oxygen_open() and oxygen_close() modify the SPDIF PCM control access flags while holding chip->mutex. Direct access changes bypass the control core locking and notification path. Move the access transition outside the driver mutex and use snd_ctl_activate_id(), which serializes the control update with the control core. Fixes: d0ce9946c52e ("[ALSA] add CMI8788 driver") Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260927121709.742840-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/pci/oxygen/oxygen_pcm.c | 37 +++++++++++++++-------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/sound/pci/oxygen/oxygen_pcm.c b/sound/pci/oxygen/oxygen_pcm.c index b716356010b8..75efbf59a675 100644 --- a/sound/pci/oxygen/oxygen_pcm.c +++ b/sound/pci/oxygen/oxygen_pcm.c @@ -171,16 +171,14 @@ static int oxygen_open(struct snd_pcm_substream *substream, snd_pcm_set_sync(substream); chip->streams[channel] = substream; - guard(mutex)(&chip->mutex); - chip->pcm_active |= 1 << channel; - if (channel == PCM_SPDIF) { - chip->spdif_pcm_bits = chip->spdif_bits; - chip->controls[CONTROL_SPDIF_PCM]->vd[0].access &= - ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | - SNDRV_CTL_EVENT_MASK_INFO, - &chip->controls[CONTROL_SPDIF_PCM]->id); + scoped_guard(mutex, &chip->mutex) { + chip->pcm_active |= 1 << channel; + if (channel == PCM_SPDIF) + chip->spdif_pcm_bits = chip->spdif_bits; } + if (channel == PCM_SPDIF) + snd_ctl_activate_id(chip->card, + &chip->controls[CONTROL_SPDIF_PCM]->id, 1); return 0; } @@ -220,19 +218,16 @@ static int oxygen_close(struct snd_pcm_substream *substream) struct oxygen *chip = snd_pcm_substream_chip(substream); unsigned int channel = oxygen_substream_channel(substream); - guard(mutex)(&chip->mutex); - chip->pcm_active &= ~(1 << channel); - if (channel == PCM_SPDIF) { - chip->controls[CONTROL_SPDIF_PCM]->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | - SNDRV_CTL_EVENT_MASK_INFO, - &chip->controls[CONTROL_SPDIF_PCM]->id); - } - if (channel == PCM_SPDIF || channel == PCM_MULTICH) - oxygen_update_spdif_source(chip); + scoped_guard(mutex, &chip->mutex) { + chip->pcm_active &= ~(1 << channel); + if (channel == PCM_SPDIF || channel == PCM_MULTICH) + oxygen_update_spdif_source(chip); - chip->streams[channel] = NULL; + chip->streams[channel] = NULL; + } + if (channel == PCM_SPDIF) + snd_ctl_activate_id(chip->card, + &chip->controls[CONTROL_SPDIF_PCM]->id, 0); return 0; } From aee7c43126e3184d7eefde6545b3b88628e5591a Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Sun, 27 Sep 2026 22:14:47 +0800 Subject: [PATCH 387/924] ALSA: via82xx: use helper to activate DXS controls The VIA DXS playback callbacks modify control access flags directly when a stream opens or closes. This bypasses the control core locking and notification path. Use snd_ctl_activate_id() for both transitions so access changes are serialized by the control core and reported with the expected info event. Fixes: 3d00941371a7 ("sound: via82xx: deactivate DXS controls of inactive streams") Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260927141447.788664-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/pci/via82xx.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index 24ee1302f27d..58816dea4996 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -1242,11 +1242,7 @@ static int snd_via8233_playback_open(struct snd_pcm_substream *substream) VIA_DXS_MAX_VOLUME - (dxs_init_volume & 31); chip->playback_volume[stream][1] = VIA_DXS_MAX_VOLUME - (dxs_init_volume & 31); - chip->dxs_controls[stream]->vd[0].access &= - ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | - SNDRV_CTL_EVENT_MASK_INFO, - &chip->dxs_controls[stream]->id); + snd_ctl_activate_id(chip->card, &chip->dxs_controls[stream]->id, 1); } return 0; } @@ -1333,10 +1329,7 @@ static int snd_via8233_playback_close(struct snd_pcm_substream *substream) stream = viadev->reg_offset / 0x10; if (chip->dxs_controls[stream]) { - chip->dxs_controls[stream]->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, - &chip->dxs_controls[stream]->id); + snd_ctl_activate_id(chip->card, &chip->dxs_controls[stream]->id, 0); } return snd_via82xx_pcm_close(substream); } From 33be70ba410598f71996bdd061194ae039a28eca Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 28 Sep 2026 09:18:23 +0700 Subject: [PATCH 388/924] ALSA: dummy: Move pcm_substreams check out of the loop The pcm_substreams[dev] range check does not depend on the loop index. Do it once before the loop instead of on every iteration. No functional change. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260928021823.6517-1-phucduc.bui@gmail.com Signed-off-by: Takashi Iwai --- sound/drivers/dummy.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index b908d2564aee..4df69b9712c5 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -1063,11 +1063,13 @@ static int snd_dummy_probe(struct platform_device *devptr) break; } } + + if (pcm_substreams[dev] < 1) + pcm_substreams[dev] = 1; + if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS) + pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; + for (idx = 0; idx < MAX_PCM_DEVICES && idx < pcm_devs[dev]; idx++) { - if (pcm_substreams[dev] < 1) - pcm_substreams[dev] = 1; - if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS) - pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; err = snd_card_dummy_pcm(dummy, idx, pcm_substreams[dev]); if (err < 0) return err; From 37e117c23563d6428a97395c23aa9af196cdbda4 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:41:15 +0000 Subject: [PATCH 389/924] ASoC: soc-dai: use snd_soc_dai_stream_active() We already have snd_soc_dai_stream_active(). Let's use it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87o6dvjpz9.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-dai.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 83d030e14bb0..0cf70f0903dc 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -688,7 +688,7 @@ int snd_soc_dai_active(const struct snd_soc_dai *dai) active = 0; for_each_pcm_streams(stream) - active += dai->stream[stream].active; + active += snd_soc_dai_stream_active(dai, stream); return active; } From d4c557f621dbfffa121dedfa421d79667ff396ad Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:41:53 +0000 Subject: [PATCH 390/924] ASoC: soc-dai: add snd_soc_dai_id() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. Add snd_soc_dai_id() to get ID from DAI. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87mrtfjpy6.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 1 + sound/soc/soc-dai.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 7e64e049d71d..02b52fed97c9 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -283,6 +283,7 @@ int snd_soc_dai_matches_args(const struct snd_soc_dai *dai, int snd_soc_dai_matches_dlc(struct snd_soc_dai *dai, const struct snd_soc_dai_link_component *dlc); const char *snd_soc_dai_name(const struct snd_soc_dai *dai); +int snd_soc_dai_id(const struct snd_soc_dai *dai); struct snd_soc_dai_ops { /* DAI driver callbacks */ diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 0cf70f0903dc..71de29bfbbd0 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1073,6 +1073,12 @@ const char *snd_soc_dai_name(const struct snd_soc_dai *dai) } EXPORT_SYMBOL_GPL(snd_soc_dai_name); +int snd_soc_dai_id(const struct snd_soc_dai *dai) +{ + return dai->id; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_id); + const struct snd_soc_pcm_stream * snd_soc_dai_pcm_stream_get_i(const struct snd_soc_dai *dai, int stream) { From 724da1c8b6302ff27b260d9d8b85ba63289f697e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:43:10 +0000 Subject: [PATCH 391/924] ASoC: soc-dai: add snd_soc_dai_to_component() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. Add snd_soc_dai_to_component() to get component from DAI. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87ld8zjpw1.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 1 + sound/soc/soc-dai.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 02b52fed97c9..7f8a64db0dc7 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -212,6 +212,7 @@ int snd_soc_dai_get_channel_map(const struct snd_soc_dai *dai, unsigned int *tx_num, unsigned int *tx_slot, unsigned int *rx_num, unsigned int *rx_slot); +struct snd_soc_component *snd_soc_dai_to_component(const struct snd_soc_dai *dai); int snd_soc_dai_is_dummy(const struct snd_soc_dai *dai); int snd_soc_dai_add_controls(struct snd_soc_dai *dai, const struct snd_kcontrol_new *controls, int num_controls); diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 71de29bfbbd0..a859acc4b28f 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -20,6 +20,12 @@ static inline int _soc_dai_ret(const struct snd_soc_dai *dai, "at %s() on %s\n", func, dai->name); } +struct snd_soc_component *snd_soc_dai_to_component(const struct snd_soc_dai *dai) +{ + return dai->component; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_to_component); + /* * We might want to check substream by using list. * In such case, we can update these macros. From e90c0220073862ccf66ea187d305971ba796e088 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:43:16 +0000 Subject: [PATCH 392/924] ASoC: soc-dai: add snd_soc_dai_to_driver() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. Add snd_soc_dai_to_driver() to get driver from DAI. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87jyojjpvv.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 1 + sound/soc/soc-dai.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 7f8a64db0dc7..375a8674d145 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -213,6 +213,7 @@ int snd_soc_dai_get_channel_map(const struct snd_soc_dai *dai, unsigned int *rx_num, unsigned int *rx_slot); struct snd_soc_component *snd_soc_dai_to_component(const struct snd_soc_dai *dai); +struct snd_soc_dai_driver *snd_soc_dai_to_driver(const struct snd_soc_dai *dai); int snd_soc_dai_is_dummy(const struct snd_soc_dai *dai); int snd_soc_dai_add_controls(struct snd_soc_dai *dai, const struct snd_kcontrol_new *controls, int num_controls); diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index a859acc4b28f..3e553e8c0535 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -26,6 +26,12 @@ struct snd_soc_component *snd_soc_dai_to_component(const struct snd_soc_dai *dai } EXPORT_SYMBOL_GPL(snd_soc_dai_to_component); +struct snd_soc_dai_driver *snd_soc_dai_to_driver(const struct snd_soc_dai *dai) +{ + return dai->driver; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_to_driver); + /* * We might want to check substream by using list. * In such case, we can update these macros. From f54b3dd4aa3519ef930faa3a6ba6d1c5d719ccf6 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:43:27 +0000 Subject: [PATCH 393/924] ASoC: soc-dai: add snd_soc_dai_{to/from}_list() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. Add snd_soc_dai_{to/from}_list() to use for_each macro. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87ik43jpvk.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 14 ++++++++++---- include/sound/soc-dai.h | 2 ++ sound/soc/soc-dai.c | 12 ++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index c49b59630101..6c1acc984ecb 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -258,10 +258,16 @@ struct snd_soc_component { void *priv; }; -#define for_each_component_dais(component, dai)\ - list_for_each_entry(dai, &(component)->dai_list, list) -#define for_each_component_dais_safe(component, dai, _dai)\ - list_for_each_entry_safe(dai, _dai, &(component)->dai_list, list) +#define for_each_component_dais(component, dai) \ + for (dai = snd_soc_dai_from_list((component)->dai_list.next); \ + snd_soc_dai_to_list(dai) != &(component)->dai_list; \ + dai = snd_soc_dai_from_list(snd_soc_dai_to_list(dai)->next)) + +#define for_each_component_dais_safe(component, dai, _dai) \ + for (dai = snd_soc_dai_from_list((component)->dai_list.next), \ + _dai = snd_soc_dai_from_list(snd_soc_dai_to_list(dai)->next); \ + snd_soc_dai_to_list(dai) != &(component)->dai_list; \ + dai = _dai, _dai = snd_soc_dai_from_list(snd_soc_dai_to_list(_dai)->next)) /** * snd_soc_component_to_dapm() - Returns the DAPM context associated with a diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 375a8674d145..bd714c3bbf3d 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -561,6 +561,8 @@ struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, bool legacy_dai_naming); void snd_soc_dai_unregister(struct snd_soc_dai *dai); +struct snd_soc_dai *snd_soc_dai_from_list(struct list_head *list); +struct list_head *snd_soc_dai_to_list(struct snd_soc_dai *dai); /* REMOVE ME */ #define snd_soc_dai_get_pcm_stream snd_soc_dai_pcm_stream_get diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 3e553e8c0535..69a3df3c56a8 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -32,6 +32,18 @@ struct snd_soc_dai_driver *snd_soc_dai_to_driver(const struct snd_soc_dai *dai) } EXPORT_SYMBOL_GPL(snd_soc_dai_to_driver); +struct snd_soc_dai *snd_soc_dai_from_list(struct list_head *list) +{ + return list_entry(list, struct snd_soc_dai, list); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_from_list); + +struct list_head *snd_soc_dai_to_list(struct snd_soc_dai *dai) +{ + return &dai->list; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_to_list); + /* * We might want to check substream by using list. * In such case, we can update these macros. From ee4f730ac8f76d89ca529f04794921872f0beb4f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:44:07 +0000 Subject: [PATCH 394/924] ASoC: soc-dai: add snd_soc_dai_get_symmetric_xxx() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. Add snd_soc_dai_get_symmetric_xxx() to get symmetric param from DAI. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87h5jnjpug.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 3 +++ sound/soc/soc-dai.c | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index bd714c3bbf3d..fc23221766b6 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -563,6 +563,9 @@ struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, void snd_soc_dai_unregister(struct snd_soc_dai *dai); struct snd_soc_dai *snd_soc_dai_from_list(struct list_head *list); struct list_head *snd_soc_dai_to_list(struct snd_soc_dai *dai); +unsigned int snd_soc_dai_get_symmetric_rate(struct snd_soc_dai *dai); +unsigned int snd_soc_dai_get_symmetric_channels(struct snd_soc_dai *dai); +unsigned int snd_soc_dai_get_symmetric_sample_bits(struct snd_soc_dai *dai); /* REMOVE ME */ #define snd_soc_dai_get_pcm_stream snd_soc_dai_pcm_stream_get diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 69a3df3c56a8..9787d7913c30 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1263,6 +1263,24 @@ struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, } EXPORT_SYMBOL_GPL(snd_soc_dai_register); +unsigned int snd_soc_dai_get_symmetric_rate(struct snd_soc_dai *dai) +{ + return dai->symmetric_rate; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_symmetric_rate); + +unsigned int snd_soc_dai_get_symmetric_channels(struct snd_soc_dai *dai) +{ + return dai->symmetric_channels; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_symmetric_channels); + +unsigned int snd_soc_dai_get_symmetric_sample_bits(struct snd_soc_dai *dai) +{ + return dai->symmetric_sample_bits; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_symmetric_sample_bits); + void snd_soc_dai_symmetric_set_params(struct snd_soc_dai *dai, struct snd_pcm_hw_params *params) { From 9ec8ccc97dd9bd089078574e0eab859c9ce95456 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:45:07 +0000 Subject: [PATCH 395/924] ASoC: soc-dai: add snd_soc_dai_{set/to}_priv() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. DAI priv has been mainly referred from SoundWire driver. Let's add snd_soc_dai_{set/to}_priv() for it. Signed-off-by: Kuninori Morimoto Reviewed-by: Charles Keepax Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87fqz7jpst.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 2 ++ sound/soc/sdca/sdca_asoc.c | 4 ++-- sound/soc/soc-dai.c | 12 ++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index fc23221766b6..be4781b61989 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -566,6 +566,8 @@ struct list_head *snd_soc_dai_to_list(struct snd_soc_dai *dai); unsigned int snd_soc_dai_get_symmetric_rate(struct snd_soc_dai *dai); unsigned int snd_soc_dai_get_symmetric_channels(struct snd_soc_dai *dai); unsigned int snd_soc_dai_get_symmetric_sample_bits(struct snd_soc_dai *dai); +void snd_soc_dai_set_priv(struct snd_soc_dai *dai, void *priv); +void *snd_soc_dai_to_priv(struct snd_soc_dai *dai); /* REMOVE ME */ #define snd_soc_dai_get_pcm_stream snd_soc_dai_pcm_stream_get diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index ce2e7c270765..ccc579f878ff 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -1507,7 +1507,7 @@ int sdca_asoc_set_constraints(struct device *dev, struct regmap *regmap, return ret; } - dai->priv = constraint; + snd_soc_dai_set_priv(dai, constraint); return 0; } @@ -1523,7 +1523,7 @@ EXPORT_SYMBOL_NS(sdca_asoc_set_constraints, "SND_SOC_SDCA"); void sdca_asoc_free_constraints(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_pcm_hw_constraint_list *constraint = dai->priv; + struct snd_pcm_hw_constraint_list *constraint = snd_soc_dai_to_priv(dai); kfree(constraint); } diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 9787d7913c30..7412294eaea4 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -32,6 +32,18 @@ struct snd_soc_dai_driver *snd_soc_dai_to_driver(const struct snd_soc_dai *dai) } EXPORT_SYMBOL_GPL(snd_soc_dai_to_driver); +void snd_soc_dai_set_priv(struct snd_soc_dai *dai, void *priv) +{ + dai->priv = priv; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_priv); + +void *snd_soc_dai_to_priv(struct snd_soc_dai *dai) +{ + return dai->priv; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_to_priv); + struct snd_soc_dai *snd_soc_dai_from_list(struct list_head *list) { return list_entry(list, struct snd_soc_dai, list); From d47f591c9a8d39b79cbce24055f78dfa8153bf11 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:45:51 +0000 Subject: [PATCH 396/924] ASoC: soc-dai: add snd_soc_dai_get_bclk[_ratio]() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. Add snd_soc_dai_get_bclk[_ratio]() for internal use. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87ecerjprk.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-dai.c | 10 ++++++++++ sound/soc/soc-internal.h | 2 ++ sound/soc/soc-pcm.c | 19 +++++++++++++------ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 7412294eaea4..edf7d808e46e 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1391,3 +1391,13 @@ void snd_soc_dai_symmetric_update(struct snd_pcm_substream *substream) if (symmetry) substream->runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; } + +struct clk *snd_soc_dai_get_bclk(struct snd_soc_dai *dai) +{ + return dai->bclk; +} + +unsigned int snd_soc_dai_get_bclk_ratio(struct snd_soc_dai *dai) +{ + return dai->bclk_ratio; +} diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 3afc50d5760e..f350fe5280e3 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -32,5 +32,7 @@ void snd_soc_dai_symmetric_set_params(struct snd_soc_dai *dai, int snd_soc_dai_symmetric_apply(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); int snd_soc_dai_symmetric_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); void snd_soc_dai_symmetric_update(struct snd_pcm_substream *substream); +struct clk *snd_soc_dai_get_bclk(struct snd_soc_dai *dai); +unsigned int snd_soc_dai_get_bclk_ratio(struct snd_soc_dai *dai); #endif /* __SOC_INTERNAL_H */ diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index a446e9c1c988..e63b6317f21d 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -445,17 +445,24 @@ static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); struct snd_interval constraint = { .empty = 1 }; unsigned int target_rate; + struct clk *dai_bclk; + unsigned int dai_bclk_ratio; int i; /* Protect the rtd list traversal with the ASoC card mutex helper. */ guard(snd_soc_card_mutex)(card); + dai_bclk = snd_soc_dai_get_bclk(dai); + dai_bclk_ratio = snd_soc_dai_get_bclk_ratio(dai); + /* Scan all DAIs on the card for an active peer sharing the same BCLK */ for_each_card_rtds(card, rtd) { for_each_rtd_cpu_dais(rtd, i, other_dai) { + struct clk *other_dai_bclk = snd_soc_dai_get_bclk(other_dai); + if (other_dai == dai) continue; - if (!other_dai->bclk) + if (!other_dai_bclk) continue; if (!snd_soc_dai_active(other_dai)) continue; @@ -467,10 +474,10 @@ static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, */ if (!other_dai->symmetric_rate) continue; - if (!clk_is_match(dai->bclk, other_dai->bclk)) + if (!clk_is_match(dai_bclk, other_dai_bclk)) continue; - active_bclk_rate = clk_get_rate(other_dai->bclk); + active_bclk_rate = clk_get_rate(other_dai_bclk); if (active_bclk_rate) goto found; } @@ -479,13 +486,13 @@ static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, return 0; found: - if (dai->bclk_ratio) { + if (dai_bclk_ratio) { /* * Driver has set an explicit BCLK ratio (e.g. for TDM where * BCLK = rate * slots * slot_width). The only valid rate is * active_bclk_rate / bclk_ratio. */ - target_rate = active_bclk_rate / dai->bclk_ratio; + target_rate = active_bclk_rate / dai_bclk_ratio; constraint.min = target_rate; constraint.max = target_rate; @@ -522,7 +529,7 @@ static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, static int soc_pcm_apply_shared_bclk(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - if (!dai->bclk) + if (!snd_soc_dai_get_bclk(dai)) return 0; dev_dbg(dai->dev, From 885ce49a700c91eb6e173755fdf5dbcd88befa33 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:46:17 +0000 Subject: [PATCH 397/924] ASoC: soc-dai: rename snd_soc_dai_action() to snd_soc_dai_active_update() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have snd_soc_dai_action() to update .active. and in the same time, we have snd_soc_dai_active() to get .active. They are confusing name. Let's rename _action() to _active_update(). And in the same time, we have snd_soc_dai_action() and has its wrapper macro _activate/deactivate(). But callers are using both original/wrapper. Let's remove wrapper to remove confusion. It is used only from ASoC framework, let's move it to soc-internal.h Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87cxubjpqu.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 13 +------------ sound/soc/soc-dai.c | 6 ++---- sound/soc/soc-dapm.c | 9 +++++---- sound/soc/soc-internal.h | 1 + sound/soc/soc-pcm.c | 2 +- 5 files changed, 10 insertions(+), 21 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index be4781b61989..c044909ef10d 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -231,18 +231,7 @@ void snd_soc_dai_suspend(struct snd_soc_dai *dai); void snd_soc_dai_resume(struct snd_soc_dai *dai); int snd_soc_dai_compress_new(struct snd_soc_dai *dai, struct snd_soc_pcm_runtime *rtd); bool snd_soc_dai_stream_valid(const struct snd_soc_dai *dai, int stream); -void snd_soc_dai_action(struct snd_soc_dai *dai, - int stream, int action); -static inline void snd_soc_dai_activate(struct snd_soc_dai *dai, - int stream) -{ - snd_soc_dai_action(dai, stream, 1); -} -static inline void snd_soc_dai_deactivate(struct snd_soc_dai *dai, - int stream) -{ - snd_soc_dai_action(dai, stream, -1); -} + int snd_soc_dai_active(const struct snd_soc_dai *dai); int snd_soc_pcm_dai_probe(struct snd_soc_pcm_runtime *rtd, int order); diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index edf7d808e46e..d124b6afb1b8 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -707,8 +707,7 @@ bool snd_soc_dai_stream_valid(const struct snd_soc_dai *dai, int dir) return stream->channels_min; } -void snd_soc_dai_action(struct snd_soc_dai *dai, - int stream, int action) +void snd_soc_dai_active_update(struct snd_soc_dai *dai, int stream, int action) { /* see snd_soc_dai_stream_active() */ dai->stream[stream].active += action; @@ -716,7 +715,6 @@ void snd_soc_dai_action(struct snd_soc_dai *dai, /* see snd_soc_component_active() */ dai->component->active += action; } -EXPORT_SYMBOL_GPL(snd_soc_dai_action); int snd_soc_dai_active(const struct snd_soc_dai *dai) { @@ -1161,7 +1159,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_stream_tdm_mask_set); unsigned int snd_soc_dai_stream_active(const struct snd_soc_dai *dai, int stream) { - /* see snd_soc_dai_action() for setup */ + /* see snd_soc_dai_active_update() for setup */ return dai->stream[stream].active; } EXPORT_SYMBOL_GPL(snd_soc_dai_stream_active); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 06b85313af12..74be5e209493 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -37,6 +37,7 @@ #include #include #include +#include "soc-internal.h" #include @@ -4010,7 +4011,7 @@ static int dapm_dai_link_event_pre_pmu(struct snd_soc_dapm_widget *w, if (ret < 0) return ret; - snd_soc_dai_activate(source, substream->stream); + snd_soc_dai_active_update(source, substream->stream, 1); } substream->stream = SNDRV_PCM_STREAM_PLAYBACK; @@ -4021,7 +4022,7 @@ static int dapm_dai_link_event_pre_pmu(struct snd_soc_dapm_widget *w, if (ret < 0) return ret; - snd_soc_dai_activate(sink, substream->stream); + snd_soc_dai_active_update(sink, substream->stream, 1); } substream->hw_opened = 1; @@ -4150,14 +4151,14 @@ static int dapm_dai_link_event(struct snd_soc_dapm_widget *w, substream->stream = SNDRV_PCM_STREAM_CAPTURE; snd_soc_dapm_widget_for_each_source_path(w, path) { source = path->source->priv; - snd_soc_dai_deactivate(source, substream->stream); + snd_soc_dai_active_update(source, substream->stream, -1); snd_soc_dai_shutdown(source, substream, 0); } substream->stream = SNDRV_PCM_STREAM_PLAYBACK; snd_soc_dapm_widget_for_each_sink_path(w, path) { sink = path->sink->priv; - snd_soc_dai_deactivate(sink, substream->stream); + snd_soc_dai_active_update(sink, substream->stream, -1); snd_soc_dai_shutdown(sink, substream, 0); } break; diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index f350fe5280e3..31559918355f 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -34,5 +34,6 @@ int snd_soc_dai_symmetric_params(struct snd_pcm_substream *substream, struct snd void snd_soc_dai_symmetric_update(struct snd_pcm_substream *substream); struct clk *snd_soc_dai_get_bclk(struct snd_soc_dai *dai); unsigned int snd_soc_dai_get_bclk_ratio(struct snd_soc_dai *dai); +void snd_soc_dai_active_update(struct snd_soc_dai *dai, int stream, int action); #endif /* __SOC_INTERNAL_H */ diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index e63b6317f21d..1ddf199edc93 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -368,7 +368,7 @@ void snd_soc_runtime_action(struct snd_soc_pcm_runtime *rtd, snd_soc_dpcm_mutex_assert_held(rtd); for_each_rtd_dais(rtd, i, dai) - snd_soc_dai_action(dai, stream, action); + snd_soc_dai_active_update(dai, stream, action); /* Increments/Decrements the active count for components without DAIs */ for_each_rtd_components(rtd, i, component) { From eb8a4dd45471b19db4862af91455a187ebd8c426 Mon Sep 17 00:00:00 2001 From: Akhil Arul Date: Sun, 20 Sep 2026 18:36:20 +0530 Subject: [PATCH 398/924] ALSA: rawmidi: give up draining output when the device stops draining snd_rawmidi_drain_output() waits up to 10 seconds for the output buffer to empty. The wait is unconditional, so a substream that has stopped being consumed costs the full timeout on every call. The sequencer OSS emulation makes that expensive. midisynth_unuse() runs as the port unuse callback with grp->list_mutex held for write, and calls snd_rawmidi_drain_output(). A single teardown closes every OSS midi port of the device, so an unresponsive device exposing many ports holds the rwsem for minutes. snd_seq_port_connect() needs the same rwsem, and in the OSS path it runs under register_mutex, so every other odev_open() queues up behind it until the hung task detector fires: INFO: task syz.4.21:6176 blocked for more than 143 seconds. __mutex_lock odev_open chrdev_open vfs_open path_openat Detect a stalled drain by sampling the free space instead of always sleeping for the whole timeout, and give up once it has not increased for a second. A substream that is still making progress is given as long as it needs, within the same overall 10 second limit as before, and each wait is clipped to the remaining time so that limit is not overshot. The warning is rate limited because a stalled drain is now detected much more often than once per 10 seconds. Closing one such device in qemu, with the syzkaller reproducer supplying the gadget, took 72-103 seconds before and 9-11 seconds after. Reported-by: syzbot+825b7e3a03dd072c187f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=825b7e3a03dd072c187f Signed-off-by: Akhil Arul Link: https://patch.msgid.link/20260920130620.61431-1-akhilarul324@gmail.com Signed-off-by: Takashi Iwai --- sound/core/rawmidi.c | 65 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 2e582688923d..3399e9aee04d 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -36,6 +36,13 @@ module_param_array(amidi_map, int, NULL, 0444); MODULE_PARM_DESC(amidi_map, "Raw MIDI device number assigned to 2nd OSS device."); #endif /* CONFIG_SND_OSSEMUL */ +/* upper bound for draining the output buffer */ +#define SNDRV_RAWMIDI_DRAIN_TIMEOUT (10 * HZ) +/* interval at which drain progress is re-checked */ +#define SNDRV_RAWMIDI_DRAIN_POLL (HZ / 5) +/* polls without progress before the drain is considered stalled */ +#define SNDRV_RAWMIDI_DRAIN_STALLS 5 + static int snd_rawmidi_dev_free(struct snd_device *device); static int snd_rawmidi_dev_register(struct snd_device *device); static int snd_rawmidi_dev_disconnect(struct snd_device *device); @@ -246,11 +253,20 @@ int snd_rawmidi_drop_output(struct snd_rawmidi_substream *substream) } EXPORT_SYMBOL(snd_rawmidi_drop_output); +static bool output_drained(struct snd_rawmidi_runtime *runtime) +{ + return runtime->avail >= runtime->buffer_size; +} + int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream) { - int err = 0; - long timeout; struct snd_rawmidi_runtime *runtime; + size_t avail, prev_avail; + unsigned int stalls = 0; + unsigned long deadline; + long timeout, wait; + bool done; + int err = 0; scoped_guard(spinlock_irq, &substream->lock) { runtime = substream->runtime; @@ -259,19 +275,52 @@ int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream) return -EINVAL; snd_rawmidi_buffer_ref(runtime); runtime->drain = 1; + prev_avail = runtime->avail; } - timeout = wait_event_interruptible_timeout(runtime->sleep, - (runtime->avail >= runtime->buffer_size), - 10*HZ); + /* + * Wait for the device to consume the buffer. Rather than always + * sleeping for the whole timeout, sample the free space and stop + * early once it has not increased for a second. A substream that + * is still making progress is given as long as it needs, within the + * same overall limit as before. + */ + deadline = jiffies + SNDRV_RAWMIDI_DRAIN_TIMEOUT; + for (;;) { + /* signed difference, so this is safe across a jiffies wrap */ + wait = (long)(deadline - jiffies); + if (wait <= 0) { + timeout = 0; + break; + } + wait = min_t(long, SNDRV_RAWMIDI_DRAIN_POLL, wait); + timeout = wait_event_interruptible_timeout(runtime->sleep, + output_drained(runtime), wait); + scoped_guard(spinlock_irq, &substream->lock) { + avail = runtime->avail; + done = output_drained(runtime); + } + if (done || signal_pending(current)) + break; + if (avail == prev_avail) { + if (++stalls >= SNDRV_RAWMIDI_DRAIN_STALLS) { + timeout = 0; + break; + } + } else { + stalls = 0; + prev_avail = avail; + } + } scoped_guard(spinlock_irq, &substream->lock) { if (signal_pending(current)) err = -ERESTARTSYS; if (runtime->avail < runtime->buffer_size && !timeout) { - rmidi_warn(substream->rmidi, - "rawmidi drain error (avail = %li, buffer_size = %li)\n", - (long)runtime->avail, (long)runtime->buffer_size); + dev_warn_ratelimited(substream->rmidi->dev, + "rawmidi drain error (avail = %li, buffer_size = %li)\n", + (long)runtime->avail, + (long)runtime->buffer_size); err = -EIO; } runtime->drain = 0; From 556cca74b6ec6f0bc6a3dc27473487de3cc8c8fe Mon Sep 17 00:00:00 2001 From: Saim Masood Date: Sat, 19 Sep 2026 05:07:09 +0500 Subject: [PATCH 399/924] ALSA: hda/realtek: Fix speakers on ASUS ExpertBook PM3606CHA The internal speakers of the ASUS ExpertBook PM3606CHA (Realtek ALC256, PCI SSID 1043:3501) stay silent with the default codec setup, and the headphone jack is not detected either. The codec needs COEF values that differ from its power-on defaults: COEF 0x10, 0x16, 0x35, 0x37 and 0x57 on NID 0x20 have to be set to the values used by the Windows driver. Toggling the codec GPIOs, and the existing ALC256 fixups alone, make no difference. Apply the COEFs from a new fixup at the INIT action, so that they are restored after resume as well. Chain to ALC256_FIXUP_ASUS_MIC_NO_PRESENCE to keep the headset mic and the jack detection working, which the fallback pin quirk provided before for this machine. The COEF values were found by the reporter of the Ubuntu bug below, who compared the codec state against the one under the Windows driver. Tested on an ASUS ExpertBook PM3606CHA (BIOS PM3606CHA.304) with the change applied to v7.2.5: with this fixup alone, the speakers, the headphones with jack detection, and the headset mic all work, also after suspend and resume. Link: https://bugs.launchpad.net/bugs/2167288 Signed-off-by: Saim Masood Link: https://patch.msgid.link/065927dd-cabf-449a-992d-64887eedda01@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 27d9a438ae79..f0212559643d 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -3579,6 +3579,27 @@ static void alc233_fixup_no_audio_jack(struct hda_codec *codec, alc_process_coef_fw(codec, alc233_fixup_no_audio_jack_coefs); } +static const struct coef_fw alc256_asus_speaker_coefs[] = { + WRITE_COEF(0x10, 0x7f20), WRITE_COEF(0x16, 0x0c50), WRITE_COEF(0x35, 0x8d6a), + WRITE_COEF(0x37, 0xfe06), WRITE_COEF(0x57, 0x7f7f), + {} +}; + +static void alc256_fixup_asus_speaker_coefs(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + /* + * The internal speakers of the ASUS ExpertBook PM3606CHA stay silent + * with the default COEF values. These are the values set by the + * Windows driver. They are applied on every init so that they are + * restored after resume, too. + */ + if (action != HDA_FIXUP_ACT_INIT) + return; + alc_process_coef_fw(codec, alc256_asus_speaker_coefs); +} + static void alc256_fixup_mic_no_presence_and_resume(struct hda_codec *codec, const struct hda_fixup *fix, int action) @@ -4429,6 +4450,7 @@ enum { ALC285_LENOVO_DAC_RENAME, ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, ALC256_FIXUP_IPASON_SMARTBOOK_S1, + ALC256_FIXUP_ASUS_SPEAKER_COEFS, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -7203,6 +7225,12 @@ static const struct hda_fixup alc269_fixups[] = { { } }, }, + [ALC256_FIXUP_ASUS_SPEAKER_COEFS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_asus_speaker_coefs, + .chained = true, + .chain_id = ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, + }, }; static const struct hda_quirk alc269_fixup_tbl[] = { @@ -7969,6 +7997,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x31e1, "ASUS B5605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x31f1, "ASUS B3605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x3391, "ASUS PM3606CKA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x3501, "ASUS PM3606CHA", ALC256_FIXUP_ASUS_SPEAKER_COEFS), SND_PCI_QUIRK(0x1043, 0x3601, "ASUS PM5406CGA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x3611, "ASUS PM5606CGA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x3701, "ASUS P5406CCA", ALC245_FIXUP_CS35L41_SPI_2), From 1e3e378d63be4e5887f5753c00562dcbcebf00aa Mon Sep 17 00:00:00 2001 From: Denis Yakovlev Date: Mon, 28 Sep 2026 00:04:55 +0300 Subject: [PATCH 400/924] ALSA: hda/realtek: Enable bass speakers on HONOR MagicBook Pro 16 2024 The HONOR MagicBook Pro 16 2024 (DRA-XX, board M1020, ALC256, SSID 1ee7:204e) has two tweeters and four woofers. The BIOS only configures the tweeter pin 0x1b; the woofer pin 0x14 is left at 0x411111f0, so the woofers stay silent. Pin 0x14 can only be connected to DAC 0x02, while the parser puts the tweeters on DAC 0x03, which turns the second speaker pair into channels 3/4 that a stereo stream never reaches (and SOF on Meteor Lake only exposes a 2-channel HDA analog PCM). Configure 0x14 as an internal speaker and restrict 0x1b to DAC 0x02, so both speaker pairs play the stereo stream with a shared volume control, as on Windows. Tested on DRA-XX with SOF (sof-hda-generic-2ch.tplg): woofers and tweeters play together, headphone auto-mute and the DMICs keep working. Assisted-by: LLM Signed-off-by: Denis Yakovlev Link: https://patch.msgid.link/20260927210455.13938-1-mfthesun@vivaldi.net Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index f0212559643d..075dc1aa326b 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -2763,6 +2763,17 @@ static void alc294_fixup_bass_speaker_15(struct hda_codec *codec, } } +/* route Speaker (0x1b) to DAC 0x02, the only DAC reachable from Bass Speaker (0x14) */ +static void alc256_fixup_honor_dra_xx_share_dac(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + static const hda_nid_t conn[] = { 0x02 }; + + snd_hda_override_conn_list(codec, 0x1b, ARRAY_SIZE(conn), conn); + } +} + /* Hook to update amp GPIO4 for automute */ static void alc280_hp_gpio4_automute_hook(struct hda_codec *codec, struct hda_jack_callback *jack) @@ -4439,6 +4450,8 @@ enum { ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED, ALC236_FIXUP_HP_DMIC, ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, + ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS, + ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC, ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX, ALC287_FIXUP_ACER_MICMUTE_LED, ALC236_FIXUP_DELL_HP_POP_NOISE, @@ -7152,6 +7165,19 @@ static const struct hda_fixup alc269_fixups[] = { { } } }, + [ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x90170111 }, /* bass speakers */ + { } + }, + .chained = true, + .chain_id = ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC + }, + [ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_honor_dra_xx_share_dac, + }, [ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX] = { .type = HDA_FIXUP_FUNC, .v.func = cs35l41_fixup_i2c_two, @@ -8448,6 +8474,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1e50, 0x7007, "Positivo DN50E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x1e50, 0x7036, "Acer Gadget E10 ETBook", ALC233_FIXUP_WUJIE_SPEAKERS), SND_PCI_QUIRK(0x1e50, 0x7038, "Positivo DN140", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1ee7, 0x204e, "HONOR DRA-XX M1020", ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS), SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro", From 082723fee626532f176727b6438b666a96d5246c Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 16:48:53 +0000 Subject: [PATCH 401/924] ASoC: codecs: wcd9335: Fix SLIM interface device leak in wcd9335_slim_status() of_slim_get_device() returns a device with an elevated reference count, and wcd->slim_ifc_dev is intentionally kept for the codec lifetime on success. However, all the failure paths of wcd9335_slim_status() return without dropping that reference, leaking the SLIM interface device whenever the register maps cannot be created or the codec fails to come up. Put the device on each of those error paths. Fixes: 20aedafdf492 ("ASoC: wcd9335: add support to wcd9335 codec") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260917164853.2163184-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/wcd9335.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index 9332082092c7..a98d1fd78925 100644 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -5135,25 +5135,32 @@ static int wcd9335_slim_status(struct slim_device *sdev, slim_get_logical_addr(wcd->slim_ifc_dev); wcd->regmap = regmap_init_slimbus(sdev, &wcd9335_regmap_config); - if (IS_ERR(wcd->regmap)) + if (IS_ERR(wcd->regmap)) { + put_device(&wcd->slim_ifc_dev->dev); return dev_err_probe(dev, PTR_ERR(wcd->regmap), "Failed to allocate slim register map\n"); + } wcd->if_regmap = regmap_init_slimbus(wcd->slim_ifc_dev, &wcd9335_ifc_regmap_config); - if (IS_ERR(wcd->if_regmap)) + if (IS_ERR(wcd->if_regmap)) { + put_device(&wcd->slim_ifc_dev->dev); return dev_err_probe(dev, PTR_ERR(wcd->if_regmap), "Failed to allocate ifc register map\n"); + } ret = wcd9335_bring_up(wcd); if (ret) { dev_err(dev, "Failed to bringup WCD9335\n"); + put_device(&wcd->slim_ifc_dev->dev); return ret; } ret = wcd9335_irq_init(wcd); - if (ret) + if (ret) { + put_device(&wcd->slim_ifc_dev->dev); return ret; + } wcd9335_probe(wcd); From b57f5571a3e88febd38d60f9a9cefd8937a383ba Mon Sep 17 00:00:00 2001 From: Liu Eason Date: Sat, 19 Sep 2026 21:08:13 +0800 Subject: [PATCH 402/924] ALSA: hda/conexant: Fix silent speaker on Huawei MateBook 14 (SN6140) On the Huawei MateBook 14 (PCI SSID 19e5:329e), the SN6140 codec silently routes the speaker output through the headphone pin's connection select: whatever DAC the headphone pin 0x16 is connected to, the speaker pin 0x17 follows. The generic parser attaches the playback stream to DAC 0x10 (shared by the headphone path) and mutes that DAC when no headphone is plugged, silencing the internal speaker while the headphone jack keeps working. Add a quirk for this machine that suppresses the auto-mute, binds the speaker pin to the shared DAC 0x10, removes the mute switch on that DAC so userspace cannot mute it, and re-applies the routing after every output update; at init it routes both pins to DAC 0x10, enables the speaker EAPD (which the driver otherwise leaves off) and unmutes the DAC. Note that this is a different problem than the existing Huawei Matebook quirk for SSID 19e5:3289 (commit d152afd1cd7a), which fixes a missing headphone jack; that machine has working speakers. Tested on Ubuntu 26.04 (kernels 7.0.0-30 and 7.0.0-31): the speaker plays at boot without any userspace helper, and keeps working across suspend/resume. Signed-off-by: Liu Eason Link: https://lore.kernel.org/ME3PR01MB75042406F30EE48AB6DA3595BD862@ME3PR01MB7504.ausprd01.prod.outlook.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/conexant.c | 67 +++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index 9bfdbf5c9032..91cbd728372f 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -312,6 +312,7 @@ enum { CXT_FIXUP_HP_A_U, CXT_FIXUP_ACER_SWIFT_HP, CXT_FIXUP_HUAWEI_MATEBOOK_HP, + CXT_FIXUP_MATEBOOK14_SN6140, }; /* for hda_fixup_thinkpad_acpi() */ @@ -817,6 +818,67 @@ static void cxt_fixup_hp_a_u(struct hda_codec *codec, cxt_setup_gpio_unmute(codec, 0x2); } +/* Huawei MateBook 14 (SN6140): the speaker output follows the headphone + * pin's connection select in hardware. Both pins must be routed to the + * shared DAC 0x10, which carries the playback stream; the generic + * parser mutes that DAC when no headphone is plugged, silencing the + * speaker. Suppress the auto-mute, bind the speaker pin to the shared + * DAC, remove its mute switch and keep it unmuted; the auto-mute hook + * re-applies the routing after every output update. + */ +static void cxt_mb14_route_unmute(struct hda_codec *codec) +{ + /* route both pins to the shared DAC 0x10, enable the speaker + * EAPD and unmute the DAC at a moderate gain; the vmaster + * volume takes over once userspace starts + */ + snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_CONNECT_SEL, 0); + snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_CONNECT_SEL, 0); + snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_EAPD_BTLENABLE, 0x02); + snd_hda_codec_amp_stereo(codec, 0x10, HDA_OUTPUT, 0, + HDA_AMP_VOLMASK | HDA_AMP_MUTE, 0x40); +} + +static void cxt_mb14_automute(struct hda_codec *codec) +{ + snd_hda_gen_update_outputs(codec); + cxt_mb14_route_unmute(codec); +} + +static void cxt_fixup_matebook14_sn6140(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + static const hda_nid_t dac_0x10[] = { 0x10 }; + + spec->gen.suppress_auto_mute = 1; + spec->gen.automute_hook = cxt_mb14_automute; + /* Bind the speaker pin to the shared DAC 0x10. + */ + snd_hda_override_conn_list(codec, 0x17, 1, dac_0x10); + return; + } + if (action == HDA_FIXUP_ACT_PROBE) { + unsigned int caps; + + /* Remove the mute switch on the shared DAC so that + * userspace (WirePlumber) cannot mute it when the + * headphone route becomes unavailable (e.g. after + * resume). Must be done here: the codec regmap is not + * available at pre-probe time. + */ + caps = query_amp_caps(codec, 0x10, HDA_OUTPUT); + snd_hda_override_amp_caps(codec, 0x10, HDA_OUTPUT, + caps & ~AC_AMPCAP_MUTE); + return; + } + if (action != HDA_FIXUP_ACT_INIT) + return; + cxt_mb14_route_unmute(codec); +} + /* ThinkPad X200 & co with cxt5051 */ static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = { { 0x16, 0x042140ff }, /* HP (seq# overridden) */ @@ -1062,6 +1124,10 @@ static const struct hda_fixup cxt_fixups[] = { { } }, }, + [CXT_FIXUP_MATEBOOK14_SN6140] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_matebook14_sn6140, + }, }; static const struct hda_quirk cxt5045_fixups[] = { @@ -1164,6 +1230,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI), SND_PCI_QUIRK(0x19e5, 0x3289, "Huawei Matebook", CXT_FIXUP_HUAWEI_MATEBOOK_HP), + SND_PCI_QUIRK(0x19e5, 0x329e, "Huawei MateBook 14", CXT_FIXUP_MATEBOOK14_SN6140), SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004), SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205), SND_PCI_QUIRK(0x1d05, 0x3012, "MECHREVO Wujie 15X Pro", CXT_FIXUP_HEADSET_MIC), From 7103a8c7b2ce196aded3bdfd8c4cf5afef4a90ff Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Sun, 27 Sep 2026 09:36:23 +0800 Subject: [PATCH 403/924] ASoC: soc-generic-dmaengine-pcm: use dmaengine_get_dma_device() for DMA device Replace chan->device->dev with dmaengine_get_dma_device(chan) in dmaengine_dma_dev() so that DMA buffer allocation and mapping go through the correct device when the DMA controller has per-channel IOMMU domains (chan_dma_dev=true). Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260927-sound-dma-v1-1-8b06f971ac0e@nxp.com Signed-off-by: Mark Brown --- sound/soc/soc-generic-dmaengine-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index 6d1dbee8f6c1..f6206ecaf26f 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -32,7 +32,7 @@ static struct device *dmaengine_dma_dev(struct dmaengine_pcm *pcm, if (!pcm->chan[substream->stream]) return NULL; - return pcm->chan[substream->stream]->device->dev; + return dmaengine_get_dma_device(pcm->chan[substream->stream]); } /** From 72107104d4f522405c9dfdfbaeb230e371c295ee Mon Sep 17 00:00:00 2001 From: Jared Wuerzburger Date: Mon, 28 Sep 2026 10:29:25 -0500 Subject: [PATCH 404/924] ALSA: hda/realtek: Fix internal mic on Minisforum V3 and V3 SE On the Minisforum V3 and V3 SE (ALC245, PCI SSID 1f4c:e001) the internal digital microphone array never produces any audio. The DMIC pin 0x12 is parsed correctly ("Internal Mic=0x12"), but the headset mic jack pin 0x19 (BIOS pin config 0x04a19050, jack detect enabled) has no presence detect wired on these boards: reading GET_PIN_SENSE on 0x19 always returns "present" (as it does on the unused, unconnected pin 0x18), while the headphone jack 0x21 reports correctly. The generic parser therefore sets up auto-mic switching and, believing an external mic is always plugged in, permanently routes the ADC to the empty jack (mixer 0x23 input 0x19 unmuted, input 0x12 muted). The "Mic Jack" kcontrol reports "on" with nothing plugged in. Mark pin 0x19 as having no presence detect. The parser then stops auto-switching and exposes a normal "Capture Source" (Internal Mic / Mic) control, which desktop audio stacks drive from their Internal Microphone / Microphone ports. The existing bass speaker DAC routing is kept by chaining to ALC245_FIXUP_BASS_HP_DAC. Tested on both models with the same pin config applied through a snd-hda-intel patch firmware ([pincfg] 0x19 0x04a19150): - V3 SE (Ryzen 7 7735U, BIOS 1.03), kernel 7.2.6 - V3 (Ryzen 7 8840U, BIOS 1.06), kernel 7.2.7 Both boards report the same codec SSID and the same 0x19 default. On both, the internal mic records at a normal level, headphone jack detection on 0x21 still switches output, and the bass routing is retained (DAC1/DAC2 Playback Volume controls present). A headset mic on the combo jack was not tested (none available); with this change it has to be selected manually. V3 users already report that the jack mic gets no input [1], which is the other symptom of the same broken presence detect. The patched alc269.c was build-tested with W=1 on tiwai/sound.git for-linus (7.3-rc3) without new warnings. The diagnosis, the fixup and this changelog were written with the help of an AI coding assistant; I reviewed them and tested the change on the hardware listed above. Fixes: e34743018249 ("ALSA: hda/realtek: Add quirk for Minisforum V3 SE") Link: https://github.com/mudkipme/awesome-minisforum-v3/issues/14 [1] Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Jared Wuerzburger Link: https://patch.msgid.link/20260928152925.69032-1-jwuerz@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 075dc1aa326b..13bc74190752 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4464,6 +4464,7 @@ enum { ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, ALC256_FIXUP_IPASON_SMARTBOOK_S1, ALC256_FIXUP_ASUS_SPEAKER_COEFS, + ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -7257,6 +7258,15 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, }, + [ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x04a19150 }, /* headset mic jack, no presence detect */ + { } + }, + .chained = true, + .chain_id = ALC245_FIXUP_BASS_HP_DAC + }, }; static const struct hda_quirk alc269_fixup_tbl[] = { @@ -8479,7 +8489,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro", ALC245_FIXUP_MINISFORUM_JACK_DETECT), - SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", ALC245_FIXUP_BASS_HP_DAC), + SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", + ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), From a718fd03e1e00211cf4662f5ab82d33f4c18e2a7 Mon Sep 17 00:00:00 2001 From: Niko Huuskonen Date: Sun, 27 Sep 2026 03:35:29 +0300 Subject: [PATCH 405/924] ALSA: caiaq: Serialize access to the EP1 command buffer snd_usb_caiaq_send_command() and snd_usb_caiaq_send_command_bank() copy the command into cdev->ep1_out_buf and send it with a synchronous bulk transfer. Nothing serializes their callers. An ALSA control write, which sets the LEDs on the Kore controllers and several other devices, can run at the same time as a PCM prepare, which sends the audio parameters through the same buffer. One caller can then overwrite the buffer while the transfer of the other is still in flight, and the device receives a mix of both commands. Protect the buffer with a mutex. All callers run in process context and already sleep in usb_bulk_msg(). The problem was found by code review while adding another user of the buffer, the Kore LCD support later in this series. It has not been observed or reproduced. Fixes: 8e3cd08ed8e5 ("[ALSA] caiaq - add control API and more input features") Assisted-by: LLM Signed-off-by: Niko Huuskonen Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260927003532.289468-2-niko.huuskonen.00@gmail.com --- sound/usb/caiaq/device.c | 5 +++++ sound/usb/caiaq/device.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index f604846ec6e6..e8619bb74358 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -212,6 +212,8 @@ int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev, if (len > EP1_BUFSIZE - 1) len = EP1_BUFSIZE - 1; + guard(mutex)(&cdev->ep1_out_mutex); + if (buffer && len > 0) memcpy(cdev->ep1_out_buf+1, buffer, len); @@ -235,6 +237,8 @@ int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev, if (len > EP1_BUFSIZE - 2) len = EP1_BUFSIZE - 2; + guard(mutex)(&cdev->ep1_out_mutex); + if (buffer && len > 0) memcpy(cdev->ep1_out_buf+2, buffer, len); @@ -443,6 +447,7 @@ static int create_card(struct usb_device *usb_dev, cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor), le16_to_cpu(usb_dev->descriptor.idProduct)); spin_lock_init(&cdev->spinlock); + mutex_init(&cdev->ep1_out_mutex); *cardp = card; return 0; diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index 1c6f34693fa8..822d872c8ad1 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -2,6 +2,8 @@ #ifndef CAIAQ_DEVICE_H #define CAIAQ_DEVICE_H +#include + #include "../usbaudio.h" #define USB_VID_NATIVEINSTRUMENTS 0x17cc @@ -68,6 +70,7 @@ struct snd_usb_caiaqdev { unsigned char ep1_in_buf[EP1_BUFSIZE]; unsigned char ep1_out_buf[EP1_BUFSIZE]; + struct mutex ep1_out_mutex; /* protects ep1_out_buf */ unsigned char midi_out_buf[EP1_BUFSIZE]; struct caiaq_device_spec spec; From c0508fc6895137f9e2dd43c1a09cc834254cc149 Mon Sep 17 00:00:00 2001 From: Niko Huuskonen Date: Sun, 27 Sep 2026 03:35:30 +0300 Subject: [PATCH 406/924] ALSA: caiaq: Fix the Kore controller key map keycode_kore does not match the hardware in two places: - Softkeys 5 to 8 are listed in reverse order, so pressing the fifth softkey reports BTN_8, the sixth BTN_7 and so on. Softkeys 1 to 4 are correct. The OpenKoreBridge project, which drives a Kore 2 through this driver, works around the same reversal in userspace. - On the first Kore controller the touch sensors of the eight knobs are scrambled: touching knob 1 reports KEY_BRL_DOT6, knob 2 KEY_BRL_DOT8, knob 3 KEY_BRL_DOT2, knob 6 KEY_BRL_DOT7, knob 7 KEY_BRL_DOT1 and knob 8 KEY_BRL_DOT3. Only knobs 4 and 5 are right. Put the softkeys in order for both controllers, and give the first Kore controller its own touch sensor order, so that BTN_n and KEY_BRL_DOTn belong to the n-th knob. The touch sensor order of the Kore 2 is left alone, as it could not be checked. Userspace that compensates for the old order needs to follow. It can read the key map with EVIOCGKEYCODE, which also makes it possible to support kernels with and without this change. Tested on a Kore controller (USB ID 17cc:4711). Link: https://github.com/OpenKoreBridge/OpenKoreBridge Fixes: 8e3cd08ed8e5 ("[ALSA] caiaq - add control API and more input features") Assisted-by: LLM Signed-off-by: Niko Huuskonen Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260927003532.289468-3-niko.huuskonen.00@gmail.com --- sound/usb/caiaq/input.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index 8d924330c54c..856235c3398e 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -41,11 +41,11 @@ static const unsigned short keycode_kore[] = { BTN_3, BTN_2, BTN_1, - BTN_8, - BTN_7, - BTN_6, BTN_5, - KEY_BRL_DOT4, /* touch sensitive knobs */ + BTN_6, + BTN_7, + BTN_8, + KEY_BRL_DOT4, /* touch sensitive knobs (Kore 2 order) */ KEY_BRL_DOT3, KEY_BRL_DOT2, KEY_BRL_DOT1, @@ -55,6 +55,21 @@ static const unsigned short keycode_kore[] = { KEY_BRL_DOT5 }; +/* index of the first touch sensor in keycode_kore */ +#define KORE_TOUCH_KEYS 24 + +/* the first Kore controller reports its touch sensors in this order */ +static const unsigned short keycode_kore1_touch[] = { + KEY_BRL_DOT4, + KEY_BRL_DOT8, + KEY_BRL_DOT3, + KEY_BRL_DOT7, + KEY_BRL_DOT2, + KEY_BRL_DOT6, + KEY_BRL_DOT1, + KEY_BRL_DOT5 +}; + #define MASCHINE_BUTTONS (42) #define MASCHINE_BUTTON(X) ((X) + BTN_MISC) #define MASCHINE_PADS (16) @@ -666,6 +681,12 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev) input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC); BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_kore)); memcpy(cdev->keycode, keycode_kore, sizeof(keycode_kore)); + BUILD_BUG_ON(KORE_TOUCH_KEYS + ARRAY_SIZE(keycode_kore1_touch) > + ARRAY_SIZE(keycode_kore)); + if (cdev->chip.usb_id == + USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER)) + memcpy(cdev->keycode + KORE_TOUCH_KEYS, keycode_kore1_touch, + sizeof(keycode_kore1_touch)); input->keycodemax = ARRAY_SIZE(keycode_kore); input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10); input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10); From 7a0b09d72d3710a6a5534c30505de657bec8a41d Mon Sep 17 00:00:00 2001 From: Niko Huuskonen Date: Sun, 27 Sep 2026 03:35:31 +0300 Subject: [PATCH 407/924] ALSA: uapi: Add hwdep interface ID for caiaq devices Add SNDRV_HWDEP_IFACE_CAIAQ for the hwdep device that snd-usb-caiaq creates for the LCD of the Native Instruments Kore controllers, added by the next patch. Assisted-by: LLM Signed-off-by: Niko Huuskonen Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260927003532.289468-4-niko.huuskonen.00@gmail.com --- include/uapi/sound/asound.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index c11da9656e38..767c96273245 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -99,9 +99,10 @@ enum { SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */ SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */ SNDRV_HWDEP_IFACE_FW_FIREFACE, /* RME Fireface series */ + SNDRV_HWDEP_IFACE_CAIAQ, /* Native Instruments caiaq USB devices */ /* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_CAIAQ }; struct snd_hwdep_info { From d243bb01c86cf7e6aaec53bf0eb8bac8c92ecffa Mon Sep 17 00:00:00 2001 From: Niko Huuskonen Date: Sun, 27 Sep 2026 03:35:32 +0300 Subject: [PATCH 408/924] ALSA: caiaq: Add LCD support for the Kore controllers Both Kore controllers have a 128x64 pixel monochrome LCD. The driver does not support it, and userspace cannot reach it while the driver is bound, so the display stays blank on Linux. The firmware passes EP1 packets with the command byte 0x08 on to an ST7565-style display controller: "08 00 " carries controller commands, "08 01 " display RAM data. The vendor software sets the controller up, then writes each 128 byte page in blocks of 32 bytes, each preceded by page and column address commands. The OpenKoreBridge project documented this from USB captures of the vendor software with a Kore 2. Add a hwdep device, "Kore LCD", for both controllers. A write carries one frame of 1024 bytes: 8 pages of 128 columns, with bit 0 as the top pixel of each page. The driver sets the controller up on the first write and afterwards only sends the pages that changed. The device is exclusive, so frames from different writers cannot interleave. Add an "LCD Contrast" control (0-63); the backlight is already the "LED lcd" control. Tested on a Kore controller (USB ID 17cc:4711): full frames, frames that change single pages and contrast changes show up as expected, while audio, MIDI, input and the LEDs keep working. A Kore 2 was not available for testing; it gets the protocol that OpenKoreBridge uses with it. Link: https://github.com/OpenKoreBridge/OpenKoreBridge Assisted-by: LLM Signed-off-by: Niko Huuskonen Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260927003532.289468-5-niko.huuskonen.00@gmail.com --- sound/usb/caiaq/Makefile | 2 +- sound/usb/caiaq/device.c | 7 ++ sound/usb/caiaq/device.h | 10 ++ sound/usb/caiaq/lcd.c | 258 +++++++++++++++++++++++++++++++++++++++ sound/usb/caiaq/lcd.h | 7 ++ 5 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 sound/usb/caiaq/lcd.c create mode 100644 sound/usb/caiaq/lcd.h diff --git a/sound/usb/caiaq/Makefile b/sound/usb/caiaq/Makefile index 9a99c17a2c1b..ffa5a7edf95c 100644 --- a/sound/usb/caiaq/Makefile +++ b/sound/usb/caiaq/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -snd-usb-caiaq-y := device.o audio.o midi.o control.o +snd-usb-caiaq-y := device.o audio.o midi.o control.o lcd.o snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += input.o obj-$(CONFIG_SND_USB_CAIAQ) += snd-usb-caiaq.o diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index e8619bb74358..dea3a68ca896 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -22,6 +22,7 @@ #include "midi.h" #include "control.h" #include "input.h" +#include "lcd.h" MODULE_AUTHOR("Daniel Mack "); MODULE_DESCRIPTION("caiaq USB audio"); @@ -387,6 +388,12 @@ static int setup_card(struct snd_usb_caiaqdev *cdev) } #endif + ret = snd_usb_caiaq_lcd_init(cdev); + if (ret < 0) { + dev_err(dev, "Unable to set up LCD (ret=%d)\n", ret); + return ret; + } + /* finally, register the card and all its sub-instances */ ret = snd_card_register(cdev->chip.card); if (ret < 0) { diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index 822d872c8ad1..dd726ca68e80 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -30,6 +30,8 @@ #define MODNAME "snd-usb-caiaq" +#define CAIAQ_LCD_FRAME_SIZE 1024 + #define EP1_CMD_GET_DEVICE_INFO 0x1 #define EP1_CMD_READ_ERP 0x2 #define EP1_CMD_READ_ANALOG 0x3 @@ -37,6 +39,7 @@ #define EP1_CMD_WRITE_IO 0x5 #define EP1_CMD_MIDI_READ 0x6 #define EP1_CMD_MIDI_WRITE 0x7 +#define EP1_CMD_LCD 0x8 #define EP1_CMD_AUDIO_PARAMS 0x9 #define EP1_CMD_AUTO_MSG 0xb #define EP1_CMD_DIMM_LEDS 0xc @@ -101,6 +104,13 @@ struct snd_usb_caiaqdev { unsigned char control_state[256]; unsigned char ep8_out_buf[2]; + /* Kore LCD */ + struct mutex lcd_mutex; /* protects the lcd_* fields */ + unsigned char lcd_frame[CAIAQ_LCD_FRAME_SIZE]; /* shown frame */ + unsigned char lcd_contrast; + bool lcd_ready; /* controller set up */ + bool lcd_frame_valid; /* lcd_frame matches the display */ + /* Linux input */ #ifdef CONFIG_SND_USB_CAIAQ_INPUT struct input_dev *input_dev; diff --git a/sound/usb/caiaq/lcd.c b/sound/usb/caiaq/lcd.c new file mode 100644 index 000000000000..dd2a3b949a43 --- /dev/null +++ b/sound/usb/caiaq/lcd.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LCD support for the Native Instruments Kore controllers + * + * Copyright (c) 2026 Niko Huuskonen + * + * Both Kore controllers have a 128x64 pixel monochrome LCD behind an + * ST7565-style controller. The firmware passes EP1 packets with the + * command byte EP1_CMD_LCD on to that controller: + * + * EP1_CMD_LCD 0x00 + * EP1_CMD_LCD 0x01 + * + * The display is exposed as an exclusive hwdep device. Each write carries + * a whole frame of CAIAQ_LCD_FRAME_SIZE bytes: 8 pages of 128 columns, + * one byte per column and page, with bit 0 as the top pixel of the page. + * Only the pages that changed since the previous frame are sent to the + * device. The contrast is an ALSA control; the backlight is the existing + * "LED lcd" control. + * + * The controller setup and the packet layout follow the USB traffic of + * the vendor software, as documented by the OpenKoreBridge project. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "lcd.h" + +#define LCD_WIDTH 128 +#define LCD_PAGES (CAIAQ_LCD_FRAME_SIZE / LCD_WIDTH) +#define LCD_COLUMN_OFFSET 4 /* first visible controller column */ +#define LCD_CHUNK 32 /* display RAM bytes per packet */ +#define LCD_CONTRAST_MAX 0x3f +#define LCD_CONTRAST_DEFAULT 0x1c + +#define LCD_KIND_COMMAND 0x00 +#define LCD_KIND_DATA 0x01 + +static int lcd_send(struct snd_usb_caiaqdev *cdev, u8 kind, + const u8 *bytes, unsigned int len) +{ + u8 buf[2 + LCD_CHUNK]; + + if (WARN_ON(len > LCD_CHUNK)) + return -EINVAL; + + buf[0] = kind; + buf[1] = len; + memcpy(buf + 2, bytes, len); + return snd_usb_caiaq_send_command(cdev, EP1_CMD_LCD, buf, len + 2); +} + +static int lcd_command(struct snd_usb_caiaqdev *cdev, u8 cmd) +{ + return lcd_send(cdev, LCD_KIND_COMMAND, &cmd, 1); +} + +static int lcd_set_contrast(struct snd_usb_caiaqdev *cdev) +{ + const u8 cmd[] = { 0x81, cdev->lcd_contrast }; + + return lcd_send(cdev, LCD_KIND_COMMAND, cmd, sizeof(cmd)); +} + +/* controller setup, in the order used by the vendor software */ +static int lcd_setup(struct snd_usb_caiaqdev *cdev) +{ + static const u8 head[] = { + 0xe2, /* reset */ + 0xa1, /* reverse column direction */ + 0xc8, /* reverse row direction */ + 0xa2, /* 1/9 bias */ + 0x2c, 0x2e, 0x2f, /* power up in three steps */ + 0x27, /* regulator resistor ratio */ + }; + static const u8 tail[] = { + 0xa6, /* normal, non-inverted display */ + 0x88, 0xef, /* sent by the vendor software */ + 0xaf, /* display on */ + }; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(head); i++) { + ret = lcd_command(cdev, head[i]); + if (ret) + return ret; + } + + ret = lcd_set_contrast(cdev); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(tail); i++) { + ret = lcd_command(cdev, tail[i]); + if (ret) + return ret; + } + + return 0; +} + +static int lcd_write_page(struct snd_usb_caiaqdev *cdev, unsigned int page, + const u8 *data) +{ + unsigned int col; + int ret; + + for (col = 0; col < LCD_WIDTH; col += LCD_CHUNK) { + unsigned int addr = LCD_COLUMN_OFFSET + col; + const u8 column[] = { 0x10 | (addr >> 4), addr & 0x0f }; + + ret = lcd_command(cdev, 0xb0 | page); + if (!ret) + ret = lcd_send(cdev, LCD_KIND_COMMAND, + column, sizeof(column)); + if (!ret) + ret = lcd_send(cdev, LCD_KIND_DATA, + data + col, LCD_CHUNK); + if (ret) + return ret; + } + + return 0; +} + +static long lcd_hwdep_write(struct snd_hwdep *hw, const char __user *buf, + long count, loff_t *offset) +{ + struct snd_usb_caiaqdev *cdev = hw->private_data; + unsigned int page; + int ret; + + if (count != CAIAQ_LCD_FRAME_SIZE) + return -EINVAL; + + u8 *frame __free(kfree) = memdup_user(buf, count); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + guard(mutex)(&cdev->lcd_mutex); + + if (!cdev->lcd_ready) { + ret = lcd_setup(cdev); + if (ret) + return ret; + cdev->lcd_ready = true; + cdev->lcd_frame_valid = false; + } + + for (page = 0; page < LCD_PAGES; page++) { + u8 *shown = cdev->lcd_frame + page * LCD_WIDTH; + const u8 *next = frame + page * LCD_WIDTH; + + if (cdev->lcd_frame_valid && !memcmp(shown, next, LCD_WIDTH)) + continue; + + ret = lcd_write_page(cdev, page, next); + if (ret) { + cdev->lcd_frame_valid = false; + return ret; + } + memcpy(shown, next, LCD_WIDTH); + } + cdev->lcd_frame_valid = true; + + return count; +} + +static int lcd_contrast_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = LCD_CONTRAST_MAX; + return 0; +} + +static int lcd_contrast_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_caiaqdev *cdev = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&cdev->lcd_mutex); + ucontrol->value.integer.value[0] = cdev->lcd_contrast; + return 0; +} + +static int lcd_contrast_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_caiaqdev *cdev = snd_kcontrol_chip(kcontrol); + long val = ucontrol->value.integer.value[0]; + int ret; + + if (val < 0 || val > LCD_CONTRAST_MAX) + return -EINVAL; + + guard(mutex)(&cdev->lcd_mutex); + if (val == cdev->lcd_contrast) + return 0; + + cdev->lcd_contrast = val; + if (cdev->lcd_ready) { + ret = lcd_set_contrast(cdev); + if (ret) + return ret; + } + + return 1; +} + +static const struct snd_kcontrol_new lcd_contrast_control = { + .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, + .name = "LCD Contrast", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = lcd_contrast_info, + .get = lcd_contrast_get, + .put = lcd_contrast_put, +}; + +int snd_usb_caiaq_lcd_init(struct snd_usb_caiaqdev *cdev) +{ + struct snd_card *card = cdev->chip.card; + struct snd_hwdep *hw; + int ret; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + break; + default: + return 0; + } + + mutex_init(&cdev->lcd_mutex); + cdev->lcd_contrast = LCD_CONTRAST_DEFAULT; + + ret = snd_hwdep_new(card, "Kore LCD", 0, &hw); + if (ret < 0) + return ret; + + strscpy(hw->name, "Kore LCD", sizeof(hw->name)); + hw->iface = SNDRV_HWDEP_IFACE_CAIAQ; + hw->private_data = cdev; + hw->exclusive = 1; + hw->ops.write = lcd_hwdep_write; + + return snd_ctl_add(card, snd_ctl_new1(&lcd_contrast_control, cdev)); +} diff --git a/sound/usb/caiaq/lcd.h b/sound/usb/caiaq/lcd.h new file mode 100644 index 000000000000..423464750278 --- /dev/null +++ b/sound/usb/caiaq/lcd.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_LCD_H +#define CAIAQ_LCD_H + +int snd_usb_caiaq_lcd_init(struct snd_usb_caiaqdev *cdev); + +#endif /* CAIAQ_LCD_H */ From e8c263e0ef957fd8286e299b2be9266b902cec16 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:38:57 +0700 Subject: [PATCH 409/924] ASoC: mediatek: mt8192: fix APLL mux error handling Propagate errors from clock operations in the enable path and clean up previously enabled clocks when a later operation fails. For the disable path, continue disabling all clocks even if a clock operation fails. Log the error without returning it, so the disable path can perform as much cleanup as possible. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-clk.c | 54 +++++++++++++--------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index 416aff726253..bd778e5663b5 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -87,7 +87,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], ret); - goto EXIT; + return ret; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], afe_priv->clk[CLK_TOP_APLL1_CK]); @@ -95,7 +95,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_TOP_APLL1_CK], ret); - goto EXIT; + goto err_disable_mux_aud_1; } /* 180.6336 / 4 = 45.1584MHz */ @@ -103,7 +103,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], ret); - goto EXIT; + goto err_set_parent_mux_aud_1; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1], afe_priv->clk[CLK_TOP_APLL1_D4]); @@ -111,31 +111,36 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_TOP_APLL1_D4], ret); - goto EXIT; + goto err_disable_mux_aud_eng1; } } else { ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1], afe_priv->clk[CLK_CLK26M]); - if (ret) { + if (ret) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_CLK26M], ret); - goto EXIT; - } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], afe_priv->clk[CLK_CLK26M]); - if (ret) { + if (ret) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_CLK26M], ret); - goto EXIT; - } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); } -EXIT: + return 0; + +err_disable_mux_aud_eng1: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); +err_set_parent_mux_aud_1: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], + afe_priv->clk[CLK_CLK26M]); +err_disable_mux_aud_1: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); + return ret; } @@ -149,7 +154,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], ret); - goto EXIT; + return ret; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], afe_priv->clk[CLK_TOP_APLL2_CK]); @@ -157,7 +162,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_TOP_APLL2_CK], ret); - goto EXIT; + goto err_disable_mux_aud_2; } /* 196.608 / 4 = 49.152MHz */ @@ -165,7 +170,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], ret); - goto EXIT; + goto err_set_parent_mux_aud_2; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2], afe_priv->clk[CLK_TOP_APLL2_D4]); @@ -173,31 +178,36 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_TOP_APLL2_D4], ret); - goto EXIT; + goto err_disable_mux_aud_eng2; } } else { ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2], afe_priv->clk[CLK_CLK26M]); - if (ret) { + if (ret) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_CLK26M], ret); - goto EXIT; - } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], afe_priv->clk[CLK_CLK26M]); - if (ret) { + if (ret) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_CLK26M], ret); - goto EXIT; - } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); } -EXIT: + return 0; + +err_disable_mux_aud_eng2: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); +err_set_parent_mux_aud_2: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], + afe_priv->clk[CLK_CLK26M]); +err_disable_mux_aud_2: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); + return ret; } From 7c54781e6e60681af8d0858148d613becb582328 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:38:58 +0700 Subject: [PATCH 410/924] ASoC: mediatek: mt8192: fix AFE clock error handling If a clock operation fails in mt8192_afe_enable_clock(), the function returns immediately via the EXIT label without disabling the clocks that were successfully enabled earlier in the sequence. This leaves unneeded clocks running and causes a resource leak. Properly unwind clocks enabled before an error by jumping to appropriate cleanup labels on failure. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-clk.c | 28 +++++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index bd778e5663b5..7647bdd463d9 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -220,21 +220,21 @@ int mt8192_afe_enable_clock(struct mtk_base_afe *afe) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_INFRA_SYS_AUDIO], ret); - goto EXIT; + return ret; } ret = clk_prepare_enable(afe_priv->clk[CLK_INFRA_AUDIO_26M]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_INFRA_AUDIO_26M], ret); - goto EXIT; + goto err_disable_infra_sys_audio; } ret = clk_prepare_enable(afe_priv->clk[CLK_MUX_AUDIO]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIO], ret); - goto EXIT; + goto err_disable_infra_audio_26m; } ret = clk_set_parent(afe_priv->clk[CLK_MUX_AUDIO], afe_priv->clk[CLK_CLK26M]); @@ -242,14 +242,14 @@ int mt8192_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIO], aud_clks[CLK_CLK26M], ret); - goto EXIT; + goto err_disable_mux_audio; } ret = clk_prepare_enable(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIOINTBUS], ret); - goto EXIT; + goto err_disable_mux_audio; } ret = mt8192_set_audio_int_bus_parent(afe, CLK_CLK26M); @@ -257,7 +257,7 @@ int mt8192_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIOINTBUS], aud_clks[CLK_CLK26M], ret); - goto EXIT; + goto err_disable_mux_audiointbus; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUDIO_H], @@ -266,17 +266,27 @@ int mt8192_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUDIO_H], aud_clks[CLK_TOP_APLL2_CK], ret); - goto EXIT; + goto err_disable_mux_audiointbus; } ret = clk_prepare_enable(afe_priv->clk[CLK_AFE]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_AFE], ret); - goto EXIT; + goto err_disable_mux_audiointbus; } -EXIT: + return 0; + +err_disable_mux_audiointbus: + clk_disable_unprepare(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); +err_disable_mux_audio: + clk_disable_unprepare(afe_priv->clk[CLK_MUX_AUDIO]); +err_disable_infra_audio_26m: + clk_disable_unprepare(afe_priv->clk[CLK_INFRA_AUDIO_26M]); +err_disable_infra_sys_audio: + clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUDIO]); + return ret; } From 88760524ca995acd229c30ec69ced7d883a90a72 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:38:59 +0700 Subject: [PATCH 411/924] ASoC: mediatek: mt8192: fix error handling in APLL enable functions If a clock operation fails in mt8192_apll1_enable() or apll2_enable(), the functions return without checking the return value of mux settings or unwinding previously enabled clocks and MUX configurations. Fix this by checking the return value of apll1_mux_setting() and apll2_mux_setting(), and adding proper unwind handling on failure. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-clk.c | 32 ++++++++++++++++------ 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index 7647bdd463d9..118dd4819682 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -308,20 +308,22 @@ int mt8192_apll1_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll1_mux_setting(afe, true); + ret = apll1_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL22M]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_APLL22M], ret); - goto EXIT; + goto err_disable_mux_setting; } ret = clk_prepare_enable(afe_priv->clk[CLK_APLL1_TUNER]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_APLL1_TUNER], ret); - goto EXIT; + goto err_disable_apll22m; } regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, @@ -332,7 +334,13 @@ int mt8192_apll1_enable(struct mtk_base_afe *afe) AFE_22M_ON_MASK_SFT, 0x1 << AFE_22M_ON_SFT); -EXIT: + return 0; + +err_disable_apll22m: + clk_disable_unprepare(afe_priv->clk[CLK_APLL22M]); +err_disable_mux_setting: + apll1_mux_setting(afe, false); + return ret; } @@ -358,20 +366,22 @@ int mt8192_apll2_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll2_mux_setting(afe, true); + ret = apll2_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL24M]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_APLL24M], ret); - goto EXIT; + goto err_disable_mux_setting; } ret = clk_prepare_enable(afe_priv->clk[CLK_APLL2_TUNER]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_APLL2_TUNER], ret); - goto EXIT; + goto err_disable_apll24m; } regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, @@ -382,7 +392,13 @@ int mt8192_apll2_enable(struct mtk_base_afe *afe) AFE_24M_ON_MASK_SFT, 0x1 << AFE_24M_ON_SFT); -EXIT: + return 0; + +err_disable_apll24m: + clk_disable_unprepare(afe_priv->clk[CLK_APLL24M]); +err_disable_mux_setting: + apll2_mux_setting(afe, false); + return ret; } From 4a0ab266cb1594f8c6aef3c79726476ad99618c5 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:00 +0700 Subject: [PATCH 412/924] ASoC: mediatek: mt8192: fix MCK clock error handling If an error occurs after enabling 'm_sel_id' or 'div_clk_id' in mt8192_mck_enable(), the function returns directly without disabling the previously enabled clocks, leading to a resource leak. Fix this by jumping to proper cleanup labels to unwind enabled clocks on failure paths. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-clk.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index 118dd4819682..77f596cab92d 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -619,7 +619,7 @@ int mt8192_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) dev_err(afe->dev, "%s(), clk_set_parent %s-%s fail %d\n", __func__, aud_clks[m_sel_id], aud_clks[apll_clk_id], ret); - return ret; + goto err_disable_m_sel; } } @@ -628,17 +628,25 @@ int mt8192_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[div_clk_id], ret); - return ret; + goto err_disable_m_sel; } ret = clk_set_rate(afe_priv->clk[div_clk_id], rate); if (ret) { dev_err(afe->dev, "%s(), clk_set_rate %s, rate %d, fail %d\n", __func__, aud_clks[div_clk_id], rate, ret); - return ret; + goto err_disable_div_clk; } return 0; + +err_disable_div_clk: + clk_disable_unprepare(afe_priv->clk[div_clk_id]); +err_disable_m_sel: + if (m_sel_id >= 0) + clk_disable_unprepare(afe_priv->clk[m_sel_id]); + + return ret; } void mt8192_mck_disable(struct mtk_base_afe *afe, int mck_id) From 6f4edfdce0099dc0c346c78c85f58a23d4ee646e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:01 +0700 Subject: [PATCH 413/924] ASoC: mediatek: mt8192: switch to devm_clk_get_optional() Switch from devm_clk_get() to devm_clk_get_optional() when requesting clocks, as optional clocks automatically return NULL when not present instead of requiring manual check and NULL assignment. Additionally, handle clock errors properly using dev_err_probe() to propagate error codes (such as -EPROBE_DEFER) on failure instead of just printing a warning and continuing with NULL pointers. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-clk.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index 77f596cab92d..9b9a3465ddb5 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -672,13 +672,10 @@ int mt8192_init_clock(struct mtk_base_afe *afe) return -ENOMEM; for (i = 0; i < CLK_NUM; i++) { - afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_warn(afe->dev, "%s devm_clk_get %s fail, ret %ld\n", - __func__, - aud_clks[i], PTR_ERR(afe_priv->clk[i])); - afe_priv->clk[i] = NULL; - } + afe_priv->clk[i] = devm_clk_get_optional(afe->dev, aud_clks[i]); + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } afe_priv->apmixedsys = syscon_regmap_lookup_by_phandle(of_node, From bed395cb305b16016dd39dc1fdcd0e47674d3132 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:02 +0700 Subject: [PATCH 414/924] ASoC: mediatek: mt8192: Use dev_err_probe() in mt8192_init_clock() Use dev_err_probe() when obtaining clocks to avoid redundant error messages, particularly for probe deferral. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-clk.c | 24 ++++++++-------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index 9b9a3465ddb5..c6ca4fe7fb19 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -680,27 +680,21 @@ int mt8192_init_clock(struct mtk_base_afe *afe) afe_priv->apmixedsys = syscon_regmap_lookup_by_phandle(of_node, "mediatek,apmixedsys"); - if (IS_ERR(afe_priv->apmixedsys)) { - dev_err(afe->dev, "%s() Cannot find apmixedsys controller: %ld\n", - __func__, PTR_ERR(afe_priv->apmixedsys)); - return PTR_ERR(afe_priv->apmixedsys); - } + if (IS_ERR(afe_priv->apmixedsys)) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->apmixedsys), + "Cannot find apmixedsys controller\n"); afe_priv->topckgen = syscon_regmap_lookup_by_phandle(of_node, "mediatek,topckgen"); - if (IS_ERR(afe_priv->topckgen)) { - dev_err(afe->dev, "%s() Cannot find topckgen controller: %ld\n", - __func__, PTR_ERR(afe_priv->topckgen)); - return PTR_ERR(afe_priv->topckgen); - } + if (IS_ERR(afe_priv->topckgen)) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->topckgen), + "Cannot find topckgen controller\n"); afe_priv->infracfg = syscon_regmap_lookup_by_phandle(of_node, "mediatek,infracfg"); - if (IS_ERR(afe_priv->infracfg)) { - dev_err(afe->dev, "%s() Cannot find infracfg: %ld\n", - __func__, PTR_ERR(afe_priv->infracfg)); - return PTR_ERR(afe_priv->infracfg); - } + if (IS_ERR(afe_priv->infracfg)) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->infracfg), + "Cannot find infracfg\n"); return 0; } From 411be3902fa8f879db6f2f55d0c15507efe09c92 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:03 +0700 Subject: [PATCH 415/924] ASoC: mediatek: mt8192: Propagate errors in mt8192_afe_gpio_request() Currently, mt8192_afe_gpio_request() ignores the return values from mt8192_afe_gpio_select() and other helper functions. If configuring a GPIO state fails, the function still returns 0, hiding the failure from callers. Fix this by capturing and returning the error codes properly. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-gpio.c | 74 +++++++++++---------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c index b993ca2dbd7c..ed6dfdf2589b 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c @@ -208,98 +208,104 @@ static int mt8192_afe_gpio_adda_ch34_ul(struct device *dev, bool enable) int mt8192_afe_gpio_request(struct device *dev, bool enable, int dai, int uplink) { + int ret; + guard(mutex)(&gpio_request_mutex); switch (dai) { case MT8192_DAI_ADDA: if (uplink) - mt8192_afe_gpio_adda_ul(dev, enable); + ret = mt8192_afe_gpio_adda_ul(dev, enable); else - mt8192_afe_gpio_adda_dl(dev, enable); + ret = mt8192_afe_gpio_adda_dl(dev, enable); break; case MT8192_DAI_ADDA_CH34: if (uplink) - mt8192_afe_gpio_adda_ch34_ul(dev, enable); + ret = mt8192_afe_gpio_adda_ch34_ul(dev, enable); else - mt8192_afe_gpio_adda_ch34_dl(dev, enable); + ret = mt8192_afe_gpio_adda_ch34_dl(dev, enable); break; case MT8192_DAI_I2S_0: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S0_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S0_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S0_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S0_OFF); break; case MT8192_DAI_I2S_1: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S1_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S1_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S1_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S1_OFF); break; case MT8192_DAI_I2S_2: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S2_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S2_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S2_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S2_OFF); break; case MT8192_DAI_I2S_3: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S3_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S3_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S3_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S3_OFF); break; case MT8192_DAI_I2S_5: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S5_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S5_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S5_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S5_OFF); break; case MT8192_DAI_I2S_6: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S6_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S6_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S6_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S6_OFF); break; case MT8192_DAI_I2S_7: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S7_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S7_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S7_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S7_OFF); break; case MT8192_DAI_I2S_8: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S8_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S8_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S8_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S8_OFF); break; case MT8192_DAI_I2S_9: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S9_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S9_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S9_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S9_OFF); break; case MT8192_DAI_TDM: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_TDM_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_TDM_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_TDM_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_TDM_OFF); break; case MT8192_DAI_VOW: if (enable) { - mt8192_afe_gpio_select(dev, - MT8192_AFE_GPIO_VOW_CLK_ON); - mt8192_afe_gpio_select(dev, - MT8192_AFE_GPIO_VOW_DAT_ON); + ret = mt8192_afe_gpio_select(dev, + MT8192_AFE_GPIO_VOW_CLK_ON); + if (ret) + break; + ret = mt8192_afe_gpio_select(dev, + MT8192_AFE_GPIO_VOW_DAT_ON); } else { - mt8192_afe_gpio_select(dev, - MT8192_AFE_GPIO_VOW_CLK_OFF); - mt8192_afe_gpio_select(dev, - MT8192_AFE_GPIO_VOW_DAT_OFF); + ret = mt8192_afe_gpio_select(dev, + MT8192_AFE_GPIO_VOW_CLK_OFF); + if (ret) + break; + ret = mt8192_afe_gpio_select(dev, + MT8192_AFE_GPIO_VOW_DAT_OFF); } break; default: dev_warn(dev, "%s(), invalid dai %d\n", __func__, dai); - return -EINVAL; + ret = -EINVAL; } - return 0; + return ret; } EXPORT_SYMBOL(mt8192_afe_gpio_request); From 59608878c59373c5f866000fff81be8eaea8a7ef Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:04 +0700 Subject: [PATCH 416/924] ASoC: mediatek: mt8192: Use dev_err_probe() for devm_pinctrl_get() Use dev_err_probe() when obtaining clocks to avoid redundant error messages, particularly for probe deferral. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-9-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-gpio.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c index ed6dfdf2589b..d4218f9708f7 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c @@ -132,12 +132,8 @@ int mt8192_afe_gpio_init(struct device *dev) int i, ret; aud_pinctrl = devm_pinctrl_get(dev); - if (IS_ERR(aud_pinctrl)) { - ret = PTR_ERR(aud_pinctrl); - dev_err(dev, "%s(), ret %d, cannot get aud_pinctrl!\n", - __func__, ret); - return ret; - } + if (IS_ERR(aud_pinctrl)) + return dev_err_probe(dev, PTR_ERR(aud_pinctrl), "cannot get aud_pinctrl!\n"); for (i = 0; i < ARRAY_SIZE(aud_gpios); i++) { aud_gpios[i].gpioctrl = pinctrl_lookup_state(aud_pinctrl, From 7cca184ad434436e8f4648f71756bab0c3593ed9 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:05 +0700 Subject: [PATCH 417/924] ASoC: mediatek: mt8192: check return values in mt8192_afe_gpio_init() Currently, mt8192_afe_gpio_init() ignores the return values of mt8192_afe_gpio_select() and mt8192_afe_gpio_request(). If configuring GPIO states or requesting GPIOs fails during initialization, the function still proceeds and returns 0, masking the initialization failure. Fix this by checking and propagating the return values properly. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-10-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-gpio.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c index d4218f9708f7..d2e99c55b8c6 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c @@ -147,13 +147,17 @@ int mt8192_afe_gpio_init(struct device *dev) } } - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_CLK_MOSI_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_CLK_MOSI_ON); + if (ret) + return ret; /* gpio status init */ - mt8192_afe_gpio_request(dev, false, MT8192_DAI_ADDA, 0); - mt8192_afe_gpio_request(dev, false, MT8192_DAI_ADDA, 1); + ret = mt8192_afe_gpio_request(dev, false, MT8192_DAI_ADDA, 0); + if (ret) + return ret; + ret = mt8192_afe_gpio_request(dev, false, MT8192_DAI_ADDA, 1); - return 0; + return ret; } EXPORT_SYMBOL(mt8192_afe_gpio_init); From d9e264741b041fc9dffc555bc1ab68708658cefd Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:06 +0700 Subject: [PATCH 418/924] ASoC: mediatek: mt8192: Handle regcache_sync() failure in runtime resume Currently, the return value of regcache_sync() in mt8192_afe_runtime_resume() is ignored. If regcache_sync() fails, the function continues execution with clocks enabled and regcache out of sync. Fix this by checking the return value of regcache_sync(). On failure, restore regcache to cache-only mode and disable the clocks enabled earlier in the function. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-11-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-pcm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c index db0ae44a86af..5e633e9573a3 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c @@ -2104,7 +2104,11 @@ static int mt8192_afe_runtime_resume(struct device *dev) goto skip_regmap; regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) { + regcache_cache_only(afe->regmap, true); + mt8192_afe_disable_clock(afe); + } /* enable audio sys DCM for power saving */ regmap_update_bits(afe_priv->infracfg, From d31c0d3eb4142347cb35486e8fc1fe68a4555987 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:07 +0700 Subject: [PATCH 419/924] ASoC: mediatek: mt8192: Remove redundant error message The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() and dev_err_probe() call is redundant and can be removed. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-12-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-pcm.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c index 5e633e9573a3..a9b203af9039 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c @@ -2201,10 +2201,8 @@ static int mt8192_afe_pcm_dev_probe(struct platform_device *pdev) /* init audio related clock */ ret = mt8192_init_clock(afe); - if (ret) { - dev_err(dev, "init clock error\n"); + if (ret) return ret; - } /* reset controller to reset audio regs before regmap cache */ rstc = devm_reset_control_get_exclusive(dev, "audiosys"); @@ -2280,7 +2278,7 @@ static int mt8192_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt8192_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); From eed9414a1b075e2df308e7222e3786981bd42b32 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:08 +0700 Subject: [PATCH 420/924] ASoC: mediatek: mt8192: Propagate mt8192_afe_gpio_request() errors in ADDA DAI Currently, DAPM event handlers in mt8192-dai-adda.c ignore the return value of mt8192_afe_gpio_request(). If GPIO configuration fails during PRE_PMU or POST_PMD events, the error is silently ignored. Fix this by checking and propagating error codes from mt8192_afe_gpio_request() in all ADDA DAPM event handlers. Fixes: 607ac4859564 ("ASoC: mediatek: mt8192: support adda in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-13-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-dai-adda.c | 44 +++++++++++++++------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-dai-adda.c b/sound/soc/mediatek/mt8192/mt8192-dai-adda.c index f8cb84621d38..c5c726a6e423 100644 --- a/sound/soc/mediatek/mt8192/mt8192-dai-adda.c +++ b/sound/soc/mediatek/mt8192/mt8192-dai-adda.c @@ -218,10 +218,13 @@ static int mtk_adda_ul_event(struct snd_soc_dapm_widget *w, struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mt8192_afe_private *afe_priv = afe->platform_priv; int mtkaif_dmic = afe_priv->mtkaif_dmic; + int ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1); + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1); + if (ret) + return ret; /* update setting to dmic */ if (mtkaif_dmic) { @@ -239,7 +242,9 @@ static int mtk_adda_ul_event(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_POST_PMD: /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ usleep_range(125, 135); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 1); + ret = mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 1); + if (ret) + return ret; break; default: break; @@ -257,11 +262,14 @@ static int mtk_adda_ch34_ul_event(struct snd_soc_dapm_widget *w, struct mt8192_afe_private *afe_priv = afe->platform_priv; int mtkaif_dmic = afe_priv->mtkaif_dmic_ch34; int mtkaif_adda6_only = afe_priv->mtkaif_adda6_only; + int ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, - 1); + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, + 1); + if (ret) + return ret; /* update setting to dmic */ if (mtkaif_dmic) { @@ -291,8 +299,10 @@ static int mtk_adda_ch34_ul_event(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_POST_PMD: /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ usleep_range(125, 135); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, - 1); + ret = mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, + 1); + if (ret) + return ret; /* reset dmic */ afe_priv->mtkaif_dmic_ch34 = 0; @@ -446,15 +456,20 @@ static int mtk_adda_dl_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0); + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ usleep_range(125, 135); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 0); + ret = mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 0); + if (ret) + return ret; break; default: break; @@ -469,17 +484,22 @@ static int mtk_adda_ch34_dl_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, - 0); + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, + 0); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ usleep_range(125, 135); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, - 0); + ret = mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, + 0); + if (ret) + return ret; break; default: break; From d0bc9613bc2d9e20a7e4f3e447cd9427c95d194a Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:09 +0700 Subject: [PATCH 421/924] ASoC: mediatek: mt8192: Propagate errors in TDM DAI DAPM event handlers Currently, DAPM event handlers in mt8192-dai-tdm.c ignore return values from mt8192_afe_gpio_request() and mt8192_mck_enable(). If GPIO configuration or master clock setup fails during DAPM events, the error is silently ignored. Fix this by checking and propagating error codes properly in all TDM DAI DAPM event handlers. Fixes: 52fcd65414ab ("ASoC: mediatek: mt8192: support tdm in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-14-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-dai-tdm.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-dai-tdm.c b/sound/soc/mediatek/mt8192/mt8192-dai-tdm.c index 49440db370af..36fa0f32ed1c 100644 --- a/sound/soc/mediatek/mt8192/mt8192-dai-tdm.c +++ b/sound/soc/mediatek/mt8192/mt8192-dai-tdm.c @@ -250,6 +250,7 @@ static int mtk_tdm_en_event(struct snd_soc_dapm_widget *w, struct mt8192_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; if (!tdm_priv) { dev_warn(afe->dev, "%s(), tdm_priv == NULL", __func__); @@ -261,16 +262,17 @@ static int mtk_tdm_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, tdm_priv->id, 0); + ret = mt8192_afe_gpio_request(afe->dev, true, tdm_priv->id, 0); break; case SND_SOC_DAPM_POST_PMD: - mt8192_afe_gpio_request(afe->dev, false, tdm_priv->id, 0); + ret = mt8192_afe_gpio_request(afe->dev, false, tdm_priv->id, 0); break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, @@ -282,6 +284,7 @@ static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, struct mt8192_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; if (!tdm_priv) { dev_warn(afe->dev, "%s(), tdm_priv == NULL", __func__); @@ -293,7 +296,9 @@ static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); + ret = mt8192_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: mt8192_mck_disable(afe, tdm_priv->bck_id); @@ -314,6 +319,7 @@ static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, struct mt8192_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; if (!tdm_priv) { dev_warn(afe->dev, "%s(), tdm_priv == NULL", __func__); @@ -325,7 +331,9 @@ static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); + ret = mt8192_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: tdm_priv->mclk_rate = 0; From 49aeb628d10ef3d5694c39b7a3427d34bde06bb2 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:10 +0700 Subject: [PATCH 422/924] ASoC: mediatek: mt8192: Propagate errors in I2S DAI DAPM event handlers Currently, DAPM event handlers in mt8192-dai-i2s.c ignore return values from mt8192_afe_gpio_request(), mt8192_apll1_enable(), mt8192_apll2_enable(), and mt8192_mck_enable(). If GPIO configuration or clock setup fails during DAPM events, the error is silently ignored. Fix this by checking and propagating error codes properly in all I2S DAI DAPM event handlers. Fixes: 2c37b4ed730b ("ASoC: mediatek: mt8192: support i2s in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-15-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-dai-i2s.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-dai-i2s.c b/sound/soc/mediatek/mt8192/mt8192-dai-i2s.c index 1632fc94776d..5608b534af0d 100644 --- a/sound/soc/mediatek/mt8192/mt8192-dai-i2s.c +++ b/sound/soc/mediatek/mt8192/mt8192-dai-i2s.c @@ -586,6 +586,7 @@ static int mtk_i2s_en_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mtk_afe_i2s_priv *i2s_priv; + int ret; i2s_priv = get_i2s_priv_by_name(afe, w->name); @@ -599,16 +600,17 @@ static int mtk_i2s_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, i2s_priv->id, 0); + ret = mt8192_afe_gpio_request(afe->dev, true, i2s_priv->id, 0); break; case SND_SOC_DAPM_POST_PMD: - mt8192_afe_gpio_request(afe->dev, false, i2s_priv->id, 0); + ret = mt8192_afe_gpio_request(afe->dev, false, i2s_priv->id, 0); break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_apll_event(struct snd_soc_dapm_widget *w, @@ -617,6 +619,7 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", __func__, w->name, event); @@ -624,9 +627,11 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: if (snd_soc_dapm_widget_name_cmp(w, APLL1_W_NAME) == 0) - mt8192_apll1_enable(afe); + ret = mt8192_apll1_enable(afe); else - mt8192_apll2_enable(afe); + ret = mt8192_apll2_enable(afe); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: if (snd_soc_dapm_widget_name_cmp(w, APLL1_W_NAME) == 0) @@ -704,6 +709,7 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mtk_afe_i2s_priv *i2s_priv; + int ret; dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", __func__, w->name, event); @@ -716,7 +722,9 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); + ret = mt8192_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: i2s_priv->mclk_rate = 0; From d6c8a0b5bf696d89350e43bf554362de8929f801 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:11 +0700 Subject: [PATCH 423/924] ASoC: mediatek: mt8192-mt6359: Fix error handling in MTKAIF calibration mt8192_mt6359_mtkaif_calibration() ignored the return values of pm_runtime_get_sync() and mt8192_afe_gpio_request(), which could leave PM runtime references or GPIO state unbalanced on failure. Switch to pm_runtime_resume_and_get(), check each GPIO request, and unwind on error. Fixes: 18b13ff23fab ("ASoC: mediatek: mt8192: add machine driver with mt6359, rt1015 and rt5682") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-16-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- .../mt8192/mt8192-mt6359-rt1015-rt5682.c | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c index c86348691641..b010726e9b57 100644 --- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c @@ -163,12 +163,23 @@ static int mt8192_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) int chosen_phase_1, chosen_phase_2, chosen_phase_3; int counter; int mtkaif_calib_ok; + int ret = 0; - pm_runtime_get_sync(afe->dev); - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1); - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0); - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, 1); - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, 0); + ret = pm_runtime_resume_and_get(afe->dev); + if (ret < 0) + return ret; + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1); + if (ret) + goto err_pm_put; + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0); + if (ret) + goto err_disable_adda_1; + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, 1); + if (ret) + goto err_disable_adda_0; + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, 0); + if (ret) + goto err_disable_adda_ch34_1; mt6359_mtkaif_calibration_enable(cmpnt_codec); @@ -289,10 +300,14 @@ static int mt8192_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) mt6359_mtkaif_calibration_disable(cmpnt_codec); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 1); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 0); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, 1); mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, 0); +err_disable_adda_ch34_1: + mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, 1); +err_disable_adda_0: + mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 0); +err_disable_adda_1: + mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 1); +err_pm_put: pm_runtime_put(afe->dev); dev_dbg(afe->dev, "%s(), mtkaif_chosen_phase[0/1/2]:%d/%d/%d\n", @@ -301,7 +316,7 @@ static int mt8192_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) afe_priv->mtkaif_chosen_phase[1], afe_priv->mtkaif_chosen_phase[2]); - return 0; + return ret; } static int mt8192_mt6359_init(struct snd_soc_pcm_runtime *rtd) @@ -319,9 +334,7 @@ static int mt8192_mt6359_init(struct snd_soc_pcm_runtime *rtd) afe_priv->mtkaif_protocol = MTKAIF_PROTOCOL_2_CLK_P2; /* mtkaif calibration */ - mt8192_mt6359_mtkaif_calibration(rtd); - - return 0; + return mt8192_mt6359_mtkaif_calibration(rtd); } static int mt8192_rt5682_init(struct snd_soc_pcm_runtime *rtd) From 7845324094fcf88060170b44661519e57b179b61 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Mon, 28 Sep 2026 14:11:45 +0800 Subject: [PATCH 424/924] ASoC: dt-bindings: fsl,imx-asrc: update port binding to support multiple paths All i.MX ASRC variants (imx35 through imx952) support up to three conversion pairs (A, B, C). The existing binding exposed only a single generic audio-graph port, which cannot represent independent conversion paths when multiple pairs are in use simultaneously. Add a ports container that enumerates up to three sub-ports, each representing an independent conversion path: port@0 -- conversion path 0 port@1 -- conversion path 1 port@2 -- conversion path 2 Each sub-port references audio-graph-port.yaml as before. Hardware pairs are allocated dynamically at stream open time; each active stream direction (playback or capture) on a port consumes one hardware pair. For backwards compatibility, keep the old single port property but mark it deprecated. A schema constraint (not: required: [port, ports]) ensures that both forms cannot be used at the same time. The binding example is updated to show the preferred ports form. Signed-off-by: Shengjiu Wang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260928061149.1844121-2-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- .../bindings/sound/fsl,imx-asrc.yaml | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml b/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml index cd69bad107c7..67d2826eef3f 100644 --- a/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml @@ -78,9 +78,19 @@ properties: power-domains: maxItems: 1 + ports: + $ref: /schemas/graph.yaml#/properties/ports + patternProperties: + "^port@[0-2]$": + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: port for an independent conversion path + port: $ref: audio-graph-port.yaml# unevaluatedProperties: false + deprecated: true + description: deprecated, use ports instead fsl,asrc-rate: $ref: /schemas/types.yaml#/definitions/uint32 @@ -125,6 +135,10 @@ required: - fsl,asrc-width allOf: + - not: + required: + - port + - ports - $ref: dai-common.yaml# - if: properties: @@ -180,11 +194,32 @@ examples: fsl,asrc-rate = <48000>; fsl,asrc-width = <16>; - port { - playback-only; + ports { + #address-cells = <1>; + #size-cells = <0>; - asrc_endpoint: endpoint { - remote-endpoint = <&fe00_ep>; + port@0 { + reg = <0>; + playback-only; + endpoint { + remote-endpoint = <&fe00_ep>; + }; + }; + + port@1 { + reg = <1>; + capture-only; + endpoint { + remote-endpoint = <&fe01_ep>; + }; + }; + + port@2 { + reg = <2>; + capture-only; + endpoint { + remote-endpoint = <&fe02_ep>; + }; }; }; }; From 33598785b31c1a550e193b7c7dc5402fa5d04a13 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Mon, 28 Sep 2026 14:11:46 +0800 Subject: [PATCH 425/924] ASoC: dt-bindings: fsl,easrc: add ports binding for multiple conversion paths The i.MX EASRC hardware supports up to four conversion contexts (A, B, C, D). Add a ports container property to the binding to allow each independent conversion path to be represented as an individual audio-graph port: port@0 -- conversion path 0 port@1 -- conversion path 1 port@2 -- conversion path 2 port@3 -- conversion path 3 Each sub-port references audio-graph-port.yaml and follows the standard audio-graph binding conventions. Contexts are allocated dynamically at stream open time; each active stream direction (playback or capture) on a port consumes one hardware context. Signed-off-by: Shengjiu Wang Reviewed-by: Frank Li Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260928061149.1844121-3-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/fsl,easrc.yaml | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/fsl,easrc.yaml b/Documentation/devicetree/bindings/sound/fsl,easrc.yaml index d5727f8bfb0b..a53ec4f6f1c0 100644 --- a/Documentation/devicetree/bindings/sound/fsl,easrc.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,easrc.yaml @@ -55,6 +55,14 @@ properties: - const: imx/easrc/easrc-imx8mn.bin description: The coefficient table for the filters + ports: + $ref: /schemas/graph.yaml#/properties/ports + patternProperties: + "^port@[0-3]$": + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: port for an independent conversion path + fsl,asrc-rate: $ref: /schemas/types.yaml#/definitions/uint32 minimum: 8000 @@ -106,4 +114,41 @@ examples: firmware-name = "imx/easrc/easrc-imx8mn.bin"; fsl,asrc-rate = <8000>; fsl,asrc-format = <2>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + playback-only; + endpoint { + remote-endpoint = <&fe00_ep>; + }; + }; + + port@1 { + reg = <1>; + playback-only; + endpoint { + remote-endpoint = <&fe01_ep>; + }; + }; + + port@2 { + reg = <2>; + capture-only; + endpoint { + remote-endpoint = <&fe02_ep>; + }; + }; + + port@3 { + reg = <3>; + capture-only; + endpoint { + remote-endpoint = <&fe03_ep>; + }; + }; + }; }; From 76915128bde26af7e357dfd4501f5e329d1364be Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Mon, 28 Sep 2026 14:11:47 +0800 Subject: [PATCH 426/924] ASoC: fsl_asrc/fsl_easrc: move DMA params into pair/context struct The shared dma_params_tx/rx fields in struct fsl_asrc were a single instance written by every concurrent fsl_asrc_dma_hw_params() call. With multiple DAIs registered (one per pair/context), parallel stream open+hw_params races would corrupt addr and maxburst for all active streams. Fix by moving dma_params into struct fsl_asrc_pair so each pair owns its own copy. Initialise them in fsl_asrc_dma_startup() where the pair is allocated, which makes the per-DAI probe callbacks in fsl_asrc.c and fsl_easrc.c redundant; remove those as well. Signed-off-by: Shengjiu Wang Reviewed-by: Frank Li Reviewed-by: Daniel Baluta Link: https://patch.msgid.link/20260928061149.1844121-4-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc.c | 11 ----------- sound/soc/fsl/fsl_asrc_common.h | 7 +++---- sound/soc/fsl/fsl_asrc_dma.c | 14 ++++++++++++++ sound/soc/fsl/fsl_easrc.c | 11 ----------- 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index cc304abbc8bd..9be903dc75e4 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -781,18 +781,7 @@ static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd, return 0; } -static int fsl_asrc_dai_probe(struct snd_soc_dai *dai) -{ - struct fsl_asrc *asrc = snd_soc_dai_get_drvdata(dai); - - snd_soc_dai_init_dma_data(dai, &asrc->dma_params_tx, - &asrc->dma_params_rx); - - return 0; -} - static const struct snd_soc_dai_ops fsl_asrc_dai_ops = { - .probe = fsl_asrc_dai_probe, .startup = fsl_asrc_dai_startup, .hw_params = fsl_asrc_dai_hw_params, .hw_free = fsl_asrc_dai_hw_free, diff --git a/sound/soc/fsl/fsl_asrc_common.h b/sound/soc/fsl/fsl_asrc_common.h index c8a1a2b5915d..4e6b00cb5d63 100644 --- a/sound/soc/fsl/fsl_asrc_common.h +++ b/sound/soc/fsl/fsl_asrc_common.h @@ -53,6 +53,7 @@ struct fsl_asrc_m2m_cap { * @dma_data: private dma data * @pos: hardware pointer position * @req_dma_chan: flag to release dev_to_dev chan + * @dma_params: DMA parameters for transmit/receive channel * @private: pair private area * @complete: dma task complete * @sample_format: format of m2m @@ -76,6 +77,8 @@ struct fsl_asrc_pair { unsigned int pos; bool req_dma_chan; + struct snd_dmaengine_dai_dma_data dma_params; + void *private; /* used for m2m */ @@ -92,8 +95,6 @@ struct fsl_asrc_pair { /** * fsl_asrc: ASRC common data * - * @dma_params_rx: DMA parameters for receive channel - * @dma_params_tx: DMA parameters for transmit channel * @pdev: platform device pointer * @regmap: regmap handler * @paddr: physical address to the base address of registers @@ -128,8 +129,6 @@ struct fsl_asrc_pair { * @private: private data structure */ struct fsl_asrc { - struct snd_dmaengine_dai_dma_data dma_params_rx; - struct snd_dmaengine_dai_dma_data dma_params_tx; struct platform_device *pdev; struct regmap *regmap; unsigned long paddr; diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index 0aa5db8973d6..b417de6abebe 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -400,6 +400,20 @@ static int fsl_asrc_dma_startup(struct snd_soc_component *component, runtime->private_data = pair; + /* + * Point the cpu DAI dma_data at the per-pair params so that + * concurrent hw_params calls on different pairs each write to + * their own struct and do not race on addr/maxburst. Use the + * per-direction setters so that a concurrent open of the other + * direction on the same DAI does not NULL out its pointer. + */ + if (tx) + snd_soc_dai_dma_data_set_playback(snd_soc_rtd_to_cpu(rtd, 0), + &pair->dma_params); + else + snd_soc_dai_dma_data_set_capture(snd_soc_rtd_to_cpu(rtd, 0), + &pair->dma_params); + /* Request a dummy pair, which will be released later. * Request pair function needs channel num as input, for this * dummy pair, we just request "1" channel temporarily. diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index d30cc3e90215..4401c4e100b3 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -1582,18 +1582,7 @@ static int fsl_easrc_hw_free(struct snd_pcm_substream *substream, return 0; } -static int fsl_easrc_dai_probe(struct snd_soc_dai *cpu_dai) -{ - struct fsl_asrc *easrc = dev_get_drvdata(cpu_dai->dev); - - snd_soc_dai_init_dma_data(cpu_dai, - &easrc->dma_params_tx, - &easrc->dma_params_rx); - return 0; -} - static const struct snd_soc_dai_ops fsl_easrc_dai_ops = { - .probe = fsl_easrc_dai_probe, .startup = fsl_easrc_startup, .trigger = fsl_easrc_trigger, .hw_params = fsl_easrc_hw_params, From 6aef9cb5c7977dae92a90e6e732f14e3784660cf Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Mon, 28 Sep 2026 14:17:27 +0800 Subject: [PATCH 427/924] ASoC: fsl_asrc: expose individual DAIs per conversion path The i.MX ASRC hardware supports three independent conversion pairs (A, B, C). The driver previously registered a single DAI with generic stream names "ASRC-Playback" and "ASRC-Capture", which prevents multiple independent conversion paths from being used simultaneously. Replace the single fsl_asrc_dai instance with an array of three DAI drivers to allow up to three independent conversion paths to be active at the same time: asrc-0 -- ASRC-Playback / ASRC-Capture (backward compatible) asrc-1 -- ASRC1-Playback / ASRC1-Capture asrc-2 -- ASRC2-Playback / ASRC2-Capture asrc-0 retains the original generic stream names to preserve backward compatibility with existing machine drivers and board configurations. Each DAI retains the same channel, rate and format capabilities as the original. Update the devm_snd_soc_register_component() call to register all three DAIs. Signed-off-by: Shengjiu Wang Reviewed-by: Chancel Liu Reviewed-by: Frank Li Link: https://patch.msgid.link/20260928061727.1844167-1-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc.c | 89 +++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 20 deletions(-) diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index 9be903dc75e4..faf348dd582d 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -792,27 +792,76 @@ static const struct snd_soc_dai_ops fsl_asrc_dai_ops = { SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_3LE) -static struct snd_soc_dai_driver fsl_asrc_dai = { - .playback = { - .stream_name = "ASRC-Playback", - .channels_min = 1, - .channels_max = 10, - .rate_min = 5512, - .rate_max = 192000, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_ASRC_FORMATS | - SNDRV_PCM_FMTBIT_S8, +static struct snd_soc_dai_driver fsl_asrc_dai[] = { + { + .name = "asrc-0", + .playback = { + .stream_name = "ASRC-Playback", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS | + SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "ASRC-Capture", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS, + }, + .ops = &fsl_asrc_dai_ops, }, - .capture = { - .stream_name = "ASRC-Capture", - .channels_min = 1, - .channels_max = 10, - .rate_min = 5512, - .rate_max = 192000, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_ASRC_FORMATS, + { + .name = "asrc-1", + .playback = { + .stream_name = "ASRC1-Playback", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS | + SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "ASRC1-Capture", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS, + }, + .ops = &fsl_asrc_dai_ops, + }, + { + .name = "asrc-2", + .playback = { + .stream_name = "ASRC2-Playback", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS | + SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "ASRC2-Capture", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS, + }, + .ops = &fsl_asrc_dai_ops, }, - .ops = &fsl_asrc_dai_ops, }; static bool fsl_asrc_readable_reg(struct device *dev, unsigned int reg) @@ -1393,7 +1442,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) goto err_pm_get_sync; ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component, - &fsl_asrc_dai, 1); + fsl_asrc_dai, ARRAY_SIZE(fsl_asrc_dai)); if (ret) { dev_err(&pdev->dev, "failed to register ASoC DAI\n"); goto err_pm_get_sync; From 788e893edb6a18a042afed661d3fabc2c0c101c9 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Mon, 28 Sep 2026 14:17:56 +0800 Subject: [PATCH 428/924] ASoC: fsl_easrc: expose individual DAIs per conversion path The i.MX EASRC hardware supports four independent conversion contexts (A, B, C, D). The driver previously registered a single DAI with generic stream names "ASRC-Playback" and "ASRC-Capture", which prevents multiple independent conversion paths from being used simultaneously. Replace the single fsl_easrc_dai instance with an array of four DAI drivers to allow up to four independent conversion paths to be active at the same time: easrc-0 -- ASRC-Playback / ASRC-Capture (backward compatible) easrc-1 -- ASRC1-Playback / ASRC1-Capture easrc-2 -- ASRC2-Playback / ASRC2-Capture easrc-3 -- ASRC3-Playback / ASRC3-Capture easrc-0 retains the original generic stream names to preserve backward compatibility with existing machine drivers and board configurations. Each DAI retains the same channel, rate and format capabilities as the original, including IEC958 subframe support on the capture side. Update the devm_snd_soc_register_component() call to register all four DAIs. Signed-off-by: Shengjiu Wang Reviewed-by: Chancel Liu Reviewed-by: Frank Li Reviewed-by: Daniel Baluta Link: https://patch.msgid.link/20260928061756.1844185-1-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_easrc.c | 112 +++++++++++++++++++++++++++++++------- 1 file changed, 92 insertions(+), 20 deletions(-) diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index 4401c4e100b3..5d715bc07357 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -1589,27 +1589,99 @@ static const struct snd_soc_dai_ops fsl_easrc_dai_ops = { .hw_free = fsl_easrc_hw_free, }; -static struct snd_soc_dai_driver fsl_easrc_dai = { - .playback = { - .stream_name = "ASRC-Playback", - .channels_min = 1, - .channels_max = 32, - .rate_min = 8000, - .rate_max = 768000, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_EASRC_FORMATS, +static struct snd_soc_dai_driver fsl_easrc_dai[] = { + { + .name = "easrc-0", + .playback = { + .stream_name = "ASRC-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, }, - .capture = { - .stream_name = "ASRC-Capture", - .channels_min = 1, - .channels_max = 32, - .rate_min = 8000, - .rate_max = 768000, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_EASRC_FORMATS | - SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + { + .name = "easrc-1", + .playback = { + .stream_name = "ASRC1-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC1-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, + }, + { + .name = "easrc-2", + .playback = { + .stream_name = "ASRC2-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC2-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, + }, + { + .name = "easrc-3", + .playback = { + .stream_name = "ASRC3-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC3-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, }, - .ops = &fsl_easrc_dai_ops, }; static const struct snd_soc_component_driver fsl_easrc_component = { @@ -2234,7 +2306,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) regcache_cache_only(easrc->regmap, true); ret = devm_snd_soc_register_component(dev, &fsl_easrc_component, - &fsl_easrc_dai, 1); + fsl_easrc_dai, ARRAY_SIZE(fsl_easrc_dai)); if (ret) { dev_err(dev, "failed to register ASoC DAI\n"); goto err_pm_disable; From 1bbc033983b76f41d51d4a48838bf94c5e6b6f50 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Mon, 28 Sep 2026 13:23:52 -0500 Subject: [PATCH 429/924] ALSA: timer: Handle userspace-driven timer ioctls from 32-bit tasks 32-bit tasks on a 64-bit kernel can't use userspace-driven timers. The compat handler for /dev/snd/timer doesn't list SNDRV_TIMER_IOCTL_CREATE, so creating a timer fails with -ENOTTY. The file returned for a timer has no .compat_ioctl either, so SNDRV_TIMER_IOCTL_TRIGGER fails with -ENOTTY too. struct snd_timer_uinfo has the same layout for 32-bit and 64-bit tasks, and SNDRV_TIMER_IOCTL_TRIGGER takes no argument, so both commands can be passed through unchanged. The utimer selftest doesn't catch this when built with -m32: it takes -ENOTTY from SNDRV_TIMER_IOCTL_CREATE to mean CONFIG_SND_UTIMER=n and skips. Fixes: 37745918e0e7 ("ALSA: timer: Introduce virtual userspace-driven timers") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260928182352.309457-1-danishkhateeb03@gmail.com Signed-off-by: Takashi Iwai --- sound/core/timer.c | 1 + sound/core/timer_compat.c | 1 + 2 files changed, 2 insertions(+) diff --git a/sound/core/timer.c b/sound/core/timer.c index 679b26435670..69a59b25fe0a 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -2176,6 +2176,7 @@ static const struct file_operations snd_utimer_fops = { .llseek = noop_llseek, .release = snd_utimer_release, .unlocked_ioctl = snd_utimer_ioctl, + .compat_ioctl = snd_utimer_ioctl, }; static int snd_utimer_start(struct snd_timer *t) diff --git a/sound/core/timer_compat.c b/sound/core/timer_compat.c index 25ee81c1668b..5bab00466de7 100644 --- a/sound/core/timer_compat.c +++ b/sound/core/timer_compat.c @@ -99,6 +99,7 @@ static long __snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, case SNDRV_TIMER_IOCTL_PAUSE: case SNDRV_TIMER_IOCTL_PAUSE_OLD: case SNDRV_TIMER_IOCTL_NEXT_DEVICE: + case SNDRV_TIMER_IOCTL_CREATE: return __snd_timer_user_ioctl(file, cmd, (unsigned long)argp, true); case SNDRV_TIMER_IOCTL_GPARAMS32: return snd_timer_user_gparams_compat(file, argp); From 2d1d019ddd292ac4e570f044e936acf974a0f866 Mon Sep 17 00:00:00 2001 From: Xucheng Pan Date: Wed, 23 Sep 2026 23:25:52 +0800 Subject: [PATCH 430/924] ALSA: usb-audio: Re-read descriptors for Xiaomi 2717:d005 The Xiaomi 2717:d005 USB audio transmitter supports Sound 2 Pro and Sound 2 Max speakers. This issue was reproduced with two Sound 2 Pro speakers paired in stereo. After a USB replug, Linux detects the transmitter and exposes a hardware volume control, but moving the volume slider changes neither the audible level nor the speakers' volume LEDs. Windows controls the same speakers with its in-box USB Audio driver. USB captures show standard UAC2 volume SET_CUR requests on both systems. Re-reading the device descriptor (18 bytes), configuration header (9 bytes), and full configuration descriptor (184 bytes on the tested unit) after 2717:d005 is configured restores hardware volume control. The USBFS sequence recovered the device after multiple replug tests. Reading only the device descriptor did not restore volume control. Perform these reads when snd-usb-audio first probes 2717:d005. Use the configuration header's wTotalLength for the final read. If a read fails, warn and continue probing so that playback remains available. Tested on 7.2.6-zen2-1-zen with the existing userspace workaround disabled. After a physical USB replug, the quirk ran when 2717:d005 appeared, and both audible volume and the speakers' LEDs followed the KDE volume slider. Signed-off-by: Xucheng Pan Link: https://patch.msgid.link/20260923152552.943151-1-panxucc@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 2b169aabf6f9..20140cf7f4aa 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -871,6 +871,49 @@ static int snd_usb_cm1a_boot_quirk(struct usb_device *dev) return 0; } +/* + * The Xiaomi audio connector ignores UAC2 volume changes after reconnecting + * until the device and configuration descriptors have been read again. + */ +#define XIAOMI_AUDIO_MAX_CONFIG_SIZE 1024 + +static void snd_usb_xiaomi_boot_quirk(struct usb_device *dev) +{ + u8 *buf __free(kfree) = kmalloc(XIAOMI_AUDIO_MAX_CONFIG_SIZE, GFP_KERNEL); + struct usb_config_descriptor *config; + unsigned int length; + int ret; + + if (!buf) + return; + + ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, buf, + USB_DT_DEVICE_SIZE); + if (ret != USB_DT_DEVICE_SIZE) { + dev_warn(&dev->dev, "device descriptor re-read failed: %d\n", ret); + return; + } + + ret = usb_get_descriptor(dev, USB_DT_CONFIG, 0, buf, + USB_DT_CONFIG_SIZE); + if (ret != USB_DT_CONFIG_SIZE) { + dev_warn(&dev->dev, "configuration header re-read failed: %d\n", ret); + return; + } + + config = (struct usb_config_descriptor *)buf; + length = le16_to_cpu(config->wTotalLength); + if (length < USB_DT_CONFIG_SIZE || + length > XIAOMI_AUDIO_MAX_CONFIG_SIZE) { + dev_warn(&dev->dev, "unexpected configuration length: %u\n", length); + return; + } + + ret = usb_get_descriptor(dev, USB_DT_CONFIG, 0, buf, length); + if (ret != (int)length) + dev_warn(&dev->dev, "configuration descriptor re-read failed: %d\n", ret); +} + /* * Some sound cards from Native Instruments are in fact compliant to the USB * audio standard of version 2 and other approved USB standards, even though @@ -1743,6 +1786,9 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, return snd_usb_motu_m_series_boot_quirk(dev); case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ return snd_usb_cm1a_boot_quirk(dev); + case USB_ID(0x2717, 0xd005): /* Xiaomi audio connector */ + snd_usb_xiaomi_boot_quirk(dev); + return 0; } return 0; From d842c03f89aef141874149f8dc8a28c104455f32 Mon Sep 17 00:00:00 2001 From: Darren Chang Date: Sun, 20 Sep 2026 20:26:53 +0800 Subject: [PATCH 431/924] ALSA: usb-audio: Add quirk for inverted sample rates on NUX NAI-24 The NUX NAI-24 (USB 3703:2000) has a UAC2 clock source that reports bmAttributes = 0x01 (internal fixed clock) and bmControls = 0x07, so the driver treats its sample rate as programmable. When the driver sends SET_CUR(SAMPLING_FREQ_CONTROL), the firmware acknowledges the request and then runs the clock on the opposite base-rate family: asking for 44100 Hz makes the device run at 48000 Hz, and asking for 48000 Hz makes it run at 44100 Hz. The result is playback that is about 8.8% fast, or 8.4% slow with heavy static on the 48000 Hz family. macOS and Windows ignore bmControls, treat the clock as fixed and resample, so they are unaffected. Work around it by sending the partner rate in SET_CUR for this device, so the device runs at the requested rate. A new QUIRK_FLAG_SWAP_RATES flag controls this, applied through the quirk flags table. Only the 44.1/48 kHz pair has been verified on hardware; the 88.2/96 kHz and 176.4/192 kHz pairs are untested but follow the same pattern. Testing: the equivalent change ran on the device as a locally built module (44100 Hz PCM, device clock 44100 Hz, no xruns, correct tempo, no static). This upstream form compiles for sound/usb without warnings and passes checkpatch, but has not been built in a full kernel tree or load-tested. Assisted-by: LLM Signed-off-by: Darren Chang Link: https://patch.msgid.link/20260920122653.41993-1-darrenchangjr01@gmail.com Signed-off-by: Takashi Iwai --- Documentation/sound/alsa-configuration.rst | 6 +++++ sound/usb/clock.c | 27 ++++++++++++++++++++++ sound/usb/quirks.c | 3 +++ sound/usb/usbaudio.h | 6 +++++ 4 files changed, 42 insertions(+) diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index e500ec84e3ef..689eccf6a592 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2407,6 +2407,12 @@ quirk_flags the requested rate. A device advertising a single rate is otherwise never sent the request at all, and some require it before streaming will start. + * bit 33: ``swap_rates`` + The clock source applies a requested rate to the other base-rate + family, i.e. asking for 44100 makes the device run at 48000 and vice + versa (same for 88200 <-> 96000 and 176400 <-> 192000). The driver + sends the partner rate in SET_CUR so the device ends up running at the + requested one. This module supports multiple devices, autoprobe and hotplugging. diff --git a/sound/usb/clock.c b/sound/usb/clock.c index 34832183a1f0..cc77eace7b59 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -549,6 +549,30 @@ static int get_sample_rate_v2v3(struct snd_usb_audio *chip, int iface, return le32_to_cpu(data); } +/* + * Return the partner rate in the other base-rate family. + * See QUIRK_FLAG_SWAP_RATES. + */ +static unsigned int swap_base_rate(unsigned int rate) +{ + switch (rate) { + case 44100: + return 48000; + case 48000: + return 44100; + case 88200: + return 96000; + case 96000: + return 88200; + case 176400: + return 192000; + case 192000: + return 176400; + default: + return rate; + } +} + /* * Try to set the given sample rate: * @@ -585,6 +609,9 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, if (!writeable) return 0; + if (chip->quirk_flags & QUIRK_FLAG_SWAP_RATES) + rate = swap_base_rate(rate); + data = cpu_to_le32(rate); err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 20140cf7f4aa..1c248075c48e 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2776,6 +2776,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), DEVICE_FLG(0x36f9, 0xc009, /* XIBERIA K03S */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x3703, 0x2000, /* NUX NAI-24 */ + QUIRK_FLAG_SWAP_RATES), DEVICE_FLG(0x3c20, 0x3d21, /* AB13X USB Audio */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x413c, 0xa506, /* Dell AE515 sound bar */ @@ -2901,6 +2903,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + QUIRK_STRING_ENTRY(SWAP_RATES), NULL }; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 24436473931d..c6d59218d35f 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -258,6 +258,10 @@ extern bool snd_usb_skip_validation; * Issue SET_CUR for the sample rate even when the clock already reports the * requested rate. A device advertising a single rate is otherwise never sent * the request at all, and some require it before streaming will start. + * QUIRK_FLAG_SWAP_RATES + * The device applies a requested sample rate to the other base-rate family + * (44100 <-> 48000, 88200 <-> 96000, 176400 <-> 192000). Send the partner + * rate in SET_CUR so that the device runs at the requested rate. */ enum { @@ -294,6 +298,7 @@ enum { QUIRK_TYPE_MIXER_GET_CUR_OK = 30, QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, QUIRK_TYPE_ALWAYS_SET_RATE = 32, + QUIRK_TYPE_SWAP_RATES = 33, /* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ }; @@ -332,5 +337,6 @@ enum { #define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK) #define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) #define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) +#define QUIRK_FLAG_SWAP_RATES QUIRK_FLAG(SWAP_RATES) #endif /* __USBAUDIO_H */ From f38638f5181ab0545ca36f2ac3b0d2cf3404b1b7 Mon Sep 17 00:00:00 2001 From: Alexander Koch Date: Tue, 29 Sep 2026 11:08:20 +0200 Subject: [PATCH 432/924] ASoC: codecs: ak4619: Add optional powerdown GPIO Add optional device tree binding that enables operation of the 'PDN' pin during initialization: codec@10 { compatible = "asahi-kasei,ak4619"; reg = <0x10>; powerdown-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>; // <-- #sound-dai-cells = <0>; }; If defined, PDN is set high during driver probe, followed by a 10 ms delay as required before first register access [1]. If no 'powerdown-gpios' is defined, the behaviour is unchanged. [1] AK4619 datasheet, sec. 2 'Power-up/down Sequence', page 39 Signed-off-by: Alexander Koch Link: https://patch.msgid.link/20260929090821.1476257-2-mail@alexanderkoch.net Signed-off-by: Mark Brown --- sound/soc/codecs/ak4619.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/soc/codecs/ak4619.c b/sound/soc/codecs/ak4619.c index d9c9f6b20028..c4f0db1e51c8 100644 --- a/sound/soc/codecs/ak4619.c +++ b/sound/soc/codecs/ak4619.c @@ -152,6 +152,7 @@ struct ak4619_priv { struct regmap *regmap; struct snd_pcm_hw_constraint_list constraint; + struct gpio_desc *pdn_gpio; int deemph_en; unsigned int playback_rate; unsigned int sysclk; @@ -875,10 +876,20 @@ static int ak4619_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, ak4619); + ak4619->pdn_gpio = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_LOW); + if (IS_ERR(ak4619->pdn_gpio)) + return dev_err_probe(dev, PTR_ERR(ak4619->pdn_gpio), + "powerdown GPIO request failed\n"); + if (ak4619->pdn_gpio) + msleep(10); + ak4619->regmap = devm_regmap_init_i2c(i2c, &ak4619_regmap_cfg); if (IS_ERR(ak4619->regmap)) { ret = PTR_ERR(ak4619->regmap); dev_err(dev, "regmap_init() failed: %d\n", ret); + if (ak4619->pdn_gpio) + gpiod_set_value_cansleep(ak4619->pdn_gpio, 1); return ret; } @@ -887,6 +898,8 @@ static int ak4619_i2c_probe(struct i2c_client *i2c) if (ret < 0) { dev_err(dev, "Failed to register ak4619 component: %d\n", ret); + if (ak4619->pdn_gpio) + gpiod_set_value_cansleep(ak4619->pdn_gpio, 1); return ret; } From b224c8932773c63f4905d217544ab67bfef1c64e Mon Sep 17 00:00:00 2001 From: Alexander Koch Date: Tue, 29 Sep 2026 11:08:21 +0200 Subject: [PATCH 433/924] ASoC: dt-bindings: asahi-kasei,ak4619: Add powerdown GPIO Add documentation for an optional property that enables operation of the 'PDN' (powerdown) pin during initialization: codec@10 { compatible = "asahi-kasei,ak4619"; reg = <0x10>; powerdown-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>; // <-- #sound-dai-cells = <0>; }; Signed-off-by: Alexander Koch Link: https://patch.msgid.link/20260929090821.1476257-3-mail@alexanderkoch.net Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/asahi-kasei,ak4619.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml index d412531ef9a2..86e0b8cbfc29 100644 --- a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml +++ b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml @@ -34,6 +34,12 @@ properties: $ref: audio-graph-port.yaml# unevaluatedProperties: false + powerdown-gpios: + maxItems: 1 + description: + GPIO connected to the 'PDN' pin, if any. Needs to be set low to + power down the codec, hence the GPIO must be GPIO_ACTIVE_LOW. + required: - compatible - reg @@ -42,6 +48,8 @@ unevaluatedProperties: false examples: - | + #include + i2c { #address-cells = <1>; #size-cells = <0>; @@ -49,6 +57,8 @@ examples: compatible = "asahi-kasei,ak4619"; reg = <0x10>; + powerdown-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>; + clocks = <&rcar_sound>; clock-names = "mclk"; From 677507a5303a6716d9e0bbf3a8ba8c0f27391ab2 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:42 +0100 Subject: [PATCH 434/924] ASoC: SDCA: allow building without ACPI The SDCA class driver is useful on non-ACPI platforms where the topology is supplied statically via sdca_class_ops.populate_function. Drop 'depends on ACPI' from SND_SOC_SDCA and guard the ACPI-specific enumeration helpers (sdca_lookup_functions, sdca_lookup_swft, sdca_lookup_interface_revision) in sdca_device.c and sdca_functions.c with IS_ENABLED(CONFIG_ACPI), providing empty stubs when ACPI is off. The fwnode-based DisCo parser (sdca_parse_function() and its helpers in sdca_functions.c) stays compiled unconditionally: it uses only fwnode_* APIs and can in principle be reached from any firmware backend that supplies a matching device tree. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260925161049.758913-2-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- include/sound/sdca.h | 23 ++++++++++++++++++----- sound/soc/sdca/Kconfig | 6 ++++-- sound/soc/sdca/sdca_device.c | 4 ++++ sound/soc/sdca/sdca_functions.c | 2 ++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/include/sound/sdca.h b/include/sound/sdca.h index 2bdf4e333e04..db2672b3aae2 100644 --- a/include/sound/sdca.h +++ b/include/sound/sdca.h @@ -60,20 +60,16 @@ enum sdca_quirk { SDCA_QUIRKS_SKIP_FUNC_TYPE_PATCHING, }; -#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SDCA) +#if IS_ENABLED(CONFIG_SND_SOC_SDCA) -void sdca_lookup_functions(struct sdw_slave *slave); void sdca_lookup_swft(struct sdw_slave *slave); -void sdca_lookup_interface_revision(struct sdw_slave *slave); bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk); int sdca_dev_register_functions(struct sdw_slave *slave); void sdca_dev_unregister_functions(struct sdw_slave *slave); #else -static inline void sdca_lookup_functions(struct sdw_slave *slave) {} static inline void sdca_lookup_swft(struct sdw_slave *slave) {} -static inline void sdca_lookup_interface_revision(struct sdw_slave *slave) {} static inline bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk) { return false; @@ -88,4 +84,21 @@ static inline void sdca_dev_unregister_functions(struct sdw_slave *slave) {} #endif +/* + * Called from the SoundWire bus during peripheral enumeration; gated on + * ACPI to avoid a soundwire_bus <-> snd_soc_sdca module cycle on DT builds + * (where the bodies are stubs anyway). + */ +#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SDCA) + +void sdca_lookup_functions(struct sdw_slave *slave); +void sdca_lookup_interface_revision(struct sdw_slave *slave); + +#else + +static inline void sdca_lookup_functions(struct sdw_slave *slave) {} +static inline void sdca_lookup_interface_revision(struct sdw_slave *slave) {} + +#endif + #endif diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig index 4c0dcb9ff3b9..c371323cc36e 100644 --- a/sound/soc/sdca/Kconfig +++ b/sound/soc/sdca/Kconfig @@ -3,11 +3,13 @@ menu "SoundWire (SDCA)" config SND_SOC_SDCA tristate "SDCA core support" - depends on ACPI select AUXILIARY_BUS help This option enables support for the MIPI SoundWire Device - Class for Audio (SDCA). + Class for Audio (SDCA). The ACPI-specific enumeration + helpers are only built when CONFIG_ACPI is enabled; on + non-ACPI platforms the SDCA function data is supplied + statically by the codec driver. config SND_SOC_SDCA_HID bool "SDCA HID support" diff --git a/sound/soc/sdca/sdca_device.c b/sound/soc/sdca/sdca_device.c index 4bcd8d1fdff8..3f302bfa5457 100644 --- a/sound/soc/sdca/sdca_device.c +++ b/sound/soc/sdca/sdca_device.c @@ -15,6 +15,7 @@ #include #include +#if IS_ENABLED(CONFIG_ACPI) void sdca_lookup_interface_revision(struct sdw_slave *slave) { struct fwnode_handle *fwnode = slave->dev.fwnode; @@ -45,6 +46,9 @@ void sdca_lookup_swft(struct sdw_slave *slave) devm_add_action_or_reset(&slave->dev, devm_acpi_table_put, slave->sdca_data.swft); } +#else +void sdca_lookup_swft(struct sdw_slave *slave) { } +#endif EXPORT_SYMBOL_NS(sdca_lookup_swft, "SND_SOC_SDCA"); static bool sdca_device_quirk_rt712_vb(struct sdw_slave *slave) diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index f39008ac0e7d..1eb6418e132d 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -25,6 +25,7 @@ */ #define SDCA_PROPERTY_LENGTH 64 +#if IS_ENABLED(CONFIG_ACPI) static int patch_sdca_function_type(u32 interface_revision, u32 *function_type) { /* @@ -201,6 +202,7 @@ void sdca_lookup_functions(struct sdw_slave *slave) acpi_dev_for_each_child(adev, find_sdca_function, &slave->sdca_data); } EXPORT_SYMBOL_NS(sdca_lookup_functions, "SND_SOC_SDCA"); +#endif struct raw_init_write { __le32 addr; From 0985313a2a9d2e84d6a6b1041c88b5c57ee46dfa Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:43 +0100 Subject: [PATCH 435/924] ASoC: SDCA: export PM helpers keyed on sdca_class_drv The class PM callbacks pull sdca_class_drv out of drvdata, so the built-in class_sdw_driver owns the drvdata slot. That works for the generic case but blocks codec drivers that want to embed sdca_class_drv in their own private struct -- they need drvdata for their codec priv. Split the four callbacks into exported helpers that take a struct sdca_class_drv * directly: sdca_class_system_suspend() sdca_class_system_resume() sdca_class_runtime_suspend() sdca_class_runtime_resume() Codec drivers can now compose these into their own dev_pm_ops without going through drvdata. For the built-in class_sdw_driver, add small dev_pm_ops wrappers that fetch drv from drvdata and wire them into sdca_class_pm_ops. The ops struct itself stays private to this file; codec drivers build their own dev_pm_ops from the four exported helpers above. No functional change: the built-in class_sdw_driver keeps the same PM semantics; only the internal plumbing shifts to operate on sdca_class_drv instead of struct device *dev. Signed-off-by: Srinivas Kandagatla Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260925161049.758913-3-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- .../soc/sdca => include/sound}/sdca_class.h | 14 ++++ sound/soc/sdca/sdca_class.c | 81 ++++++++++++++----- sound/soc/sdca/sdca_class_function.c | 2 +- 3 files changed, 78 insertions(+), 19 deletions(-) rename {sound/soc/sdca => include/sound}/sdca_class.h (58%) diff --git a/sound/soc/sdca/sdca_class.h b/include/sound/sdca_class.h similarity index 58% rename from sound/soc/sdca/sdca_class.h rename to include/sound/sdca_class.h index 57f7f8d08f49..8a6a34a8dd4c 100644 --- a/sound/soc/sdca/sdca_class.h +++ b/include/sound/sdca_class.h @@ -32,4 +32,18 @@ struct sdca_class_drv { struct work_struct boot_work; }; +/* + * PM helpers. Codec drivers embed sdca_class_drv in their own priv, + * own dev_set_drvdata(), and compose these into their own dev_pm_ops: + * + * static int wcd_runtime_suspend(struct device *dev) { + * struct wcd_priv *priv = dev_get_drvdata(dev); + * return sdca_class_runtime_suspend(&priv->class); + * } + */ +int sdca_class_runtime_suspend(struct sdca_class_drv *drv); +int sdca_class_runtime_resume(struct sdca_class_drv *drv); +int sdca_class_system_suspend(struct sdca_class_drv *drv); +int sdca_class_system_resume(struct sdca_class_drv *drv); + #endif /* __SDCA_CLASS_H__ */ diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index d7444f442c71..a092ebaa620a 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -20,7 +20,7 @@ #include #include #include -#include "sdca_class.h" +#include #define CLASS_SDW_ATTACH_TIMEOUT_MS 5000 @@ -194,30 +194,41 @@ static void class_sdw_remove(struct sdw_slave *sdw) cancel_work_sync(&drv->boot_work); } -static int class_suspend(struct device *dev) +/** + * sdca_class_system_suspend - SDCA class system suspend helper + * @drv: caller-owned sdca_class_drv. + * + * Codec drivers compose this into their own dev_pm_ops. Disables the + * SoundWire interrupt and forces runtime suspend of the underlying + * class regmap. + */ +int sdca_class_system_suspend(struct sdca_class_drv *drv) { - struct sdca_class_drv *drv = dev_get_drvdata(dev); int ret; disable_irq(drv->sdw->irq); - ret = pm_runtime_force_suspend(dev); + ret = pm_runtime_force_suspend(drv->dev); if (ret) { - dev_err(dev, "failed to force suspend: %d\n", ret); + dev_err(drv->dev, "failed to force suspend: %d\n", ret); return ret; } return 0; } +EXPORT_SYMBOL_NS_GPL(sdca_class_system_suspend, "SND_SOC_SDCA_CLASS"); -static int class_resume(struct device *dev) +/** + * sdca_class_system_resume - SDCA class system resume helper + * @drv: caller-owned sdca_class_drv. + */ +int sdca_class_system_resume(struct sdca_class_drv *drv) { - struct sdca_class_drv *drv = dev_get_drvdata(dev); int ret; - ret = pm_runtime_force_resume(dev); + ret = pm_runtime_force_resume(drv->dev); if (ret) { - dev_err(dev, "failed to force resume: %d\n", ret); + dev_err(drv->dev, "failed to force resume: %d\n", ret); return ret; } @@ -225,11 +236,14 @@ static int class_resume(struct device *dev) return 0; } +EXPORT_SYMBOL_NS_GPL(sdca_class_system_resume, "SND_SOC_SDCA_CLASS"); -static int class_runtime_suspend(struct device *dev) +/** + * sdca_class_runtime_suspend - SDCA class runtime suspend helper + * @drv: caller-owned sdca_class_drv. + */ +int sdca_class_runtime_suspend(struct sdca_class_drv *drv) { - struct sdca_class_drv *drv = dev_get_drvdata(dev); - /* * Whilst the driver doesn't power the chip down here, going into runtime * suspend lets the SoundWire bus power down, which means the driver @@ -239,10 +253,14 @@ static int class_runtime_suspend(struct device *dev) return 0; } +EXPORT_SYMBOL_NS_GPL(sdca_class_runtime_suspend, "SND_SOC_SDCA_CLASS"); -static int class_runtime_resume(struct device *dev) +/** + * sdca_class_runtime_resume - SDCA class runtime resume helper + * @drv: caller-owned sdca_class_drv. + */ +int sdca_class_runtime_resume(struct sdca_class_drv *drv) { - struct sdca_class_drv *drv = dev_get_drvdata(dev); int ret; ret = sdw_slave_wait_for_init(drv->sdw, CLASS_SDW_ATTACH_TIMEOUT_MS); @@ -265,10 +283,37 @@ static int class_runtime_resume(struct device *dev) return ret; } +EXPORT_SYMBOL_NS_GPL(sdca_class_runtime_resume, "SND_SOC_SDCA_CLASS"); -static const struct dev_pm_ops class_pm_ops = { - SYSTEM_SLEEP_PM_OPS(class_suspend, class_resume) - RUNTIME_PM_OPS(class_runtime_suspend, class_runtime_resume, NULL) +/* + * Convenience dev_pm_ops used by the built-in class_sdw_driver, which + * stashes its sdca_class_drv in drvdata directly. Codec drivers that + * embed sdca_class_drv in their own priv compose their own dev_pm_ops + * using the sdca_class_*_suspend/resume helpers above. + */ +static int class_pm_system_suspend(struct device *dev) +{ + return sdca_class_system_suspend(dev_get_drvdata(dev)); +} + +static int class_pm_system_resume(struct device *dev) +{ + return sdca_class_system_resume(dev_get_drvdata(dev)); +} + +static int class_pm_runtime_suspend(struct device *dev) +{ + return sdca_class_runtime_suspend(dev_get_drvdata(dev)); +} + +static int class_pm_runtime_resume(struct device *dev) +{ + return sdca_class_runtime_resume(dev_get_drvdata(dev)); +} + +static const struct dev_pm_ops sdca_class_pm_ops = { + SYSTEM_SLEEP_PM_OPS(class_pm_system_suspend, class_pm_system_resume) + RUNTIME_PM_OPS(class_pm_runtime_suspend, class_pm_runtime_resume, NULL) }; static const struct sdw_device_id class_sdw_id[] = { @@ -282,7 +327,7 @@ MODULE_DEVICE_TABLE(sdw, class_sdw_id); static struct sdw_driver class_sdw_driver = { .driver = { .name = "sdca_class", - .pm = pm_ptr(&class_pm_ops), + .pm = pm_ptr(&sdca_class_pm_ops), }, .probe = class_sdw_probe, diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index 1d7fd6603882..411e3d717bb6 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -26,7 +26,7 @@ #include #include #include -#include "sdca_class.h" +#include #include "sdca_function_device.h" struct class_function_drv { From a60f1ac41fe0a2ea83f57ce252caf6f08ae48d62 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:44 +0100 Subject: [PATCH 436/924] ASoC: SDCA: expose class SoundWire probe/remove as library Split the internal class_sdw_probe/class_sdw_remove functions into caller-friendly library helpers: sdca_class_probe(sdw, drv) sdca_class_remove(drv) The class_sdw_probe/class_sdw_remove callbacks of the built-in class_sdw_driver are now thin wrappers that allocate a bare sdca_class_drv, stash it in drvdata, and defer to the exported helpers. The exported sdca_class_probe() takes a caller-owned struct sdca_class_drv * so codec-specific SoundWire drivers can embed the class state in their own priv struct, own dev_set_drvdata() themselves, and avoid a second allocation. No functional change for the built-in driver. This lays the groundwork for codec-specific SDCA SoundWire drivers that want to compose the class-side probe with their own quirks; the next patches add the class_ops mechanism on top. Signed-off-by: Srinivas Kandagatla Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260925161049.758913-4-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- include/sound/sdca_class.h | 4 +++ sound/soc/sdca/sdca_class.c | 62 +++++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/include/sound/sdca_class.h b/include/sound/sdca_class.h index 8a6a34a8dd4c..890c2dbd88a2 100644 --- a/include/sound/sdca_class.h +++ b/include/sound/sdca_class.h @@ -32,6 +32,10 @@ struct sdca_class_drv { struct work_struct boot_work; }; +/* Library helpers used by codec-specific SDCA SoundWire drivers. */ +int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv); +void sdca_class_remove(struct sdca_class_drv *drv); + /* * PM helpers. Codec drivers embed sdca_class_drv in their own priv, * own dev_set_drvdata(), and compose these into their own dev_pm_ops: diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index a092ebaa620a..0e6dead41495 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -136,18 +136,31 @@ static void class_boot_work(struct work_struct *work) pm_runtime_put_sync(drv->dev); } -static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id) +/** + * sdca_class_probe - SDCA class SoundWire slave probe helper + * @sdw: SoundWire slave + * @drv: caller-allocated sdca_class_drv storage. The caller (a codec + * driver, or the built-in class_sdw_driver in this file) owns the + * allocation and sets its own dev_set_drvdata() -- the framework + * does not touch drvdata. Typically embedded in the codec's own + * priv struct so codec drivers can keep per-slave state. + * + * Codec-specific SoundWire drivers call this from their .probe after + * allocating a struct sdca_class_drv (usually embedded in their own + * priv) and setting drvdata to their priv. The framework fills in the + * sdca_class_drv fields, sets up the class regmap, and queues the + * deferred boot work. + */ +int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv) { struct device *dev = &sdw->dev; struct regmap_config *dev_config; - struct sdca_class_drv *drv; int ret; sdca_lookup_swft(sdw); - drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); if (!drv) - return -ENOMEM; + return -EINVAL; dev_config = devm_kmemdup(dev, &class_dev_regmap_config, sizeof(*dev_config), GFP_KERNEL); @@ -159,8 +172,6 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id mutex_init(&drv->regmap_lock); mutex_init(&drv->init_lock); - dev_set_drvdata(drv->dev, drv); - INIT_WORK(&drv->boot_work, class_boot_work); dev_config->lock_arg = &drv->regmap_lock; @@ -185,13 +196,46 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id return 0; } +EXPORT_SYMBOL_NS_GPL(sdca_class_probe, "SND_SOC_SDCA_CLASS"); + +static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id) +{ + struct sdca_class_drv *drv; + + /* + * Pure-generic SDCA parts: no codec priv to embed, so allocate a + * bare sdca_class_drv here and stash it in drvdata for the + * built-in PM ops to fetch. + */ + drv = devm_kzalloc(&sdw->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + dev_set_drvdata(&sdw->dev, drv); + + return sdca_class_probe(sdw, drv); +} + +/** + * sdca_class_remove - SDCA class SoundWire slave remove helper + * @drv: caller-owned sdca_class_drv (the one handed to sdca_class_probe()). + * + * Cancels the deferred boot work so devres can safely free @drv and the + * embedding codec priv without racing class_boot_work. Codec-specific + * SoundWire drivers that call sdca_class_probe() must call this from + * their .remove with the same drv pointer they passed to probe. + */ +void sdca_class_remove(struct sdca_class_drv *drv) +{ + cancel_work_sync(&drv->boot_work); +} +EXPORT_SYMBOL_NS_GPL(sdca_class_remove, "SND_SOC_SDCA_CLASS"); static void class_sdw_remove(struct sdw_slave *sdw) { - struct device *dev = &sdw->dev; - struct sdca_class_drv *drv = dev_get_drvdata(dev); + struct sdca_class_drv *drv = dev_get_drvdata(&sdw->dev); - cancel_work_sync(&drv->boot_work); + sdca_class_remove(drv); } /** From 3273ec3e149ade420500014d442e4159bcc0d264 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:45 +0100 Subject: [PATCH 437/924] ASoC: SDCA: add class_ops with populate_function Add struct sdca_class_ops with a populate_function callback that lets codec drivers supply the SDCA topology (entities, clusters, init_table, ...) from static tables in place of sdca_parse_function() on DT/non-DisCo platforms. The callback is a pure data source and performs no bus I/O. Codec drivers embed sdca_class_drv in their own priv and register their populate_function through class_ops passed to sdca_class_probe(). Signed-off-by: Srinivas Kandagatla Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260925161049.758913-5-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- include/sound/sdca_class.h | 19 ++++++++++++++++++- sound/soc/sdca/sdca_class.c | 9 +++++++-- sound/soc/sdca/sdca_class_function.c | 9 ++++++++- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/include/sound/sdca_class.h b/include/sound/sdca_class.h index 890c2dbd88a2..794a756472cd 100644 --- a/include/sound/sdca_class.h +++ b/include/sound/sdca_class.h @@ -19,6 +19,19 @@ struct regmap; struct sdw_slave; struct sdca_function_data; +/** + * struct sdca_class_ops - optional codec-provided class callbacks + * @populate_function: fill @function (entities, clusters, init_table, ...) + * from static tables in place of sdca_parse_function() on + * DT/non-DisCo platforms. Called once per SDCA function with + * @dev pointing at that function's device. Pure data source; + * performs no bus I/O. Return 0 on success or a negative errno. + */ +struct sdca_class_ops { + int (*populate_function)(struct device *dev, + struct sdca_function_data *function); +}; + struct sdca_class_drv { struct device *dev; struct regmap *dev_regmap; @@ -26,6 +39,8 @@ struct sdca_class_drv { struct sdca_interrupt_info *irq_info; + const struct sdca_class_ops *ops; + struct mutex regmap_lock; /* Serialise function initialisations */ struct mutex init_lock; @@ -33,7 +48,9 @@ struct sdca_class_drv { }; /* Library helpers used by codec-specific SDCA SoundWire drivers. */ -int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv); +int sdca_class_probe(struct sdw_slave *sdw, + struct sdca_class_drv *drv, + const struct sdca_class_ops *ops); void sdca_class_remove(struct sdca_class_drv *drv); /* diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index 0e6dead41495..79fdb672be1d 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -144,6 +144,8 @@ static void class_boot_work(struct work_struct *work) * allocation and sets its own dev_set_drvdata() -- the framework * does not touch drvdata. Typically embedded in the codec's own * priv struct so codec drivers can keep per-slave state. + * @ops: optional codec-provided class callbacks (may be NULL for + * pure-generic SDCA parts that need no quirks) * * Codec-specific SoundWire drivers call this from their .probe after * allocating a struct sdca_class_drv (usually embedded in their own @@ -151,7 +153,9 @@ static void class_boot_work(struct work_struct *work) * sdca_class_drv fields, sets up the class regmap, and queues the * deferred boot work. */ -int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv) +int sdca_class_probe(struct sdw_slave *sdw, + struct sdca_class_drv *drv, + const struct sdca_class_ops *ops) { struct device *dev = &sdw->dev; struct regmap_config *dev_config; @@ -169,6 +173,7 @@ int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv) drv->dev = dev; drv->sdw = sdw; + drv->ops = ops; mutex_init(&drv->regmap_lock); mutex_init(&drv->init_lock); @@ -213,7 +218,7 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id dev_set_drvdata(&sdw->dev, drv); - return sdca_class_probe(sdw, drv); + return sdca_class_probe(sdw, drv, NULL); } /** diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index 411e3d717bb6..c49afa2e626d 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -329,7 +329,14 @@ static int class_function_probe(struct auxiliary_device *auxdev, drv->core = core; drv->function = &sdev->function; - ret = sdca_parse_function(dev, drv->function); + if (core->ops && core->ops->populate_function) { + ret = core->ops->populate_function(dev, drv->function); + } else if (drv->function->desc->node) { + ret = sdca_parse_function(dev, drv->function); + } else { + dev_err(dev, "no firmware node and no populate_function hook\n"); + return -ENOENT; + } if (ret) return ret; From e0e876c99f280fd00156a6a11eace1f91ea883f5 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:46 +0100 Subject: [PATCH 438/924] ASoC: SDCA: class_function: xlate sound-dai cell by entity index sdca_asoc_populate_dais() sets each DAI id from the SDCA Function's entity array index. ASoC's default of_xlate_dai_name treats the phandle cell as a positional index into the DAI list, so on a Function with non-DAI entries between dataport entities a sound-dai phandle resolves to the wrong DAI. Register a custom of_xlate_dai_name that walks the entity array and returns the dataport entity whose array index matches the cell value. Signed-off-by: Srinivas Kandagatla Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260925161049.758913-6-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class_function.c | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index c49afa2e626d..c716e28e5ddc 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -216,9 +216,43 @@ static int class_function_set_jack(struct snd_soc_component *component, return sdca_jack_set_jack(core->irq_info, jack); } +/* + * DT phandle cell is the SDCA entity index (matches dais[].id), not the + * positional DAI index the default xlate assumes. + */ +static int class_function_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name) +{ + struct class_function_drv *drv = snd_soc_component_get_drvdata(component); + struct sdca_function_data *function = drv->function; + struct sdca_entity *entity; + u32 target; + + if (args->args_count != 1) + return -EINVAL; + + target = args->args[0]; + if (target >= function->num_entities) + goto err; + + entity = &function->entities[target]; + if ((entity->type != SDCA_ENTITY_TYPE_IT && + entity->type != SDCA_ENTITY_TYPE_OT) || !entity->iot.is_dataport) + goto err; + + *dai_name = entity->label; + return 0; +err: + dev_err(component->dev, "xlate: no dataport entity at index %u (num_entities=%d)\n", + target, function->num_entities); + return -EINVAL; +} + static const struct snd_soc_component_driver class_function_component_drv = { .fixup_controls = class_function_component_fixup_controls, .remove = class_function_component_remove, + .of_xlate_dai_name = class_function_of_xlate_dai_name, .endianness = 1, }; From a36d24da5cf96d8129948fece042c331ab769d3e Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:47 +0100 Subject: [PATCH 439/924] ASoC: dt-bindings: qcom: add Tambora WCD9378 SDCA codec Describe the WCD9378 SoundWire peripheral which is SDCA compatible soundwire device. It provides SDCA SimpleJack for headphone playback headset mic capture and MBHC jack detection. The same silicon is integrated in two ways and enumerates with the same SoundWire class ID: mobile mode: two slaves (tx, rx) on separate SoundWire masters. SDCA / compute mode: one aggregated slave on a multi-lane master. Note: both of them have different bus toplogies 1 device vs 2 devices. Currently only compute mode is supported by this bindings, mobile mode will extend this binding as required. Signed-off-by: Srinivas Kandagatla Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260925161049.758913-7-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- .../bindings/sound/qcom,wcd9378-sdw.yaml | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml diff --git a/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml b/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml new file mode 100644 index 000000000000..62d85b1dca7f --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/qcom,wcd9378-sdw.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm WCD9378 (Tambora) SoundWire codec + +maintainers: + - Srinivas Kandagatla + +description: + Qualcomm WCD9378 (Tambora) SoundWire codec. The same silicon can be + fused for a mobile (non-SDCA) mode or an SDCA-compliant mode; the + qcom,sdca-compliant property selects the latter and is required by + this driver. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: sdw20217011000 + + reg: + maxItems: 1 + + qcom,sdca-compliant: + description: + Present when the codec is fused as SDCA-compliant. The mobile + (non-SDCA) variant carries the same class-ID compatible and is + handled by a different driver. + type: boolean + + qcom,port-mapping: + description: + Each entry maps a slave data port to a master data port. + Entries are in order starting from slave port 1; slave port 0 + is reserved and not represented. SDCA-compliant mode uses 8 + entries covering DP1..DP8. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 8 + maxItems: 8 + + reset-gpios: + description: GPIO used to release the codec from reset. + maxItems: 1 + + vdd-buck-supply: + description: 1.8 V analog buck supply. + + vdd-rxtx-supply: + description: 1.8 V RX/TX supply. + + vdd-io-supply: + description: Digital I/O supply. + + vdd-mic-bias-supply: + description: Mic bias supply. + + '#sound-dai-cells': + const: 1 + +required: + - compatible + - reg + - qcom,sdca-compliant + - qcom,port-mapping + - reset-gpios + - vdd-buck-supply + - vdd-rxtx-supply + - vdd-io-supply + - vdd-mic-bias-supply + - '#sound-dai-cells' + +unevaluatedProperties: false + +examples: + - | + #include + + soundwire@7630000 { + reg = <0x07630000 0x10000>; + #address-cells = <2>; + #size-cells = <0>; + + wcd9378c_sdw: audio-codec@0,3 { + compatible = "sdw20217011000"; + reg = <0 3>; + + qcom,sdca-compliant; + + qcom,port-mapping = <2 2 3 4 5 6 7 8>; + + reset-gpios = <&tlmm 191 GPIO_ACTIVE_LOW>; + + vdd-buck-supply = <&vreg_l15b_1p8>; + vdd-rxtx-supply = <&vreg_l15b_1p8>; + vdd-io-supply = <&vreg_l18b_1p2>; + vdd-mic-bias-supply = <&vreg_bob1>; + + #sound-dai-cells = <1>; + }; + }; +... From 0c30b8ca84aeea378d23874e7b14108021288b18 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:48 +0100 Subject: [PATCH 440/924] ASoC: codecs: add Qualcomm Tambora (WCD9378) SDCA codec Add support for the Qualcomm Tambora (WCD9378) headset codec in SDCA mode over SoundWire. On ARM/DT platforms without ACPI/DisCo firmware the SDCA topology and SoundWire port properties are supplied as static data through the codec driver. The codec exposes a single SimpleJack SDCA Function providing: - Headphone playback via FU 6 (mute + Q7.8 volume) and OT 43/45. - Headset mic capture via IT 33 with MICB2 bias at 2.75V. - MBHC-based headset jack detection. The driver enables the codec supplies, releases the reset GPIO, and uses: - sdw_slave_ops.read_prop to publish SoundWire slave properties and dpn caps for the compute-mode dataports. - sdca_class_ops.populate_function to hand the SDCA Function data (entities, clusters, init_table) to the class driver from static tables. Binds SoundWire slave id 0x0217:0x0110 when qcom,sdca-compliant is set on the DT node. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260925161049.758913-8-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 11 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wcd9378-sdca.c | 1080 +++++++++++++++++++++++++++++++ sound/soc/codecs/wcd9378-sdca.h | 20 + sound/soc/codecs/wcd9378-sdw.c | 51 ++ 5 files changed, 1164 insertions(+) create mode 100644 sound/soc/codecs/wcd9378-sdca.c create mode 100644 sound/soc/codecs/wcd9378-sdca.h create mode 100644 sound/soc/codecs/wcd9378-sdw.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 3cce95528dbf..ba59dabeeccd 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -2552,6 +2552,17 @@ config SND_SOC_WCD939X_SDW The WCD9390/9395 is a audio codec IC Integrated in Qualcomm SoCs like SM8650. +config SND_SOC_WCD9378_SDCA + tristate "Qualcomm Tambora (WCD9378) SDCA codec" + depends on SOUNDWIRE + depends on SND_SOC_SDCA_CLASS + help + This enables support for the Qualcomm Tambora (WCD9378) headset + codec when driven via the SDCA class driver on ARM platforms + without ACPI/DisCo tables. It provides the static SDCA topology + and SoundWire data port properties transcribed from the factory + ACPI tables. + config SND_SOC_WM0010 tristate depends on SPI_MASTER diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 1679c3ffc634..3826f9e4fce8 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -369,6 +369,7 @@ snd-soc-wcd938x-y := wcd938x.o snd-soc-wcd938x-sdw-y := wcd938x-sdw.o snd-soc-wcd939x-y := wcd939x.o snd-soc-wcd939x-sdw-y := wcd939x-sdw.o +snd-soc-wcd9378-y := wcd9378-sdw.o wcd9378-sdca.o snd-soc-wm-adsp-y := wm_adsp.o snd-soc-wm-adsp-test-y := wm_adsp_fw_find_test.o snd-soc-wm0010-y := wm0010.o @@ -825,6 +826,7 @@ ifdef CONFIG_SND_SOC_WCD939X_SDW # avoid link failure by forcing sdw code built-in when needed obj-$(CONFIG_SND_SOC_WCD939X) += snd-soc-wcd939x-sdw.o endif +obj-$(CONFIG_SND_SOC_WCD9378_SDCA) += snd-soc-wcd9378.o obj-$(CONFIG_SND_SOC_WM0010) += snd-soc-wm0010.o obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o diff --git a/sound/soc/codecs/wcd9378-sdca.c b/sound/soc/codecs/wcd9378-sdca.c new file mode 100644 index 000000000000..7c15f885390c --- /dev/null +++ b/sound/soc/codecs/wcd9378-sdca.c @@ -0,0 +1,1080 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright (c) 2025 Qualcomm Technologies, Inc. All rights reserved. + +/* + * WCD9378 (Tambora) SDCA SimpleJack codec. Supplies the static SDCA + * topology on DT platforms where no ACPI/DisCo enumeration exists. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wcd9378-sdca.h" + +struct wcd9378_priv { + struct sdca_class_drv class; +}; + +static struct sdca_entity wcd9378_sdca_entities[]; +/* + * Entity array index map. Order matches the ASL entity-id-list; + * Function (Entity 0) is last. + * + * [0] E001 IT 41 (0x1) [12] E00F IT 33 (0xf) + * [1] E002 CS 41 (0x2) [13] E010 PDE 34 (0x10) + * [2] E003 MFPU 21 (0x3) [14] E011 FU 33 (0x11) + * [3] E004 XU 42 (0x4) [15] E012 SU 35 (0x12) + * [4] E007 SU 43 (0x7) [16] E013 XU 36 (0x13) + * [5] E008 SU 45 (0x8) [17] E015 CS 36 (0x15) + * [6] E009 PDE 47 (0x9) [18] E016 OT 36 (0x16) + * [7] E00A OT 43 (0xa) [19] E017 MFPU 236 (0x17) + * [8] E00B OT 45 (0xb) [20] E018 CS 236 (0x18) + * [9] E00C GE 35 (0xc) [21] E019 OT 236 (0x19) + * [10] E00D IT 131 (0xd) [22] E006 FU 6 (0x6) + * [11] E00E CS 131 (0xe) [23] E000 Function (0x0) + */ +#define QSJ_IT41 0 +#define QSJ_CS41 1 +#define QSJ_MFPU21 2 +#define QSJ_XU42 3 +#define QSJ_SU43 4 +#define QSJ_SU45 5 +#define QSJ_PDE47 6 +#define QSJ_OT43 7 +#define QSJ_OT45 8 +#define QSJ_GE35 9 +#define QSJ_IT131 10 +#define QSJ_CS131 11 +#define QSJ_IT33 12 +#define QSJ_PDE34 13 +#define QSJ_FU33 14 +#define QSJ_SU35 15 +#define QSJ_XU36 16 +#define QSJ_CS36 17 +#define QSJ_OT36 18 +#define QSJ_MFPU236 19 +#define QSJ_CS236 20 +#define QSJ_OT236 21 +#define QSJ_FU6 22 + +/* Range data: {cols, rows, data[cols*rows]}, transcribed from ASL. */ + +/* IT 41 Usage: HIFI (0x2). ULP (0x3) does not route to the HPH analog driver. */ +static u32 range_it41_usage_data[] = { + /* usage, CBN, sample_rate, sample_width, full_scale, noise_floor, tag */ + 0x2, 0x2d0, 0xbb80, 0x10, 0x0, 0x0, 0x0, +}; + +static u32 range_it41_cluster_data[] = { 0x1, 0x1 }; + +/* IT 41 DataPort selector: DP6 (HPH render). */ +static u32 range_it41_dp_data[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* CS 41 SampleRateIndex: 1 -> 48kHz PCM. */ +static u32 range_cs41_sr_data[] = { 0x1, 0xbb80 }; + +/* SU selector: disconnected / connected. */ +static u32 range_su_sel_data[] = { 0x0, 0x1 }; + +/* PDE Requested_PS: PS0 / PS3. */ +static u32 range_pde_req_ps_data[] = { 0x0, 0x3 }; + +/* + * GE 35 SelectedMode -> terminal type. + * + * Tambora MBHC only handles mechanical detection; ADC HP/HS + * discrimination is not implemented, so modes 0 (Unplugged) and 1 + * (Unknown) are aliased to Headphone (mode 4) to keep the DAPM path + * alive. Mode 2 (Line-out) is not fitted on this board. + */ +static u32 range_ge35_mode_data[] = { + 0x0, 0x6c0, /* Unplugged -> HPH (alias) */ + 0x1, 0x6c0, /* Unknown -> HPH (alias) */ + 0x3, 0x6d0, /* Headset */ + 0x4, 0x6c0, /* Headphone */ +}; + +/* IT 131 Usage: optimization render stream at 192kHz. */ +static u32 range_it131_usage_data[] = { + 0x3, 0x334, 0x2ee00, 0x8, 0x0, 0x0, 0x0, +}; + +static u32 range_it131_cluster_data[] = { 0x1, 0x3 }; + +/* IT 131 DataPort selector: DP7. */ +static u32 range_it131_dp_data[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* CS 131 SampleRateIndex: 1 -> 192kHz. */ +static u32 range_cs131_sr_data[] = { 0x1, 0x2ee00 }; + +/* IT 33 MIC_BIAS: SDCA MIC_BIAS index 0x5 (2.75V). */ +static u32 range_it33_micbias_data[] = { 0x5 }; + +static u32 range_it33_usage_data[] = { + 0x1, 0x2c6, 0x0, 0x0, 0x0, 0x0, 0x0, +}; + +static u32 range_it33_cluster_data[] = { 0x1, 0x2 }; + +static u32 range_cs36_sr_data[] = { 0x1, 0xbb80 }; + +/* OT 36 Usage: PDM capture, host-visible 48kHz/16-bit PCM. */ +static u32 range_ot36_usage_data[] = { + 0x1, 0x2c6, 0xbb80, 0x10, 0x0, 0x0, 0x0, +}; + +/* OT 36 DataPort selector: DP2. */ +static u32 range_ot36_dp_data[] = { + 0xff, 0xff, 0x2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +static u32 range_cs236_sr_data[] = { 0x1, 0xbb80 }; + +/* OT 236 Usage: optimization capture at 192kHz (clocked by CS 131). */ +static u32 range_ot236_usage_data[] = { + 0x1, 0x334, 0x2ee00, 0x8, 0x0, 0x0, 0x0, +}; + +/* OT 236 DataPort selector: DP5. */ +static u32 range_ot236_dp_data[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0x5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* Entity 0 (Function) Control Values */ +static int ctrl_fun_sdca_ver_vals[] = { 0x11 }; +static int ctrl_fun_type_vals[] = { 0x08 }; /* SimpleJack */ +static int ctrl_fun_man_id_vals[] = { 0x0217 }; +static int ctrl_fun_id_vals[] = { 0x3 }; +static int ctrl_fun_ver_vals[] = { 0x0 }; +static int ctrl_dev_sdca_ver_vals[] = { 0x11 }; + +/* Entity 1 (IT 41) Control Values */ +static int ctrl_it41_latency_vals[] = { 0x0 }; +static int ctrl_it41_cluster_vals[] = { 0x1 }; +static int ctrl_it41_dp_vals[] = { 0x6 }; + +/* Entity 2 (CS 41) Control Values */ +static int ctrl_cs41_sr_vals[] = { 0x1 }; + +/* Entity 3 (MFPU 21) Control Values */ +static int ctrl_mfpu21_bypass_vals[] = { 0x1 }; + +/* Entity 4 (XU 42) Control Values */ +static int ctrl_xu42_id_vals[] = { 0x2131 }; +static int ctrl_xu42_ver_vals[] = { 0x1 }; + +/* Entity 0xd (IT 131) Control Values */ +static int ctrl_it131_latency_vals[] = { 0x0 }; +static int ctrl_it131_cluster_vals[] = { 0x1 }; +static int ctrl_it131_dp_vals[] = { 0x7 }; + +/* Entity 0xe (CS 131) Control Values */ +static int ctrl_cs131_sr_vals[] = { 0x1 }; + +/* IT 33 MIC_BIAS: fixed 2.75V (SDCA MIC_BIAS index 0x5), reapplied by PDE34. */ +static int ctrl_it33_micbias_vals[] = { 0x5 }; +static int ctrl_it33_latency_vals[] = { 0x0 }; +static int ctrl_it33_cluster_vals[] = { 0x1 }; + +/* Entity 0x13 (XU 36) Control Values */ +static int ctrl_xu36_bypass_vals[] = { 0x1 }; +static int ctrl_xu36_id_vals[] = { 0x2131 }; +static int ctrl_xu36_ver_vals[] = { 0x1 }; + +/* Entity 0x15 (CS 36) Control Values */ +static int ctrl_cs36_sr_vals[] = { 0x1 }; + +/* Entity 0x16 (OT 36) Control Values */ +static int ctrl_ot36_latency_vals[] = { 0x0 }; +static int ctrl_ot36_dp_vals[] = { 0x2 }; + +/* Entity 0x17 (MFPU 236) Control Values */ +static int ctrl_mfpu236_bypass_vals[] = { 0x1 }; + +/* Entity 0x18 (CS 236) Control Values */ +static int ctrl_cs236_sr_vals[] = { 0x1 }; + +/* Entity 0x19 (OT 236) Control Values */ +static int ctrl_ot236_latency_vals[] = { 0x0 }; +static int ctrl_ot236_dp_vals[] = { 0x5 }; + +/* Entity 0 (Function) Controls */ +static struct sdca_control entity0_controls[] = { + { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_COMMIT_GROUP_MASK_NAME }, + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_sdca_ver_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_SDCA_VERSION_NAME }, + { .sel = 0x5, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_type_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_TYPE_NAME }, + { .sel = 0x6, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_man_id_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_MANUFACTURER_ID_NAME }, + { .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_id_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_ID_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_ver_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_VERSION_NAME }, + { .sel = 0x9, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_FUNCTION_EXTENSION_ID_NAME }, + { .sel = 0xa, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_FUNCTION_EXTENSION_VERSION_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_RW1C, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_FUNCTION_STATUS_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_RW1S, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_FUNCTION_ACTION_NAME }, + { .sel = 0x2c, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_DEVICE_MANUFACTURER_ID_NAME }, + { .sel = 0x2d, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_DEVICE_PART_ID_NAME }, + { .sel = 0x2e, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_DEVICE_VERSION_NAME }, + { .sel = 0x2f, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_dev_sdca_ver_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DEVICE_SDCA_VERSION_NAME }, +}; + +/* Entity 1 (IT 41) Controls */ +static struct sdca_control entity_it41_controls[] = { + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_it41_usage_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_it41_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it41_cluster_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_it41_cluster_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_CLUSTERINDEX_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it41_dp_vals, .has_fixed = true, + .range = { .cols = 0x10, .rows = 0x4, .data = range_it41_dp_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DATAPORT_SELECTOR_NAME }, +}; + +/* Entity 2 (CS 41) Controls */ +static struct sdca_control entity_cs41_controls[] = { + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_cs41_sr_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_cs41_sr_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SAMPLERATEINDEX_NAME }, +}; + +/* Entity 3 (MFPU 21) Controls */ +static struct sdca_entity *entity_mfpu21_sources[] = { + &wcd9378_sdca_entities[QSJ_IT41], + &wcd9378_sdca_entities[QSJ_IT131], +}; + +static struct sdca_control entity_mfpu21_controls[] = { + { .sel = SDCA_CTL_MFPU_BYPASS, .mode = SDCA_ACCESS_MODE_DC, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .values = ctrl_mfpu21_bypass_vals, .has_fixed = true, .label = SDCA_CTL_BYPASS_NAME }, +}; + +/* Entity 4 (XU 42) Controls */ +static struct sdca_entity *entity_xu42_sources[] = { &wcd9378_sdca_entities[QSJ_MFPU21] }; + +static struct sdca_control entity_xu42_controls[] = { + { .sel = SDCA_CTL_XU_BYPASS, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .label = SDCA_CTL_BYPASS_NAME }, + { .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_xu42_id_vals, .has_fixed = true, .label = SDCA_CTL_XU_ID_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_xu42_ver_vals, .has_fixed = true, .label = SDCA_CTL_XU_VERSION_NAME }, +}; + +/* + * SU Selector is RO but its value is deterministic from the current + * GE mode which the class framework drives; leave it non-volatile so + * regmap caches it and DAPM reads succeed when the codec is + * runtime-suspended. + */ + +/* Entity 7 (SU 43) Controls */ +static struct sdca_entity *entity_su43_sources[] = { &wcd9378_sdca_entities[QSJ_XU42] }; + +static struct sdca_control entity_su43_controls[] = { + { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_DEVICE, + .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SELECTOR_NAME }, +}; + +/* Entity 8 (SU 45) Controls */ +static struct sdca_entity *entity_su45_sources[] = { &wcd9378_sdca_entities[QSJ_XU42] }; + +static struct sdca_control entity_su45_controls[] = { + { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_DEVICE, + .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SELECTOR_NAME }, +}; + +/* + * PDE 47 manages the HPH render path (OT 43, OT 45). FU 6 is listed + * so its cached mute/volume are reasserted on PS0 entry. + */ +static struct sdca_entity *entity_pde47_managed[] = { + &wcd9378_sdca_entities[QSJ_FU6], + &wcd9378_sdca_entities[QSJ_OT43], + &wcd9378_sdca_entities[QSJ_OT45], +}; + +static struct sdca_pde_delay pde47_delays[] = { + { .from_ps = 3, .to_ps = 0, .us = 30000 }, + { .from_ps = 0, .to_ps = 3, .us = 30000 }, +}; + +static struct sdca_control entity_pde47_controls[] = { + { .sel = SDCA_CTL_PDE_REQUESTED_PS, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_pde_req_ps_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 3, + .label = SDCA_CTL_REQUESTED_PS_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_ACTUAL_PS_NAME }, + /* + * HPH protection IRQs (OCP/CNP/SURGE) fire in codec HW but are + * not exposed to Linux; needs an SDCA framework interface for + * standalone status-bit IRQs. Wire up in a follow-up. + */ +}; + +/* OT 43 (Headphone), OT 45 (Headset): no controls. */ +static struct sdca_entity *entity_ot43_sources[] = { &wcd9378_sdca_entities[QSJ_IT41] }; +static struct sdca_entity *entity_ot45_sources[] = { &wcd9378_sdca_entities[QSJ_IT41] }; + +/* + * GE 35 mode -> SU selector. Modes 0/1 alias to Headphone (see + * range_ge35_mode_data). SU 45 = source 1 (XU 42) for HPH paths, + * SU 43 = source 1 (XU 42) for Headset. + */ +static struct sdca_ge_control ge35_mode0_controls[] = { + { .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 }, +}; + +static struct sdca_ge_control ge35_mode1_controls[] = { + { .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 }, +}; + +static struct sdca_ge_control ge35_mode3_controls[] = { + { .id = 0x7, .sel = 0x1, .cn = 0x0, .val = 0x1 }, +}; + +static struct sdca_ge_control ge35_mode4_controls[] = { + { .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 }, +}; + +static struct sdca_ge_mode ge35_modes[] = { + { .val = 0x0, .num_controls = ARRAY_SIZE(ge35_mode0_controls), .controls = ge35_mode0_controls }, + { .val = 0x1, .num_controls = ARRAY_SIZE(ge35_mode1_controls), .controls = ge35_mode1_controls }, + { .val = 0x3, .num_controls = ARRAY_SIZE(ge35_mode3_controls), .controls = ge35_mode3_controls }, + { .val = 0x4, .num_controls = ARRAY_SIZE(ge35_mode4_controls), .controls = ge35_mode4_controls }, +}; + +static struct sdca_control entity_ge35_controls[] = { + { .sel = SDCA_CTL_GE_SELECTED_MODE, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .range = { .cols = 0x2, .rows = 0x4, .data = range_ge35_mode_data }, + .has_reset = true, .reset = 0, + .label = SDCA_CTL_SELECTED_MODE_NAME }, + { .sel = 0x2, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = 4, /* SDCA_4 = GE_DETECTED_MODE, see init_table INTMASK_1 */ + .is_volatile = true, .label = SDCA_CTL_DETECTED_MODE_NAME }, +}; + +static struct sdca_control entity_it131_controls[] = { + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_it131_usage_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_it131_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it131_cluster_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_it131_cluster_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_CLUSTERINDEX_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it131_dp_vals, .has_fixed = true, + .range = { .cols = 0x10, .rows = 0x4, .data = range_it131_dp_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DATAPORT_SELECTOR_NAME }, +}; + +/* Entity 0xe (CS 131) Controls */ +static struct sdca_control entity_cs131_controls[] = { + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_cs131_sr_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_cs131_sr_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SAMPLERATEINDEX_NAME }, +}; + +/* Entity 0xf (IT 33) - headset mic input */ +static struct sdca_control entity_it33_controls[] = { + { .sel = 0x3, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it33_micbias_vals, .has_default = true, + .range = { .cols = 0x1, .rows = 0x1, .data = range_it33_micbias_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_MIC_BIAS_NAME }, + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_it33_usage_data }, + .has_reset = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_it33_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it33_cluster_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_it33_cluster_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_CLUSTERINDEX_NAME }, +}; + +/* Entity 0x10 (PDE 34) - manages IT 33 */ +static struct sdca_entity *entity_pde34_managed[] = { &wcd9378_sdca_entities[QSJ_IT33] }; + +static struct sdca_pde_delay pde34_delays[] = { + { .from_ps = 3, .to_ps = 0, .us = 30000 }, + { .from_ps = 0, .to_ps = 3, .us = 30000 }, +}; + +static struct sdca_control entity_pde34_controls[] = { + { .sel = SDCA_CTL_PDE_REQUESTED_PS, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_pde_req_ps_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 3, + .label = SDCA_CTL_REQUESTED_PS_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_ACTUAL_PS_NAME }, +}; + +/* + * FU 6 (vendor FU42): HPH mute + Q7.8 volume. DUAL-mode CVR-alias + * writes take effect without an SCP_COMMIT. + */ +/* + * FU 6 volume range: MIN, MAX, STEP in Q7.8 (LSB = 1/256 dB). + * 0x8000 = -128 dB, 0x7fff = +127.996 dB, STEP = 1. Framework + * sign-extends and converts to 0.01 dB TLV via (val * 100) >> 8. + */ +static u32 range_fu6_vol_data[] = { + 0x00008000, 0x00007fff, 0x00000001, +}; + +static struct sdca_control entity_fu6_controls[] = { + { .sel = SDCA_CTL_FU_MUTE, .mode = SDCA_ACCESS_MODE_DUAL, + .layers = SDCA_ACCESS_LAYER_USER, .cn_list = 0x6, .nbits = 1, + .type = SDCA_CTL_DATATYPE_ONEBIT, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .label = SDCA_CTL_MUTE_NAME }, + { .sel = SDCA_CTL_FU_CHANNEL_VOLUME, .mode = SDCA_ACCESS_MODE_DUAL, + .layers = SDCA_ACCESS_LAYER_USER, .cn_list = 0x6, .nbits = 16, + .type = SDCA_CTL_DATATYPE_Q7P8DB, + .range = { .cols = SDCA_VOLUME_LINEAR_NCOLS, .rows = 1, + .data = range_fu6_vol_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_CHANNEL_VOLUME_NAME }, +}; + +/* Entity 0x11 (FU 33) - no controls */ +static struct sdca_entity *entity_fu33_sources[] = { &wcd9378_sdca_entities[QSJ_IT33] }; + +/* Entity 0x12 (SU 35) Controls */ +static struct sdca_entity *entity_su35_sources[] = { &wcd9378_sdca_entities[QSJ_FU33] }; + +/* + * SU 35 is class-layer: the SDCA class framework drives the Selector + * directly via a DAPM mux widget, so the Control must be writeable. + */ +static struct sdca_control entity_su35_controls[] = { + { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SELECTOR_NAME }, +}; + +/* Entity 0x13 (XU 36) Controls */ +static struct sdca_entity *entity_xu36_sources[] = { &wcd9378_sdca_entities[QSJ_SU35] }; + +static struct sdca_control entity_xu36_controls[] = { + /* bypass=1: pass mic signal through XU36 without proprietary processing */ + { .sel = SDCA_CTL_XU_BYPASS, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .values = ctrl_xu36_bypass_vals, .has_default = true, .label = SDCA_CTL_BYPASS_NAME }, + { .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_xu36_id_vals, .has_fixed = true, .label = SDCA_CTL_XU_ID_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_xu36_ver_vals, .has_fixed = true, .label = SDCA_CTL_XU_VERSION_NAME }, +}; + +/* Entity 0x15 (CS 36) Controls */ +static struct sdca_control entity_cs36_controls[] = { + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_cs36_sr_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_cs36_sr_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SAMPLERATEINDEX_NAME }, +}; + +/* OT 36 mic capture: IT33 -> FU33 -> SU35 -> XU36 -> OT36. */ +static struct sdca_entity *entity_ot36_sources[] = { &wcd9378_sdca_entities[QSJ_XU36] }; + +static struct sdca_control entity_ot36_controls[] = { + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_ot36_usage_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_ot36_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_ot36_dp_vals, .has_fixed = true, + .range = { .cols = 0x10, .rows = 0x4, .data = range_ot36_dp_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DATAPORT_SELECTOR_NAME }, +}; + +/* Entity 0x17 (MFPU 236) Controls */ +static struct sdca_control entity_mfpu236_controls[] = { + { .sel = SDCA_CTL_MFPU_BYPASS, .mode = SDCA_ACCESS_MODE_DC, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .values = ctrl_mfpu236_bypass_vals, .has_fixed = true, .label = SDCA_CTL_BYPASS_NAME }, +}; + +/* Entity 0x18 (CS 236) Controls */ +static struct sdca_control entity_cs236_controls[] = { + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_cs236_sr_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_cs236_sr_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SAMPLERATEINDEX_NAME }, +}; + +/* Entity 0x19 (OT 236) - optimization stream capture output */ +static struct sdca_entity *entity_ot236_sources[] = { &wcd9378_sdca_entities[QSJ_IT33] }; + +static struct sdca_control entity_ot236_controls[] = { + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_ot236_usage_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_ot236_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_ot236_dp_vals, .has_fixed = true, + .range = { .cols = 0x10, .rows = 0x4, .data = range_ot236_dp_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DATAPORT_SELECTOR_NAME }, +}; + +static struct sdca_entity wcd9378_sdca_entities[] = { + /* [0] E001: IT 41 - PDM render stream input */ + { .id = 0x1, .label = "IT 41", .type = SDCA_ENTITY_TYPE_IT, + .iot = { .type = 0x0191, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS41] }, + .num_controls = ARRAY_SIZE(entity_it41_controls), .controls = entity_it41_controls }, + /* [1] E002: CS 41 */ + { .id = 0x2, .label = "CS 41", .type = SDCA_ENTITY_TYPE_CS, + .cs = { .type = 0x0 }, + .num_controls = ARRAY_SIZE(entity_cs41_controls), .controls = entity_cs41_controls }, + /* [2] E003: MFPU 21 */ + { .id = 0x3, .label = "MFPU 21", .type = SDCA_ENTITY_TYPE_MFPU, + .num_controls = ARRAY_SIZE(entity_mfpu21_controls), .controls = entity_mfpu21_controls, + .num_sources = ARRAY_SIZE(entity_mfpu21_sources), .sources = entity_mfpu21_sources }, + /* [3] E004: XU 42 */ + { .id = 0x4, .label = "XU 42", .type = SDCA_ENTITY_TYPE_XU, + .num_controls = ARRAY_SIZE(entity_xu42_controls), .controls = entity_xu42_controls, + .num_sources = ARRAY_SIZE(entity_xu42_sources), .sources = entity_xu42_sources }, + /* [4] E007: SU 43 - render selector (headset path), driven by GE 35 jack detection */ + { .id = 0x7, .label = "SU 43", .type = SDCA_ENTITY_TYPE_SU, + .group = &wcd9378_sdca_entities[QSJ_GE35], + .num_controls = ARRAY_SIZE(entity_su43_controls), .controls = entity_su43_controls, + .num_sources = ARRAY_SIZE(entity_su43_sources), .sources = entity_su43_sources }, + /* [5] E008: SU 45 - render selector (headphone path), driven by GE 35 jack detection */ + { .id = 0x8, .label = "SU 45", .type = SDCA_ENTITY_TYPE_SU, + .group = &wcd9378_sdca_entities[QSJ_GE35], + .num_controls = ARRAY_SIZE(entity_su45_controls), .controls = entity_su45_controls, + .num_sources = ARRAY_SIZE(entity_su45_sources), .sources = entity_su45_sources }, + /* [6] E009: PDE 47 - render power domain */ + { .id = 0x9, .label = "PDE 47", .type = SDCA_ENTITY_TYPE_PDE, + .pde = { .num_managed = ARRAY_SIZE(entity_pde47_managed), .managed = entity_pde47_managed, + .num_max_delay = ARRAY_SIZE(pde47_delays), .max_delay = pde47_delays }, + .num_controls = ARRAY_SIZE(entity_pde47_controls), .controls = entity_pde47_controls }, + /* [7] E00A: OT 43 - Headphone on jack */ + { .id = 0xa, .label = "OT 43", .type = SDCA_ENTITY_TYPE_OT, + .iot = { .type = 0x06c0 }, + .num_sources = ARRAY_SIZE(entity_ot43_sources), .sources = entity_ot43_sources }, + /* [8] E00B: OT 45 - Headset output on jack */ + { .id = 0xb, .label = "OT 45", .type = SDCA_ENTITY_TYPE_OT, + .iot = { .type = 0x06d0 }, + .num_sources = ARRAY_SIZE(entity_ot45_sources), .sources = entity_ot45_sources }, + /* [9] E00C: GE 35 - jack detection group entity */ + { .id = 0xc, .label = "GE 35", .type = SDCA_ENTITY_TYPE_GE, + .ge = { .num_modes = ARRAY_SIZE(ge35_modes), .modes = ge35_modes }, + .num_controls = ARRAY_SIZE(entity_ge35_controls), .controls = entity_ge35_controls }, + /* [10] E00D: IT 131 - optimization stream input */ + { .id = 0xd, .label = "IT 131", .type = SDCA_ENTITY_TYPE_IT, + .iot = { .type = 0x0190, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS131] }, + .num_controls = ARRAY_SIZE(entity_it131_controls), .controls = entity_it131_controls }, + /* [11] E00E: CS 131 */ + { .id = 0xe, .label = "CS 131", .type = SDCA_ENTITY_TYPE_CS, + .cs = { .type = 0x0 }, + .num_controls = ARRAY_SIZE(entity_cs131_controls), .controls = entity_cs131_controls }, + /* [12] E00F: IT 33 - headset mic input */ + { .id = 0xf, .label = "IT 33", .type = SDCA_ENTITY_TYPE_IT, + .iot = { .type = 0x06d0 }, + .num_controls = ARRAY_SIZE(entity_it33_controls), .controls = entity_it33_controls }, + /* [13] E010: PDE 34 - mic power domain */ + { .id = 0x10, .label = "PDE 34", .type = SDCA_ENTITY_TYPE_PDE, + .pde = { .num_managed = ARRAY_SIZE(entity_pde34_managed), .managed = entity_pde34_managed, + .num_max_delay = ARRAY_SIZE(pde34_delays), .max_delay = pde34_delays }, + .num_controls = ARRAY_SIZE(entity_pde34_controls), .controls = entity_pde34_controls }, + /* [14] E011: FU 33 - mic feature unit */ + { .id = 0x11, .label = "FU 33", .type = SDCA_ENTITY_TYPE_FU, + .num_sources = ARRAY_SIZE(entity_fu33_sources), .sources = entity_fu33_sources }, + /* [15] E012: SU 35 - mic selector */ + { .id = 0x12, .label = "SU 35", .type = SDCA_ENTITY_TYPE_SU, + .num_controls = ARRAY_SIZE(entity_su35_controls), .controls = entity_su35_controls, + .num_sources = ARRAY_SIZE(entity_su35_sources), .sources = entity_su35_sources }, + /* [16] E013: XU 36 - mic extension unit */ + { .id = 0x13, .label = "XU 36", .type = SDCA_ENTITY_TYPE_XU, + .num_controls = ARRAY_SIZE(entity_xu36_controls), .controls = entity_xu36_controls, + .num_sources = ARRAY_SIZE(entity_xu36_sources), .sources = entity_xu36_sources }, + /* [17] E015: CS 36 */ + { .id = 0x15, .label = "CS 36", .type = SDCA_ENTITY_TYPE_CS, + .cs = { .type = 0x0 }, + .num_controls = ARRAY_SIZE(entity_cs36_controls), .controls = entity_cs36_controls }, + /* [18] E016: OT 36 - PDM mic capture output */ + { .id = 0x16, .label = "OT 36", .type = SDCA_ENTITY_TYPE_OT, + .iot = { .type = 0x0191, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS36] }, + .num_controls = ARRAY_SIZE(entity_ot36_controls), .controls = entity_ot36_controls, + .num_sources = ARRAY_SIZE(entity_ot36_sources), .sources = entity_ot36_sources }, + /* [19] E017: MFPU 236 - optimization TX processing */ + { .id = 0x17, .label = "MFPU 236", .type = SDCA_ENTITY_TYPE_MFPU, + .num_controls = ARRAY_SIZE(entity_mfpu236_controls), .controls = entity_mfpu236_controls }, + /* [20] E018: CS 236 */ + { .id = 0x18, .label = "CS 236", .type = SDCA_ENTITY_TYPE_CS, + .cs = { .type = 0x0 }, + .num_controls = ARRAY_SIZE(entity_cs236_controls), .controls = entity_cs236_controls }, + /* [21] E019: OT 236 - optimization stream capture output */ + { .id = 0x19, .label = "OT 236", .type = SDCA_ENTITY_TYPE_OT, + .iot = { .type = 0x0190, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS131] }, + .num_controls = ARRAY_SIZE(entity_ot236_controls), .controls = entity_ot236_controls, + .num_sources = ARRAY_SIZE(entity_ot236_sources), .sources = entity_ot236_sources }, + /* E006: FU 6 (FU42) - vendor Feature Unit; HPH mute + Q7.8 volume. */ + { .id = 0x6, .label = "FU 6", .type = SDCA_ENTITY_TYPE_FU, + .num_controls = ARRAY_SIZE(entity_fu6_controls), .controls = entity_fu6_controls }, + /* Entity 0 (Function) */ + { .id = 0x0, .label = "entity0", + .num_controls = ARRAY_SIZE(entity0_controls), .controls = entity0_controls }, +}; + +/* Clusters */ +static struct sdca_channel cl1_channels[] = { /* CL01 - render (HPH) stereo */ + { .id = 0x1, .purpose = 0x1, .relationship = 0x2 }, /* Left */ + { .id = 0x2, .purpose = 0x1, .relationship = 0x3 }, /* Right */ +}; + +static struct sdca_channel cl2_channels[] = { /* CL02 - mic capture mono */ + { .id = 0xff, .purpose = 0x1, .relationship = 0x1 }, +}; + +static struct sdca_channel cl3_channels[] = { /* CL03 - optimization RX */ + { .id = 0xff, .purpose = 0x1, .relationship = 0x1 }, /* Mono */ + { .id = 0x1, .purpose = 0x1, .relationship = 0x2 }, /* Left */ + { .id = 0x2, .purpose = 0x1, .relationship = 0x3 }, /* Right */ +}; + +static struct sdca_channel cl5_channels[] = { /* CL05 - optimization TX mono */ + { .id = 0xff, .purpose = 0x1, .relationship = 0x1 }, +}; + +static struct sdca_cluster wcd9378_sdca_clusters[] = { + { .id = 0x1, .num_channels = ARRAY_SIZE(cl1_channels), .channels = cl1_channels }, + { .id = 0x2, .num_channels = ARRAY_SIZE(cl2_channels), .channels = cl2_channels }, + { .id = 0x3, .num_channels = ARRAY_SIZE(cl3_channels), .channels = cl3_channels }, + { .id = 0x5, .num_channels = ARRAY_SIZE(cl5_channels), .channels = cl5_channels }, +}; + +/* Init table transcribed from ASL. */ +static struct sdca_init_write wcd9378_sdca_init_table[] = { + { .addr = 0x401804f0, .val = 0x00 }, /* DIGITAL_PLATFORM_CTL */ + { .addr = 0x4018046e, .val = 0x10 }, /* DIGITAL_INTR_MODE */ + { .addr = 0x0000004d, .val = 0x01 }, /* SWRS_SCP_BUSCLOCK_BASE */ + { .addr = 0x00000062, .val = 0x02 }, /* SWRS_SCP_BUSCLOCK_SCALE_BANK */ + { .addr = 0x4018016a, .val = 0x80 }, /* CP_DTOP_CTRL_14 */ + { .addr = 0x40180165, .val = 0x6b }, /* CP_DTOP_CTRL_9 */ + { .addr = 0x40180103, .val = 0x1e }, /* SLEEP_CTL BG_CTL (0.9V) */ + { .addr = 0x40180103, .val = 0x9e }, /* SLEEP_CTL BG_EN */ + { .addr = 0x40180103, .val = 0xde }, /* SLEEP_CTL LDOL_BG_SEL */ + { .addr = 0x40180029, .val = 0xb5 }, /* BIAS_VBG_FINE_ADJ */ + { .addr = 0x40180001, .val = 0x80 }, /* ANA_BIAS ANALOG_BIAS_EN */ + { .addr = 0x40180001, .val = 0xc0 }, /* ANA_BIAS PRECHRG_EN(1) */ + { .addr = 0x40180001, .val = 0x80 }, /* ANA_BIAS PRECHRG_EN(0) */ + { .addr = 0x4018007b, .val = 0xa2 }, /* TX_COM_TXFE_DIV_CTL SEQ_BYPASS */ + { .addr = 0x40180465, .val = 0x17 }, /* PDM_WD_CTL0 TIME_OUT_SEL_PCM */ + { .addr = 0x40180466, .val = 0x17 }, /* PDM_WD_CTL1 TIME_OUT_SEL_PCM */ + { .addr = 0x4018006c, .val = 0x01 }, /* MICB1_TEST_CTL_2 IBIAS_LDO_DRIVER */ + { .addr = 0x40180072, .val = 0x81 }, /* MICB3_TEST_CTL_2 IBIAS_LDO_DRIVER */ + { .addr = 0x401800ce, .val = 0x38 }, /* HPH_OCP_CTL OCP_FSM_EN */ + { .addr = 0x401800ce, .val = 0x3a }, /* HPH_OCP_CTL SCD_OP_EN */ + { .addr = 0x401800d4, .val = 0xe1 }, /* HPH_L_TEST OCP_DET_EN */ + { .addr = 0x401800d7, .val = 0xe1 }, /* HPH_R_TEST OCP_DET_EN */ + { .addr = 0x4018044e, .val = 0x04 }, /* CDC_HPH_GAIN_CTL HPHL_RX_EN */ + { .addr = 0x4018044e, .val = 0x0c }, /* CDC_HPH_GAIN_CTL HPHR_RX_EN */ + { .addr = 0x4018000f, .val = 0x0c }, /* ANA_TX_CH2 GAIN (18.0dB) */ + { .addr = 0x40180133, .val = 0x84 }, /* HPH_NEW_INT_RDAC_HD2_CTL_L */ + { .addr = 0x40180136, .val = 0x84 }, /* HPH_NEW_INT_RDAC_HD2_CTL_R */ + { .addr = 0x401800d9, .val = 0x19 }, /* HPH_RDAC_CLK_CTL1 OPAMP_CHOP_CLK_EN */ + { .addr = 0x40180132, .val = 0x50 }, /* HPH_NEW_INT_RDAC_GAIN_CTL RDAC_GAINCTL(0.55) */ + { .addr = 0x40180510, .val = 0x05 }, /* SEQR_CTRL HPH_UP_T0 */ + { .addr = 0x40180519, .val = 0x05 }, /* SEQR_CTRL HPH_UP_T9 */ + { .addr = 0x4018051b, .val = 0x06 }, /* SEQR_CTRL HPH_DN_T0 */ + { .addr = 0x40180414, .val = 0x02 }, /* CDC_COMP_CTL_0 HPHL_COMP_EN */ + { .addr = 0x40180414, .val = 0x03 }, /* CDC_COMP_CTL_0 HPHR_COMP_EN */ + { .addr = 0x401804f2, .val = 0x80 }, /* DRE_DLY_VAL SWR_HPHL(0) */ + { .addr = 0x401804f2, .val = 0x00 }, /* DRE_DLY_VAL SWR_HPHR(0) */ + { .addr = 0x40180501, .val = 0x01 }, /* SEQR_CTRL SYS_USAGE_CTRL */ + /* Arms MBHC: jack insertion asserts SDCA_4 (GE_DETECTED_MODE). */ + { .addr = 0x40180601, .val = 0x01 }, /* MBHC_CTRL DEVICE_DET */ + { .addr = 0x40180414, .val = 0x00 }, /* CDC_COMP_CTL_0 */ + { .addr = 0x401804f2, .val = 0x88 }, /* DRE_DLY_VAL */ + { .addr = 0x40180517, .val = 0x07 }, /* SEQR_CTRL HPH_UP_T7 */ + { .addr = 0x4018051c, .val = 0x07 }, /* SEQR_CTRL HPH_DN_T1 */ + { .addr = 0x401800ce, .val = 0x28 }, /* HPH_OCP_CTL */ + { .addr = 0x401800d4, .val = 0xe0 }, /* HPH_L_TEST */ + { .addr = 0x401800d7, .val = 0xe0 }, /* HPH_R_TEST */ + { .addr = 0x40180510, .val = 0x07 }, /* SEQR_CTRL HPH_UP_T0 */ + { .addr = 0x40c80008, .val = 0x01 }, /* SMP_JACK_CTRL FUNC_ACT (RESET_FUNCTION_NOW) */ + { .addr = 0x40c00008, .val = 0x02 }, /* SMP_JACK_CTRL CMT_GRP_MASK */ + { .addr = 0x40c80000, .val = 0xff }, /* SMP_JACK_CTRL FUNC_STAT */ + { .addr = 0x00000000, .val = 0x08 }, /* clear DP0 INT status SDCA_CASCADE */ + { .addr = 0x00000041, .val = 0x08 }, /* SCP_INT_STATUS_MASK_1 PORT_0_CASCADE_3 */ + /* Only SDCA_4 unmasked; protection IRQs stay masked. */ + { .addr = 0x0000005c, .val = 0x10 }, /* INTMASK_1 SDCA_4 (GE_DETECTED_MODE) */ + { .addr = 0x4018016a, .val = 0x00 }, /* CP_DTOP_CTRL_14 */ + { .addr = 0x40180165, .val = 0x6b }, /* CP_DTOP_CTRL_9 */ + /* + * L_DET_EN is left disabled: asserting SDCA_4 before the machine + * card registers the jack blocks the deferred card probe. + */ +}; + +/* Function Descriptor */ +static struct sdca_function_desc wcd9378_sdca_desc = { + .adr = 0x3, + .type = SDCA_FUNCTION_TYPE_SIMPLE_JACK, + .name = SDCA_FUNCTION_TYPE_SIMPLE_NAME, +}; + +/* Main Function Data */ +static struct sdca_function_data wcd9378_sdca_data = { + .desc = &wcd9378_sdca_desc, + .num_entities = ARRAY_SIZE(wcd9378_sdca_entities), + .entities = wcd9378_sdca_entities, + .num_clusters = ARRAY_SIZE(wcd9378_sdca_clusters), + .clusters = wcd9378_sdca_clusters, + .num_init_table = ARRAY_SIZE(wcd9378_sdca_init_table), + .init_table = wcd9378_sdca_init_table, + .reset_max_delay = 25000, /* 25ms — WCD9378 power-on reset completes well within this */ +}; + +/* Vendor SCP register: host clock divide-by-2 (bank 1 shadow). */ +#define WCD9378_SCP_HOST_CLK_DIV2_CTL_B1 0xf0 + +/* Slave ports 1..8, mapped to master ports via qcom,port-mapping. */ +#define WCD9378_SDCA_MAX_PORTS 8 + +static const char * const wcd9378_sdca_supplies[] = { + "vdd-buck", "vdd-rxtx", "vdd-io", "vdd-mic-bias", +}; + +int wcd9378_sdca_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + struct device *dev = &slave->dev; + struct sdw_dpn_prop *sink, *src; + int ret; + + /* + * All SoundWire slave properties for compute-mode WCD9378 are + * fixed by the silicon and are not described in DT, so we do + * not call sdw_slave_read_prop() here. + */ + prop->use_domain_irq = true; + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY | + SDW_SCP_INT1_IMPL_DEF; + prop->simple_clk_stop_capable = true; + prop->paging_support = true; + prop->clock_reg_supported = true; + prop->lane_control_support = true; + + /* Source ports: DP2 (headset mic), DP5 (optimisation TX). */ + prop->source_ports = BIT(2) | BIT(5); + /* Sink ports: DP6 (HPH audio), DP7 (HPH envelope), DP8 (optimisation RX). */ + prop->sink_ports = BIT(6) | BIT(7) | BIT(8); + + src = devm_kcalloc(dev, 2, sizeof(*src), GFP_KERNEL); + if (!src) + return -ENOMEM; + + src[0].num = 2; + src[0].type = SDW_DPN_SIMPLE; + src[0].simple_ch_prep_sm = true; + src[0].ch_prep_timeout = 10; + src[0].max_ch = 1; + src[0].min_ch = 1; + + src[1].num = 5; + src[1].type = SDW_DPN_SIMPLE; + src[1].simple_ch_prep_sm = true; + src[1].ch_prep_timeout = 10; + src[1].max_ch = 1; + src[1].min_ch = 1; + + prop->src_dpn_prop = src; + + sink = devm_kcalloc(dev, 3, sizeof(*sink), GFP_KERNEL); + if (!sink) + return -ENOMEM; + + sink[0].num = 6; + sink[0].type = SDW_DPN_SIMPLE; + sink[0].simple_ch_prep_sm = true; + sink[0].ch_prep_timeout = 10; + sink[0].max_ch = 2; + sink[0].min_ch = 1; + + sink[1].num = 7; + sink[1].type = SDW_DPN_FULL; + sink[1].simple_ch_prep_sm = true; + sink[1].ch_prep_timeout = 10; + sink[1].max_ch = 1; + sink[1].min_ch = 1; + + sink[2].num = 8; + sink[2].type = SDW_DPN_REDUCED; + sink[2].simple_ch_prep_sm = true; + sink[2].ch_prep_timeout = 10; + sink[2].max_ch = 2; + sink[2].min_ch = 1; + + prop->sink_dpn_prop = sink; + + ret = device_property_read_u32_array(dev, "qcom,port-mapping", + &slave->m_port_map[1], + WCD9378_SDCA_MAX_PORTS); + if (ret) + return dev_err_probe(dev, ret, "qcom,port-mapping missing\n"); + + return 0; +} + +static int wcd9378_sdca_populate_function(struct device *dev, + struct sdca_function_data *function) +{ + /* @function->desc is already set by the framework; fill payload only. */ + if (function->desc->type != wcd9378_sdca_desc.type) + return -EINVAL; + + function->num_entities = wcd9378_sdca_data.num_entities; + function->entities = wcd9378_sdca_data.entities; + function->num_clusters = wcd9378_sdca_data.num_clusters; + function->clusters = wcd9378_sdca_data.clusters; + function->num_init_table = wcd9378_sdca_data.num_init_table; + function->init_table = wcd9378_sdca_data.init_table; + function->reset_max_delay = wcd9378_sdca_data.reset_max_delay; + + return 0; +} + +static const struct sdca_class_ops wcd9378_sdca_class_ops = { + .populate_function = wcd9378_sdca_populate_function, +}; + +int wcd9378_sdca_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct device *dev = &slave->dev; + struct sdca_device_data *data = &slave->sdca_data; + struct wcd9378_priv *priv; + struct gpio_desc *reset; + int ret; + + /* + * 0x0217:0x0110 covers both mobile (non-SDCA) and SDCA-compliant + * fuse variants. Only SDCA-compliant nodes bind to this driver; + * mobile-mode nodes are handled by a different driver. + */ + if (!device_property_read_bool(dev, "qcom,sdca-compliant")) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + + /* No SPMI parent: supplies and reset live on the SoundWire DT node. */ + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(wcd9378_sdca_supplies), + wcd9378_sdca_supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to enable supplies\n"); + + reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), + "failed to get reset GPIO\n"); + + if (reset) { + gpiod_set_value(reset, 1); + usleep_range(20, 30); + gpiod_set_value(reset, 0); + usleep_range(20, 30); + } + + /* SCP writes below need the slave attached. */ + ret = sdw_slave_wait_for_init(slave, 5000); + if (ret) + return dev_err_probe(dev, ret, + "slave attach timeout: %d\n", ret); + + /* + * TX PDM clock: bank-1 shadow + SCP_COMMIT. SCP survives PDE + * cycles; one-shot before any port is enabled. + */ + ret = sdw_write_no_pm(slave, WCD9378_SCP_HOST_CLK_DIV2_CTL_B1, 0x01); + if (ret) + return dev_err_probe(dev, ret, + "HOST_CLK_DIV2_CTL_B1: %d\n", ret); + + ret = sdw_write_no_pm(slave, SDW_SCP_COMMIT, 0x02); + if (ret) + return dev_err_probe(dev, ret, "SCP_COMMIT: %d\n", ret); + + /* DT has no DisCo enumeration; seed the descriptor here. */ + if (!data->num_functions) { + data->function[0].type = wcd9378_sdca_desc.type; + data->function[0].adr = wcd9378_sdca_desc.adr; + data->function[0].name = wcd9378_sdca_desc.name; + data->num_functions = 1; + } + + return sdca_class_probe(slave, &priv->class, &wcd9378_sdca_class_ops); +} + +void wcd9378_sdca_remove(struct sdw_slave *slave) +{ + struct wcd9378_priv *priv = dev_get_drvdata(&slave->dev); + + sdca_class_remove(&priv->class); +} + +int wcd9378_sdca_runtime_suspend(struct device *dev) +{ + struct wcd9378_priv *priv = dev_get_drvdata(dev); + + return sdca_class_runtime_suspend(&priv->class); +} + +int wcd9378_sdca_runtime_resume(struct device *dev) +{ + struct wcd9378_priv *priv = dev_get_drvdata(dev); + + return sdca_class_runtime_resume(&priv->class); +} + +int wcd9378_sdca_system_suspend(struct device *dev) +{ + struct wcd9378_priv *priv = dev_get_drvdata(dev); + + return sdca_class_system_suspend(&priv->class); +} + +int wcd9378_sdca_system_resume(struct device *dev) +{ + struct wcd9378_priv *priv = dev_get_drvdata(dev); + + return sdca_class_system_resume(&priv->class); +} + +/* SoundWire slave-driver plumbing lives in wcd9378-sdw.c. */ diff --git a/sound/soc/codecs/wcd9378-sdca.h b/sound/soc/codecs/wcd9378-sdca.h new file mode 100644 index 000000000000..6ddae9da395b --- /dev/null +++ b/sound/soc/codecs/wcd9378-sdca.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* Copyright (c) 2025 Qualcomm Technologies, Inc. */ + +#ifndef _WCD9378_SDCA_H +#define _WCD9378_SDCA_H + +#include +#include + +int wcd9378_sdca_probe(struct sdw_slave *slave, + const struct sdw_device_id *id); +void wcd9378_sdca_remove(struct sdw_slave *slave); +int wcd9378_sdca_read_prop(struct sdw_slave *slave); + +int wcd9378_sdca_runtime_suspend(struct device *dev); +int wcd9378_sdca_runtime_resume(struct device *dev); +int wcd9378_sdca_system_suspend(struct device *dev); +int wcd9378_sdca_system_resume(struct device *dev); + +#endif /* _WCD9378_SDCA_H */ diff --git a/sound/soc/codecs/wcd9378-sdw.c b/sound/soc/codecs/wcd9378-sdw.c new file mode 100644 index 000000000000..52295e7646cf --- /dev/null +++ b/sound/soc/codecs/wcd9378-sdw.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright (c) 2025 Qualcomm Technologies, Inc. + +/* + * WCD9378 (Tambora) SoundWire driver glue for the SDCA compute-mode + * codec. The SDCA topology and hardware-specific probe logic live in + * wcd9378-sdca.c; this file carries the SoundWire slave driver plumbing + * and the module boilerplate. + */ + +#include +#include +#include +#include +#include +#include "wcd9378-sdca.h" + +static const struct dev_pm_ops wcd9378_sdw_pm_ops = { + SYSTEM_SLEEP_PM_OPS(wcd9378_sdca_system_suspend, + wcd9378_sdca_system_resume) + RUNTIME_PM_OPS(wcd9378_sdca_runtime_suspend, + wcd9378_sdca_runtime_resume, NULL) +}; + +static const struct sdw_slave_ops wcd9378_sdw_ops = { + .read_prop = wcd9378_sdca_read_prop, +}; + +static const struct sdw_device_id wcd9378_sdw_id[] = { + SDW_SLAVE_ENTRY(0x0217, 0x0110, 0), + { } +}; +MODULE_DEVICE_TABLE(sdw, wcd9378_sdw_id); + +static struct sdw_driver wcd9378_sdw_driver = { + .driver = { + .name = "wcd9378", + .pm = pm_ptr(&wcd9378_sdw_pm_ops), + }, + .probe = wcd9378_sdca_probe, + .remove = wcd9378_sdca_remove, + .id_table = wcd9378_sdw_id, + .ops = &wcd9378_sdw_ops, +}; +module_sdw_driver(wcd9378_sdw_driver); + +MODULE_DESCRIPTION("Qualcomm WCD9378 (Tambora) SoundWire codec"); +MODULE_AUTHOR("Qualcomm Technologies, Inc."); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS("SND_SOC_SDCA"); +MODULE_IMPORT_NS("SND_SOC_SDCA_CLASS"); From 08e4f9133c68413acbe365a8d02fa15fb24cb27b Mon Sep 17 00:00:00 2001 From: Christian Ruppert Date: Sat, 19 Sep 2026 17:18:38 +0200 Subject: [PATCH 441/924] Fix Audient EVO4 master playback control name The Audient EVO4 master volume mixer channel enumerates as 'EVO4 '. Rename it to 'Master' according to control-names.rst. Signed-off-by: Christian Ruppert Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260919151840.24371-2-arc@gmx.li --- sound/usb/mixer_maps.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index f8e1f78039e1..98b4146216ae 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -505,6 +505,14 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { {} }; +/* Audient EVO4: Rename FU 10 from 'EVO4 ' to 'Master' according to the + * ALSA naming convention + */ +static const struct usbmix_name_map audient_evo4_map[] = { + { 10, "Master Playback" }, + {} +}; + /* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is * traced through to the Speaker output terminal, so it is named "Speaker * Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI @@ -632,6 +640,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x2708, 0x0002), .map = audient_id14_map, }, + { + /* Audient EVO4 MkI */ + .id = USB_ID(0x2708, 0x0006), + .map = audient_evo4_map, + }, { /* Audient iD14 MkII */ .id = USB_ID(0x2708, 0x0008), From 025877a3833d12aff023ce9e89d75f4d6fd61ddb Mon Sep 17 00:00:00 2001 From: Christian Ruppert Date: Sat, 19 Sep 2026 17:18:39 +0200 Subject: [PATCH 442/924] Add Audient EVO4 mixer quirks The controls enumerated by the Audient EVO4 are incomplete. Add missing controls for * channel muting * the cross-fader * the recording mixer * phantom power The driver is structured so that it should be trivial to extend it to the EVO8 (and potentially other Audient devices) but we need a kind soul who has access to that hardware to correctly map the control names and test everything. Signed-off-by: Christian Ruppert Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260919151840.24371-3-arc@gmx.li --- MAINTAINERS | 7 + sound/usb/Makefile | 1 + sound/usb/mixer_evo.c | 505 +++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_evo.h | 12 + sound/usb/mixer_quirks.c | 5 + 5 files changed, 530 insertions(+) create mode 100644 sound/usb/mixer_evo.c create mode 100644 sound/usb/mixer_evo.h diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c..656721c04713 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4346,6 +4346,13 @@ F: drivers/net/ieee802154/at86rf230.h F: drivers/net/ieee802154/atusb.c F: drivers/net/ieee802154/atusb.h +AUDIENT EVO MIXER DRIVER +M: Christian Ruppert +L: linux-sound@vger.kernel.org +S: Maintained +F: sound/usb/mixer_evo.c +F: sound/usb/mixer_evo.h + AUDIT SUBSYSTEM M: Paul Moore M: Eric Paris diff --git a/sound/usb/Makefile b/sound/usb/Makefile index e62794a87e73..2e842dcc73de 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -11,6 +11,7 @@ snd-usb-audio-y := card.o \ helper.o \ implicit.o \ mixer.o \ + mixer_evo.o \ mixer_quirks.o \ mixer_scarlett.o \ mixer_scarlett2.o \ diff --git a/sound/usb/mixer_evo.c b/sound/usb/mixer_evo.c new file mode 100644 index 000000000000..c327b82c8d2d --- /dev/null +++ b/sound/usb/mixer_evo.c @@ -0,0 +1,505 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Audient EVO driver for ALSA + * Copyright (C) 2026 Christian Ruppert + * + * Based on the work of Ivan Hazucha for audient-evo-py. + * https://github.com/vanzaho/audient-evo-py/ + */ + + +#include +#include +#include +#include +#include +#include + +#include "usbaudio.h" +#include "mixer.h" +#include "mixer_quirks.h" +#include "helper.h" +#include "mixer_evo.h" + + +enum snd_evo_type { + SND_EVO_TYPE_MONITOR = 0, + SND_EVO_TYPE_PHANTOM, + SND_EVO_TYPE_MUTE, + SND_EVO_TYPE_MUTE_OUT, + SND_EVO_TYPE_MIXER, + SND_EVO_TYPE_NCTYPES +}; + +static const struct snd_evo_ctrl_type { + snd_ctl_elem_type_t type; + bool invert; + union { + int integer; + } min; + union { + int integer; + } max; + u16 wValue; + u16 wIndex; +} snd_evo_ctypes[SND_EVO_TYPE_NCTYPES] = { + [SND_EVO_TYPE_MONITOR] = { + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .min.integer = 0, + .max.integer = 127, + .wValue = 0x0000, + .wIndex = 0x3800, + }, + [SND_EVO_TYPE_PHANTOM] = { + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .invert = false, + .min.integer = 0, + .max.integer = 1, + .wValue = 0x0000, + .wIndex = 0x3A00, + }, + [SND_EVO_TYPE_MUTE] = { + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .invert = true, + .min.integer = 0, + .max.integer = 1, + .wValue = 0x0200, + .wIndex = 0x3A00, + }, + [SND_EVO_TYPE_MUTE_OUT] = { + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .invert = true, + .min.integer = 0, + .max.integer = 1, + .wValue = 0x0100, + .wIndex = 0x3B00, + }, + [SND_EVO_TYPE_MIXER] = { + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .min.integer = -128 * 256, + .max.integer = 6 * 256, + .wValue = 0x0100, + .wIndex = 0x3C00, + }, +}; + +#define SND_EVO_FLAGS_DIRECTION (1L<<0) +#define SND_EVO_FLAGS_CAPTURE (1L<<0) +#define SND_EVO_FLAGS_PLAYBACK (0L<<0) + +struct snd_evo_ctrl { + char *basename; + enum snd_evo_type type; + unsigned int channels; + unsigned int hwchannels; + unsigned int val_offs; + unsigned long flags; +}; + +static const struct snd_evo_ctrl evo4_controls[] = { + { + .basename = "Master", + .type = SND_EVO_TYPE_MUTE_OUT, + .channels = 1, + .hwchannels = 1, + .val_offs = 0, + .flags = SND_EVO_FLAGS_PLAYBACK, + }, + { + .basename = "Mic", + .type = SND_EVO_TYPE_MUTE, + .channels = 4, + .hwchannels = 2, + .val_offs = 0, + .flags = SND_EVO_FLAGS_PLAYBACK, + }, + { + .basename = "Monitor", + .type = SND_EVO_TYPE_MONITOR, + .channels = 1, + .hwchannels = 1, + .val_offs = 0, + .flags = SND_EVO_FLAGS_PLAYBACK, + }, + { + .basename = "Mic 1", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 0, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Mic 2", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 2, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Master Left", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 4, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Master Right", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 6, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Loopback Left", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 8, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Loopback Right", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 10, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Mic 1 Phantom", + .type = SND_EVO_TYPE_PHANTOM, + .channels = 1, + .hwchannels = 1, + .val_offs = 0, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Mic 2 Phantom", + .type = SND_EVO_TYPE_PHANTOM, + .channels = 1, + .hwchannels = 1, + .val_offs = 1, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { 0 }, /* sentinel */ +}; + +static const struct evo_devinfo { + u32 usb_id; + const struct snd_evo_ctrl *controls; +} evo_devinfo[] = { + { + .usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID), + .controls = evo4_controls, + }, + { + .usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID), + .controls = NULL, + }, + { 0 } /* sentinel */ +}; + + +static inline unsigned long snd_evo_kctl_priv(enum snd_evo_type type, + u8 valoffs, u8 hwchannels) +{ + unsigned long type_l = type & 0xFF; + unsigned long valoffs_l = valoffs & 0xFF; + unsigned long hwchannels_l = hwchannels & 0xFF; + + return (hwchannels_l << 16) | (valoffs_l << 8) | type_l; +} + +static __always_inline +const struct snd_evo_ctrl_type *snd_evo_kctl_type(struct snd_kcontrol *kctl) +{ + return &snd_evo_ctypes[kctl->private_value & 0xFF]; +} + +static __always_inline u16 snd_evo_kctl_valoffs(struct snd_kcontrol *kctl) +{ + return (kctl->private_value >> 8) & 0xFF; +} + +static __always_inline u16 snd_evo_kctl_hwchannels(struct snd_kcontrol *kctl) +{ + return (kctl->private_value >> 16) & 0xFF; +} + +static inline u16 snd_evo_kctl_channels(struct snd_kcontrol *kctl) +{ + struct usb_mixer_elem_info *info = kctl->private_data; + + return info->channels; +} + + +#define EVO_REQ_CUR (0x01) +#define EVO_REQTYPE_SET (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) +#define EVO_REQTYPE_GET (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +static inline int snd_evo_get_cur(struct usb_mixer_interface *mixer, + u16 value, u16 index, + void *buf, size_t len) +{ + struct usb_device *const dev = mixer->chip->dev; + + return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + EVO_REQ_CUR, EVO_REQTYPE_GET, + value, index, buf, len); +} + +static inline int snd_evo_set_cur(struct usb_mixer_interface *mixer, + u16 value, u16 index, + void *buf, size_t len) +{ + struct usb_device *const dev = mixer->chip->dev; + + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + EVO_REQ_CUR, EVO_REQTYPE_SET, + value, index, buf, len); +} + + +static int snd_ctl_evo_boolean_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *const mixer = list->mixer; + + const unsigned int nchan = snd_evo_kctl_channels(kctl); + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int i; + + for (i = 0; i < hwchan; i++) { + u32 buf; + int ret; + + ret = snd_evo_get_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + + ctl_val->value.integer.value[i] = le32_to_cpu(buf) ? 1 : 0; + + if (ctype->invert) + ctl_val->value.integer.value[i] ^= 0x1; + } + + for (; i < nchan; i++) + ctl_val->value.integer.value[i] = ctype->invert + ? ctype->max.integer + : ctype->min.integer; + + return 0; +} + +static int snd_ctl_evo_boolean_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int changed = 0; + int i; + + for (i = 0; i < hwchan; i++) { + u32 rbuf; + u32 buf = cpu_to_le32(ctl_val->value.integer.value[i] ? 1 : 0); + int ret; + + if (ctype->invert) + buf ^= 0x1; + + ret = snd_evo_get_cur(mixer, value + i, index, + &rbuf, sizeof(rbuf)); + if (ret < 0) + return ret; + + if (rbuf != buf) { + changed = 1; + + ret = snd_evo_set_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + } + } + + return changed; +} + +static int snd_ctl_evo_integer_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *const mixer = list->mixer; + + const unsigned int nchan = snd_evo_kctl_channels(kctl); + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int i; + + for (i = 0; i < hwchan; i++) { + u16 buf; + s16 val; + int ret; + + ret = snd_evo_get_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + + val = le16_to_cpu(buf); + ctl_val->value.integer.value[i] = val; + } + + for (; i < nchan; i++) + ctl_val->value.integer.value[i] = ctype->min.integer; + + return 0; +} + +static int snd_ctl_evo_integer_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int changed = 0; + int i; + + for (i = 0; i < hwchan; i++) { + s16 rbuf; + s16 buf = cpu_to_le16(ctl_val->value.integer.value[i]); + int ret; + + ret = snd_evo_get_cur(mixer, value + i, index, + &rbuf, sizeof(rbuf)); + if (ret < 0) + return ret; + + if (rbuf != buf) { + changed = 1; + + ret = snd_evo_set_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + } + } + + return changed; +} + +static int snd_ctl_evo_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + + uinfo->type = ctype->type; + uinfo->access = SNDRV_CTL_ELEM_ACCESS_READWRITE + | SNDRV_CTL_ELEM_ACCESS_VOLATILE; + uinfo->count = snd_evo_kctl_channels(kctl); + uinfo->value.integer.min = ctype->min.integer; + uinfo->value.integer.max = ctype->max.integer; + return 0; +} + + +static int snd_evo_add_ctrl(struct usb_mixer_interface *mixer, + const struct snd_evo_ctrl *ctrl) +{ + const struct snd_evo_ctrl_type *ctype = &snd_evo_ctypes[ctrl->type]; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct snd_kcontrol_new knew = { 0 }; + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.name = name; + knew.info = snd_ctl_evo_info; + knew.private_value = snd_evo_kctl_priv(ctrl->type, ctrl->val_offs, + ctrl->hwchannels); + + switch (ctype->type) { + case SNDRV_CTL_ELEM_TYPE_INTEGER: + knew.get = snd_ctl_evo_integer_get; + knew.put = snd_ctl_evo_integer_set; + break; + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + knew.get = snd_ctl_evo_boolean_get; + knew.put = snd_ctl_evo_boolean_set; + break; + default: + return -EINVAL; + } + + snprintf(name, sizeof(name), "%s %s %s", ctrl->basename, + ((ctrl->flags & SND_EVO_FLAGS_DIRECTION) + == SND_EVO_FLAGS_CAPTURE) ? "Capture" : "Playback", + (ctype->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN) + ? "Switch" : "Volume"); + + elem = kzalloc_obj(*elem); + if (!elem) + return -ENOMEM; + + elem->head.mixer = mixer; + elem->channels = ctrl->channels; + + kctl = snd_ctl_new1(&knew, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + + kctl->private_free = snd_usb_mixer_elem_free; + + return snd_usb_mixer_add_control(&elem->head, kctl); +} + + +/* called from mixer_quirks.c */ +int snd_evo_controls_create(struct usb_mixer_interface *mixer) +{ + const struct evo_devinfo *info; + const struct snd_evo_ctrl *ctrl; + + for (info = evo_devinfo; info->controls; info++) + if (info->usb_id == mixer->chip->usb_id) + break; + + if (!info->controls) + return -ENODEV; + + for (ctrl = info->controls; ctrl->basename; ctrl++) { + const int ret = snd_evo_add_ctrl(mixer, ctrl); + + if (ret < 0) + return ret; + } + + return 0; +} diff --git a/sound/usb/mixer_evo.h b/sound/usb/mixer_evo.h new file mode 100644 index 000000000000..7fe7e197185c --- /dev/null +++ b/sound/usb/mixer_evo.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_MIXER_EVO_H +#define __USB_MIXER_EVO_H + +#include "mixer.h" + +#define USB_AUDIENT_VID (0x2708) +#define USB_EVO4_PID (0x0006) + +int snd_evo_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_EVO_H */ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index fc622eb95dc5..1e266fdbbb8e 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -38,6 +38,7 @@ #include "mixer_scarlett2.h" #include "mixer_us16x08.h" #include "mixer_s1810c.h" +#include "mixer_evo.h" #include "helper.h" #include "fcp.h" @@ -4539,6 +4540,10 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) err = snd_fcp_init(mixer); break; + case USB_ID(USB_AUDIENT_VID, USB_EVO4_PID): /* Audient EVO 4 */ + err = snd_evo_controls_create(mixer); + break; + case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */ err = snd_soundblaster_e1_switch_create(mixer); break; From 19b42c1fff7f220a6346c614d79ce1bda6d95b40 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 29 Sep 2026 22:17:26 +0800 Subject: [PATCH 443/924] ALSA: usb-audio: Protect Roland control activation The USB MIDI driver changes the Roland MIDI Input Mode control's access flags directly from the rawmidi open and close paths. These changes are not protected by the ALSA control core and the corresponding notifications can race with control access. Use snd_ctl_activate_id() so that the control core updates the access flags and sends the notification under its lock. Release the USB MIDI mutex before calling it because the control write path holds controls_rwsem while roland_load_put() takes the USB MIDI mutex. Keep the state transition and alternate-setting change under the USB MIDI mutex; rawmidi's open mutex serializes the enclosing open and close paths. Fixes: 96f61d9ade82 ("sound: usb-audio: allow switching altsetting on Roland USB MIDI devices") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260929141726.2166899-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/usb/midi.c | 53 ++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/sound/usb/midi.c b/sound/usb/midi.c index 0480a9b89e8f..e3d157554061 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -1150,41 +1150,42 @@ static int substream_open(struct snd_rawmidi_substream *substream, int dir, int open) { struct snd_usb_midi *umidi = substream->rmidi->private_data; - struct snd_kcontrol *ctl; + struct snd_ctl_elem_id ctl_id; + bool activate_ctl = false; + bool active; guard(rwsem_read)(&umidi->disc_rwsem); if (umidi->disconnected) return open ? -ENODEV : 0; - guard(mutex)(&umidi->mutex); - if (open) { - if (!umidi->opened[0] && !umidi->opened[1]) { - if (umidi->roland_load_ctl) { - ctl = umidi->roland_load_ctl; - ctl->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(umidi->card, - SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); - update_roland_altsetting(umidi); + scoped_guard(mutex, &umidi->mutex) { + if (open) { + if (!umidi->opened[0] && !umidi->opened[1]) { + if (umidi->roland_load_ctl) { + ctl_id = umidi->roland_load_ctl->id; + activate_ctl = true; + active = false; + update_roland_altsetting(umidi); + } } - } - umidi->opened[dir]++; - if (umidi->opened[1]) - snd_usbmidi_input_start(&umidi->list); - } else { - umidi->opened[dir]--; - if (!umidi->opened[1] && !umidi->keep_input_running) - snd_usbmidi_input_stop(&umidi->list); - if (!umidi->opened[0] && !umidi->opened[1]) { - if (umidi->roland_load_ctl) { - ctl = umidi->roland_load_ctl; - ctl->vd[0].access &= - ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(umidi->card, - SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); + umidi->opened[dir]++; + if (umidi->opened[1]) + snd_usbmidi_input_start(&umidi->list); + } else { + umidi->opened[dir]--; + if (!umidi->opened[1] && !umidi->keep_input_running) + snd_usbmidi_input_stop(&umidi->list); + if (!umidi->opened[0] && !umidi->opened[1]) { + if (umidi->roland_load_ctl) { + ctl_id = umidi->roland_load_ctl->id; + activate_ctl = true; + active = true; + } } } } + if (activate_ctl) + snd_ctl_activate_id(umidi->card, &ctl_id, active); return 0; } From e4f960371ffdc387b0b559143f09357af342dcdc Mon Sep 17 00:00:00 2001 From: Ilya Pavlukhin Date: Sun, 20 Sep 2026 12:28:05 +0300 Subject: [PATCH 444/924] ACPI: scan: Add CLSA0102 to the serial bus ignore list The Lenovo Yoga Slim 7 Carbon 14ACN6 (82L0) describes the two CS35L41 amplifiers of its bass speakers as a single ACPI node, HID CLSA0102, with two I2cSerialBusV2 resources (0x40 and 0x41), like CLSA0100 and CLSA0101 on the Legion 7. Skip the default I2C enumeration so that serial-multi-instantiate can create a client for each amplifier. Link: https://bugzilla.kernel.org/show_bug.cgi?id=215632 Assisted-by: Claude Opus 5 Signed-off-by: Ilya Pavlukhin Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260920092808.17234-2-i.pavluhin@ya.ru --- drivers/acpi/scan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 163a3cccf197..41a4756f0ea9 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1764,6 +1764,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) /* Non-conforming _HID for Cirrus Logic already released */ {"CLSA0100", }, {"CLSA0101", }, + {"CLSA0102", }, /* * Some ACPI devs contain SerialBus resources even though they are not * attached to a serial bus at all. From 5a0f971766c276f4efbafbc5d6d9b2f60181d08e Mon Sep 17 00:00:00 2001 From: Ilya Pavlukhin Date: Sun, 20 Sep 2026 12:28:06 +0300 Subject: [PATCH 445/924] platform/x86: serial-multi-instantiate: Add CLSA0102 Instantiate both CS35L41 amplifiers of the CLSA0102 node found on the Lenovo Yoga Slim 7 Carbon 14ACN6 (82L0), the same way as for CLSA0100 and CLSA0101. Link: https://bugzilla.kernel.org/show_bug.cgi?id=215632 Assisted-by: Claude Opus 5 Signed-off-by: Ilya Pavlukhin Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260920092808.17234-3-i.pavluhin@ya.ru --- drivers/platform/x86/serial-multi-instantiate.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c index 3e89fcdd45f7..d535025a3f73 100644 --- a/drivers/platform/x86/serial-multi-instantiate.c +++ b/drivers/platform/x86/serial-multi-instantiate.c @@ -426,6 +426,7 @@ static const struct acpi_device_id smi_acpi_ids[] = { /* Non-conforming _HID for Cirrus Logic already released */ { "CLSA0100", (unsigned long)&cs35l41_hda }, { "CLSA0101", (unsigned long)&cs35l41_hda }, + { "CLSA0102", (unsigned long)&cs35l41_hda }, { } }; MODULE_DEVICE_TABLE(acpi, smi_acpi_ids); From 7cd9f9f4e1f5fdf22720e1dd13050c6de67cde9e Mon Sep 17 00:00:00 2001 From: Ilya Pavlukhin Date: Sun, 20 Sep 2026 12:28:07 +0300 Subject: [PATCH 446/924] ALSA: hda: cs35l41: Add support for CLSA0102 The Lenovo Yoga Slim 7 Carbon 14ACN6 (82L0) drives its bass speakers with two CS35L41 amplifiers, described by ACPI HID CLSA0102 without a _DSD. The node has the same layout as CLSA0100/CLSA0101: both amps in one node, reset on GPIO index 0 and the interrupt on a GpioInt. Unlike the Legion 7 there is no speaker ID GPIO, index 2 is the interrupt line. The Windows driver runs the amps from an external VSPK supply (ExternalVspkControl, boost converter disabled), so use CS35L41_EXT_BOOST with the VSPK switch on GPIO1. With the internal boost converter enabled the amps report "Amp short error" during playback. There is no _SUB in the ACPI node, so the firmware files are looked up by the codec SSID, 17aa3856. Link: https://bugzilla.kernel.org/show_bug.cgi?id=215632 Assisted-by: Claude Opus 5 Signed-off-by: Ilya Pavlukhin Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260920092808.17234-4-i.pavluhin@ya.ru --- .../hda/codecs/side-codecs/cs35l41_hda_i2c.c | 3 ++ .../codecs/side-codecs/cs35l41_hda_property.c | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c b/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c index fdf406e92fca..59b82238f2b8 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c @@ -23,6 +23,8 @@ static int cs35l41_hda_i2c_probe(struct i2c_client *clt) device_name = "CLSA0100"; else if (strstr(dev_name(&clt->dev), "CLSA0101")) device_name = "CLSA0101"; + else if (strstr(dev_name(&clt->dev), "CLSA0102")) + device_name = "CLSA0102"; else if (strstr(dev_name(&clt->dev), "CSC3551")) device_name = "CSC3551"; else @@ -45,6 +47,7 @@ static const struct i2c_device_id cs35l41_hda_i2c_id[] = { static const struct acpi_device_id cs35l41_acpi_hda_match[] = { {"CLSA0100", 0 }, {"CLSA0101", 0 }, + {"CLSA0102", 0 }, {"CSC3551", 0 }, {} }; diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index 416d7bf3e289..c4f2bbed0748 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -439,6 +439,33 @@ static int lenovo_legion_no_acpi(struct cs35l41_hda *cs35l41, struct device *phy return 0; } +/* + * Device CLSA0102 (Lenovo Yoga Slim 7 Carbon 14ACN6) has the same ACPI layout as CLSA010(0/1): + * no _DSD, both amps in one node, reset on GPIO index 0. It has no speaker ID GPIO, the + * interrupt GPIO sits at index 2. The speakers use an external VSPK supply switched by GPIO1 + * (the Windows driver sets ExternalVspkControl and keeps the boost converter off). + */ +static int lenovo_yoga_slim7_carbon_no_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, + int id, const char *hid) +{ + struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; + + /* check I2C address to assign the index */ + cs35l41->index = id == 0x40 ? 0 : 1; + cs35l41->channel_index = 0; + cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH); + cs35l41->speaker_id = -ENOENT; + hw_cfg->spk_pos = cs35l41->index; + hw_cfg->bst_type = CS35L41_EXT_BOOST; + hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; + hw_cfg->gpio1.valid = true; + hw_cfg->gpio2.func = CS35L41_INTERRUPT; + hw_cfg->gpio2.valid = true; + hw_cfg->valid = true; + + return 0; +} + static int missing_speaker_id_gpio2(struct cs35l41_hda *cs35l41, struct device *physdev, int id, const char *hid) { @@ -463,6 +490,7 @@ struct cs35l41_prop_model { static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CLSA0100", NULL, lenovo_legion_no_acpi }, { "CLSA0101", NULL, lenovo_legion_no_acpi }, + { "CLSA0102", NULL, lenovo_yoga_slim7_carbon_no_acpi }, { "CSC3551", "10251826", generic_dsd_config }, { "CSC3551", "1025182C", generic_dsd_config }, { "CSC3551", "10251844", generic_dsd_config }, From 2afb053cf1e92213344134289088bd4cdb366a43 Mon Sep 17 00:00:00 2001 From: Ilya Pavlukhin Date: Sun, 20 Sep 2026 12:28:08 +0300 Subject: [PATCH 447/924] ALSA: hda/realtek: Add quirk for Lenovo Yoga Slim 7 Carbon 14ACN6 The bass speakers of the Lenovo Yoga Slim 7 Carbon 14ACN6 (82L0) are driven by two CS35L41 amplifiers, fed over I2S by the ALC287. The codec only clocks the I2S bus while pin 0x17 is enabled, but the BIOS reports the pin as unconnected (0x411111f0). The amplifiers then never see a clock and fail to power up: cs35l41-hda i2c-CLSA0102:00-cs35l41-hda.0: Enable(1) failed: -110 Configure pin 0x17 as a speaker and keep it on DAC 0x02 together with pin 0x14: with DAC 0x06 the path is powered down during stereo playback, which stops the clock again. Then bind the two CS35L41 amplifiers. The PCI SSID of this machine is 17aa:0000, so match on the codec SSID. Link: https://bugzilla.kernel.org/show_bug.cgi?id=215632 Assisted-by: Claude Opus 5 Signed-off-by: Ilya Pavlukhin Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260920092808.17234-5-i.pavluhin@ya.ru --- sound/hda/codecs/realtek/alc269.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 13bc74190752..ebef36c8e9ed 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -3486,6 +3486,28 @@ static void alc287_fixup_legion_16ithg6_speakers(struct hda_codec *cdc, const st comp_generic_fixup(cdc, action, "i2c", "CLSA0101", "-%s:00-cs35l41-hda.%d", 2); } +static void alc287_fixup_yoga_slim7_carbon_speakers(struct hda_codec *cdc, + const struct hda_fixup *fix, int action) +{ + /* + * The bass speakers are driven by two CS35L41 amps fed over I2S. The codec only + * clocks the I2S bus while pin 0x17 is enabled, but the BIOS marks it unconnected. + * Keep the pin on DAC 0x02 with the other speakers: DAC 0x06 gets powered down + * with stereo streams, which stops the clock and the amps fail to power up. + */ + static const struct hda_pintbl pincfgs[] = { + { 0x17, 0x90170121 }, + { } + }; + static const hda_nid_t conn[] = { 0x02 }; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + snd_hda_apply_pincfgs(cdc, pincfgs); + snd_hda_override_conn_list(cdc, 0x17, ARRAY_SIZE(conn), conn); + } + comp_generic_fixup(cdc, action, "i2c", "CLSA0102", "-%s:00-cs35l41-hda.%d", 2); +} + static void alc285_fixup_asus_ga403u(struct hda_codec *cdc, const struct hda_fixup *fix, int action) { /* @@ -4390,6 +4412,7 @@ enum { ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE, ALC295_FIXUP_FRAMEWORK_LAPTOP_LIMIT_INT_MIC_BOOST, ALC287_FIXUP_LEGION_16ITHG6, + ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS, ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK, ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, ALC287_FIXUP_YOGA9_14IMH9_BASS_SPK_PIN, @@ -6778,6 +6801,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc287_fixup_legion_16ithg6_speakers, }, + [ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_yoga_slim7_carbon_speakers, + }, [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { @@ -8316,6 +8343,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), + HDA_CODEC_QUIRK(0x17aa, 0x3856, "Lenovo Yoga Slim 7 Carbon 14ACN6", ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), From af3d225b639c6f9fb8fd5e1e27f11672d56724b6 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 29 Sep 2026 16:45:40 +0800 Subject: [PATCH 448/924] ALSA: sgio2audio: Only free successfully requested IRQs snd_sgio2audio_create() calls snd_sgio2audio_free() when an IRQ request fails. The cleanup helper currently walks the complete IRQ table, including entries whose request_irq() calls have not been attempted yet. free_irq() then receives an IRQ and dev_id pair that was never registered. Pass the number of successfully requested IRQs to the cleanup helper on the request failure path, while retaining the full-table cleanup for teardown after all requests have succeeded. Fixes: 862c2c0a61c5 ("ALSA: ALSA driver for SGI O2 audio board") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260929084540.2065213-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/mips/sgio2audio.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sound/mips/sgio2audio.c b/sound/mips/sgio2audio.c index 497d84cff12f..35648b0b97b9 100644 --- a/sound/mips/sgio2audio.c +++ b/sound/mips/sgio2audio.c @@ -742,7 +742,8 @@ static struct { /* ALSA driver */ -static int snd_sgio2audio_free(struct snd_sgio2audio *chip) +static int snd_sgio2audio_free(struct snd_sgio2audio *chip, + unsigned int irq_count) { int i; @@ -752,7 +753,7 @@ static int snd_sgio2audio_free(struct snd_sgio2audio *chip) writeq(0, &mace->perif.audio.control); /* release IRQ's */ - for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) + for (i = 0; i < irq_count; i++) free_irq(snd_sgio2_isr_table[i].irq, &chip->channel[snd_sgio2_isr_table[i].idx]); @@ -768,7 +769,7 @@ static int snd_sgio2audio_dev_free(struct snd_device *device) { struct snd_sgio2audio *chip = device->device_data; - return snd_sgio2audio_free(chip); + return snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table)); } static const struct snd_device_ops ops = { @@ -819,7 +820,7 @@ static int snd_sgio2audio_create(struct snd_card *card, 0, snd_sgio2_isr_table[i].desc, &chip->channel[snd_sgio2_isr_table[i].idx])) { - snd_sgio2audio_free(chip); + snd_sgio2audio_free(chip, i); printk(KERN_ERR "sgio2audio: cannot allocate irq %d\n", snd_sgio2_isr_table[i].irq); return -EBUSY; @@ -843,13 +844,13 @@ static int snd_sgio2audio_create(struct snd_card *card, /* initialize the AD1843 codec */ err = ad1843_init(&chip->ad1843); if (err < 0) { - snd_sgio2audio_free(chip); + snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table)); return err; } err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); if (err < 0) { - snd_sgio2audio_free(chip); + snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table)); return err; } *rchip = chip; From fa2e77ac5bf81a60864e71fc72196227692d7162 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 29 Sep 2026 16:54:00 +0800 Subject: [PATCH 449/924] m68k: dmasound: Keep the Q40 IRQ handler registered Q40PlayNextFrame() re-registers the sample IRQ after stopping it. This function can be called with dmasound.lock held and from the sample IRQ handler, so free_irq() and request_irq() can sleep or deadlock. A failed re-registration can also leave playback without an IRQ. Register one handler for the lifetime of the driver and select mono or stereo sample handling inside that handler. Call the frame completion path after releasing dmasound.lock so it can refill the queue safely. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260929085400.2068977-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/oss/dmasound/dmasound_q40.c | 65 ++++++++++++++----------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/sound/oss/dmasound/dmasound_q40.c b/sound/oss/dmasound/dmasound_q40.c index e25a78dd1bf2..339311e5d807 100644 --- a/sound/oss/dmasound/dmasound_q40.c +++ b/sound/oss/dmasound/dmasound_q40.c @@ -49,9 +49,8 @@ static int Q40SetFormat(int format); static int Q40SetVolume(int volume); static void Q40PlayNextFrame(int index); static void Q40Play(void); -static irqreturn_t Q40StereoInterrupt(int irq, void *dummy); -static irqreturn_t Q40MonoInterrupt(int irq, void *dummy); -static void Q40Interrupt(void); +static irqreturn_t q40_audio_interrupt(int irq, void *dummy); +static void q40_frame_done(void); /*** Mid level stuff *********************************************************/ @@ -372,8 +371,8 @@ static void Q40Free(void *ptr, unsigned int size) static int __init Q40IrqInit(void) { /* Register interrupt handler. */ - if (request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, - "DMA sound", Q40Interrupt)) + if (request_irq(Q40_IRQ_SAMPLE, q40_audio_interrupt, 0, + "DMA sound", q40_frame_done)) return 0; return(1); @@ -384,7 +383,7 @@ static int __init Q40IrqInit(void) static void Q40IrqCleanUp(void) { master_outb(0,SAMPLE_ENABLE_REG); - free_irq(Q40_IRQ_SAMPLE, Q40Interrupt); + free_irq(Q40_IRQ_SAMPLE, q40_frame_done); } #endif /* MODULE */ @@ -403,7 +402,6 @@ static void Q40PlayNextFrame(int index) u_char *start; u_long size; u_char speed; - int error; /* used by Q40Play() if all doubts whether there really is something * to be played are already wiped out. @@ -420,15 +418,6 @@ static void Q40PlayNextFrame(int index) speed=(dmasound.hard.speed==10000 ? 0 : 1); master_outb( 0,SAMPLE_ENABLE_REG); - free_irq(Q40_IRQ_SAMPLE, Q40Interrupt); - if (dmasound.soft.stereo) - error = request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, - "Q40 sound", Q40Interrupt); - else - error = request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0, - "Q40 sound", Q40Interrupt); - if (error && printk_ratelimit()) - pr_err("Couldn't register sound interrupt\n"); master_outb( speed, SAMPLE_RATE_REG); master_outb( 1,SAMPLE_CLEAR_REG); @@ -456,31 +445,35 @@ static void Q40Play(void) spin_unlock_irqrestore(&dmasound.lock, flags); } -static irqreturn_t Q40StereoInterrupt(int irq, void *dummy) +static irqreturn_t q40_audio_interrupt(int irq, void *dummy) { + bool frame_done = false; + spin_lock(&dmasound.lock); - if (q40_sc>1){ - *DAC_LEFT=*q40_pp++; - *DAC_RIGHT=*q40_pp++; - q40_sc -=2; - master_outb(1,SAMPLE_CLEAR_REG); - }else Q40Interrupt(); + if (dmasound.hard.stereo) { + if (q40_sc > 1) { + *DAC_LEFT = *q40_pp++; + *DAC_RIGHT = *q40_pp++; + q40_sc -= 2; + master_outb(1, SAMPLE_CLEAR_REG); + } else { + frame_done = true; + } + } else if (q40_sc > 0) { + *DAC_LEFT = *q40_pp; + *DAC_RIGHT = *q40_pp++; + q40_sc--; + master_outb(1, SAMPLE_CLEAR_REG); + } else { + frame_done = true; + } spin_unlock(&dmasound.lock); + if (frame_done) + q40_frame_done(); return IRQ_HANDLED; } -static irqreturn_t Q40MonoInterrupt(int irq, void *dummy) -{ - spin_lock(&dmasound.lock); - if (q40_sc>0){ - *DAC_LEFT=*q40_pp; - *DAC_RIGHT=*q40_pp++; - q40_sc --; - master_outb(1,SAMPLE_CLEAR_REG); - }else Q40Interrupt(); - spin_unlock(&dmasound.lock); - return IRQ_HANDLED; -} -static void Q40Interrupt(void) + +static void q40_frame_done(void) { if (!write_sq.active) { /* playing was interrupted and sq_reset() has already cleared From ad63c5b62843895e8c3cd7291ed07cc659df0355 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 29 Sep 2026 14:53:41 +0200 Subject: [PATCH 450/924] ALSA: core: Check shutdown flag at D0 power-state, too The snd_power_ref_and_wait() skips the card->shutdown check when the card is already in D0 state and immediately returns as successful, by assuming the all-green in D0. This assumption makes the code after this sync point behaving as if all power is up and ready, even though actually it might have been already at the disconnected state. Add the missing check of shutdown flag there for the more consistent behavior. Cc: stable@vger.kernel.org Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260929125346.1478631-2-tiwai@suse.de --- sound/core/init.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/core/init.c b/sound/core/init.c index 1bcb6a2e7550..a51b71812fd9 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -1174,12 +1174,12 @@ EXPORT_SYMBOL(snd_card_file_remove); int snd_power_ref_and_wait(struct snd_card *card) { snd_power_ref(card); - if (snd_power_get_state(card) == SNDRV_CTL_POWER_D0) - return 0; - wait_event_cmd(card->power_sleep, - card->shutdown || - snd_power_get_state(card) == SNDRV_CTL_POWER_D0, - snd_power_unref(card), snd_power_ref(card)); + if (snd_power_get_state(card) != SNDRV_CTL_POWER_D0) { + wait_event_cmd(card->power_sleep, + card->shutdown || + snd_power_get_state(card) == SNDRV_CTL_POWER_D0, + snd_power_unref(card), snd_power_ref(card)); + } if (card->shutdown) { snd_power_unref(card); return -ENODEV; From d4febca239be3c91498762dd0535a87d4af1eabc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 29 Sep 2026 14:53:42 +0200 Subject: [PATCH 451/924] ALSA: control: Fix UAF in snd_ctl_elem_add() on card disconnect A use-after-free can be triggered via SNDRV_CTL_IOCTL_ELEM_ADD when a USB audio card is disconnected while an ELEM_ADD ioctl is in flight. The reproducer parks the ioctl thread inside copy_from_user() using userfaultfd, then tears down the USB device. Unlike every other ALSA control _user handler (ELEM_INFO, ELEM_READ, ELEM_WRITE, TLV_*), snd_ctl_elem_add_user() never calls snd_power_ref_and_wait(), so the parked thread holds no power reference. snd_card_disconnect() therefore cannot observe it via snd_power_sync_ref() and proceeds unimpeded: 1. card->shutdown is set to 1 2. device_del(&card->card_dev) drops the kobject reference on the parent USB interface device (card->dev = &intf->dev) 3. The USB core drops its own reference and calls device_release(), freeing the struct usb_interface, including the embedded struct device that card->dev points to When the userfaultfd is resolved and the thread resumes, snd_ctl_elem_add() acquires controls_rwsem without checking card->shutdown and calls __snd_ctl_add_replace(). Because the reproducer pre-registered the same control, the CTL_ADD_EXCLUSIVE path calls dev_err(card->dev, ...) on the freed USB interface: KASAN: slab-use-after-free Read in __dev_printk Add a card->shutdown guard immediately after acquiring controls_rwsem in snd_ctl_elem_add(). At that point card->shutdown is guaranteed to be stable: snd_card_disconnect() sets it before freeing the parent device, and it never transitions back to 0. A thread that acquired the lock before disconnect sees shutdown=0 and holds the write lock through the rest of the operation, preventing concurrent disconnect from proceeding past its own controls_rwsem-less shutdown=1 store (which happened earlier, outside the lock) from racing with dev_err(). Reported-by: Farhad Alemi Cc: stable@vger.kernel.org Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260929125346.1478631-3-tiwai@suse.de --- sound/core/control.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/core/control.c b/sound/core/control.c index 4199342d4ffe..535ceba294ac 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -1802,6 +1802,8 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, alloc_size = compute_user_elem_size(private_size, count); guard(rwsem_write)(&card->controls_rwsem); + if (card->shutdown) + return -ENODEV; if (check_user_elem_overflow(card, alloc_size)) return -ENOMEM; From 0bebef817d4816a95a5d13a00210908f96596552 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Tue, 15 Sep 2026 12:47:35 +0800 Subject: [PATCH 452/924] ASoC: tas2781: Add TAS2573 calibration support This patch adds complete calibration workflow support for TAS2573. Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20260915044735.1305-1-shenghao-ding@ti.com Signed-off-by: Mark Brown --- include/sound/tas2781-dsp.h | 9 + include/sound/tas2781.h | 24 +++ sound/soc/codecs/tas2781-fmwlib.c | 36 +++- sound/soc/codecs/tas2781-i2c.c | 320 +++++++++++++++++++++++++++--- 4 files changed, 361 insertions(+), 28 deletions(-) diff --git a/include/sound/tas2781-dsp.h b/include/sound/tas2781-dsp.h index de087f4ca13f..3380dfd526de 100644 --- a/include/sound/tas2781-dsp.h +++ b/include/sound/tas2781-dsp.h @@ -146,6 +146,7 @@ struct tasdevice_fw { struct tasdevice_calibration *calibrations; struct fct_param_address fct_par_addr; struct device *dev; + int calibration_config_id; }; enum tasdevice_fw_state { @@ -210,6 +211,14 @@ struct tasdevice_rca { * capture. */ int capture_profile_id; + /* + * Primarily designed for speaker calibration scenarios with special + * requirements. For regular use cases, when the default value -1 is + * set, calibration will directly reuse the current playback + * configuration; non-negative values can be customized for special + * calibration demands. + */ + int calibration_profile_id; /* * Since version 0x105, the keyword 'init' was introduced into the * profile, which is used for chip initialization, particularly to diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index b763da613769..7367c76959aa 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -91,6 +91,30 @@ /* prm_TE_1_Beta1 */ #define TAS2563_TE_DT_REG TASDEVICE_REG(0x00, 0x0f, 0x70) +#define TAS2573_FCT_INT_LATCH TASDEVICE_REG(0x00, 0x00, 0x67) +/* YM57 */ +#define TAS2573_FCT_OUTPUT_R0 TASDEVICE_REG(0x00, 0x65, 0x74) +/* YM58 */ +#define TAS2573_FCT_OUTPUT_R0_LOW TASDEVICE_REG(0x00, 0x65, 0x78) +/* YM59 */ +#define TAS2573_FCT_OUTPUT_INV_R0 TASDEVICE_REG(0x00, 0x65, 0x7C) +/* YM61 */ +#define TAS2573_FCT_OUTPUT_POWERTOT TASDEVICE_REG(0x00, 0x66, 0x0C) +/* YM64 */ +#define TAS2573_FCT_OUTPUT_F0 TASDEVICE_REG(0x00, 0x65, 0x18) +/* YM147 */ +#define TAS2573_FCT_STATUS_CTRL TASDEVICE_REG(0x00, 0x68, 0x74) +/* YM148 */ +#define TAS2573_FCT_STATUS_BINNING TASDEVICE_REG(0x00, 0x68, 0x78) +/* YM664 */ +#define TAS2573_RE_OUT TASDEVICE_REG(0x00, 0x7A, 0x18) +/* YM952 */ +#define TAS2573_SILENCE_DETECTED TASDEVICE_REG(0x00, 0x83, 0x60) +/* YM954 */ +#define TAS2573_OPEN_CIRCUIT TASDEVICE_REG(0x00, 0x83, 0x68) +/* YM955 */ +#define TAS2573_SHORTCKT TASDEVICE_REG(0x00, 0x83, 0x6C) + #define TAS2781_PRM_INT_MASK_REG TASDEVICE_REG(0x00, 0x00, 0x3b) #define TAS2781_PRM_CLK_CFG_REG TASDEVICE_REG(0x00, 0x00, 0x5c) #define TAS2781_PRM_RSVD_REG TASDEVICE_REG(0x00, 0x01, 0x19) diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c index dc1c552206f8..e8ceeb027e1b 100644 --- a/sound/soc/codecs/tas2781-fmwlib.c +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -199,15 +199,22 @@ static struct tasdevice_config_info *tasdevice_add_config( dev_err(tas_priv->dev, "add conf: Out of boundary\n"); goto out; } - /* If in the RCA bin file are several profiles with the - * keyword "init", init_profile_id only store the last - * init profile id. + /* + * If in the RCA bin file are several profiles with the + * keyword "init"/"calib", init_profile_id only store the last + * init profile id, and same as calibration_profile_id. */ if (strnstr(&config_data[config_offset], "init", 64)) { tas_priv->rcabin.init_profile_id = tas_priv->rcabin.ncfgs - 1; dev_dbg(tas_priv->dev, "%s: init profile id = %d\n", __func__, tas_priv->rcabin.init_profile_id); + } else if (strnstr(&config_data[config_offset], "calib", 64)) { + tas_priv->rcabin.calibration_profile_id = + tas_priv->rcabin.ncfgs - 1; + dev_dbg(tas_priv->dev, "%s: calib profile id = %d\n", + __func__, + tas_priv->rcabin.calibration_profile_id); } config_offset += 64; } @@ -314,6 +321,7 @@ int tasdevice_rca_parser(void *context, const struct firmware *fmw) rca = &(tas_priv->rcabin); /* Initialize to none */ rca->init_profile_id = -1; + rca->calibration_profile_id = -1; fw_hdr = &(rca->fw_hdr); if (!fmw || !fmw->data) { dev_err(tas_priv->dev, "Failed to read %s\n", @@ -632,6 +640,17 @@ static int fw_parse_configuration_data_kernel( goto out; } memcpy(config->name, &data[offset], 64); + + /* + * If in the coef bin file are several configs with the + * keyword "calib", calibration_config_id only store the last + * calibration profile id. + */ + if (strnstr(config->name, "calib", 64)) { + tas_fmw->calibration_config_id = i; + dev_dbg(tas_priv->dev, "%s: calib cofig = %d\n", + __func__, tas_fmw->calibration_config_id); + } /*skip extra 16 bytes*/ offset += 80; @@ -1412,6 +1431,17 @@ static int fw_parse_configuration_data( memcpy(config->name, &data[offset], 64); offset += 64; + /* + * If in the coef bin file are several configs with the + * keyword "calib", calibration_config_id only store the last + * calibration profile id. + */ + if (strnstr(config->name, "calib", 64)) { + tas_fmw->calibration_config_id = i; + dev_dbg(tas_priv->dev, "%s: calib cofig id = %d\n", + __func__, tas_fmw->calibration_config_id); + } + n = tasdevice_fw_strnlen(fmw, offset); if (n < 0) { dev_err(tas_priv->dev, "Description err\n"); diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c index 6f349f88f194..995bfd52f5fc 100644 --- a/sound/soc/codecs/tas2781-i2c.c +++ b/sound/soc/codecs/tas2781-i2c.c @@ -613,6 +613,39 @@ static void tas2563_calib_stop_put(struct tasdevice_priv *tas_priv) } } +static void tas2573_calib_stop_put(struct tasdevice_priv *tas_priv) +{ + int cal_prof_id = tas_priv->rcabin.calibration_profile_id; + + if (cal_prof_id >= 0) + tasdevice_select_cfg_blk(tas_priv, cal_prof_id, + TASDEVICE_BIN_BLK_PRE_SHUTDOWN); +} + +static int tas2573_calib_start_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(comp); + int cal_prof_id = tas_priv->rcabin.calibration_profile_id; + int cal_conf_id = tas_priv->fmw->calibration_config_id; + + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->chip_id != TAS2573) { + WARN_ON_ONCE(1); + return -ENODEV; + } + + if (cal_prof_id >= 0) + tasdevice_select_cfg_blk(tas_priv, cal_prof_id, + TASDEVICE_BIN_BLK_PRE_POWER_UP); + + tasdevice_select_tuningprm_cfg(tas_priv, tas_priv->cur_prog, + cal_conf_id, cal_prof_id); + + return 1; +} + static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -621,10 +654,22 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, int i; guard(mutex)(&priv->codec_lock); - if (priv->chip_id == TAS2563) + + switch (priv->chip_id) { + case TAS2563: tas2563_calib_stop_put(priv); - else + break; + case TAS2573: + tas2573_calib_stop_put(priv); + break; + case TAS2781: tas2781_calib_stop_put(priv); + break; + default: + dev_err(priv->dev, "%s: Chip(%d) unsupports calibration\n", + __func__, priv->chip_id); + return -1; + } /* * Set reloading-firmware flag after calibration, the flag will work @@ -983,6 +1028,13 @@ static const struct snd_kcontrol_new tas2563_cali_controls[] = { tasdev_nop_get, tas2563_calib_start_put), }; +static const struct snd_kcontrol_new tas2573_cali_controls[] = { + SOC_SINGLE_EXT("Calibration Start", SND_SOC_NOPM, 0, 1, 0, + tasdev_nop_get, tas2573_calib_start_put), + SOC_SINGLE_EXT("Calibration Stop", SND_SOC_NOPM, 0, 1, 0, + tasdev_nop_get, tasdev_calib_stop_put), +}; + static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -1493,6 +1545,182 @@ static int create_tas2781_cali_start_ktrl(struct tasdevice_priv return 0; } +static int tas2573_re_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *priv = snd_soc_component_get_drvdata(comp); + unsigned char *dst = ucontrol->value.bytes.data; + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *) kcontrol->private_value; + unsigned int i = 0; + unsigned int j; + int rc; + + guard(mutex)(&priv->codec_lock); + dst[i++] = bytes_ext->max; + dst[i++] = 'r'; + + dst[i++] = TASDEVICE_BOOK_ID(TAS2573_RE_OUT); + dst[i++] = TASDEVICE_PAGE_ID(TAS2573_RE_OUT); + dst[i++] = TASDEVICE_PAGE_REG(TAS2573_RE_OUT); + + dst[i++] = 'D'; + + for (j = 0; j < priv->ndev; j++) { + dst[i++] = j; + + rc = tasdevice_dev_bulk_read(priv, j, TAS2573_RE_OUT, + &dst[i], 4); + if (rc < 0) + dev_err(priv->dev, "chn %d bulk_rd err = %d\n", j, rc); + + i += 4; + } + + return 0; +} + +static int tas2573_calib_status_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *priv = snd_soc_component_get_drvdata(comp); + unsigned char *dst = ucontrol->value.bytes.data; + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *) kcontrol->private_value; + unsigned int fct_status_regs[] = { + TAS2573_FCT_INT_LATCH, + TAS2573_FCT_STATUS_CTRL, + TAS2573_FCT_STATUS_BINNING, + TAS2573_SILENCE_DETECTED, + TAS2573_OPEN_CIRCUIT, + TAS2573_SHORTCKT, + TAS2573_FCT_OUTPUT_R0, + TAS2573_FCT_OUTPUT_R0_LOW, + TAS2573_FCT_OUTPUT_INV_R0, + TAS2573_FCT_OUTPUT_POWERTOT, + TAS2573_FCT_OUTPUT_F0, + TAS2573_RE_OUT, + }; + unsigned int j, k, val; + unsigned int i = 0; + int rc; + + guard(mutex)(&priv->codec_lock); + dst[i++] = bytes_ext->max; + dst[i++] = 'r'; + + for (j = 0; j < ARRAY_SIZE(fct_status_regs); j++) { + dst[i++] = TASDEVICE_BOOK_ID(fct_status_regs[j]); + dst[i++] = TASDEVICE_PAGE_ID(fct_status_regs[j]); + dst[i++] = TASDEVICE_PAGE_REG(fct_status_regs[j]); + } + dst[i++] = 'D'; + + for (j = 0; j < priv->ndev; j++) { + dst[i++] = j; + dst[i++] = 0; + dst[i++] = 0; + dst[i++] = 0; + + rc = tasdevice_dev_read(priv, j, fct_status_regs[0], &val); + if (rc < 0) + dev_err(priv->dev, + "chn %d fct_status_regs[0] rd err = %d\n", + j, rc); + else + dst[i++] = val; + + for (k = 1; k < ARRAY_SIZE(fct_status_regs); k++, i += 4) { + rc = tasdevice_dev_bulk_read(priv, j, + fct_status_regs[k], &dst[i], 4); + if (rc < 0) { + dev_err(priv->dev, + "chn %d regs[%u] bulk_rd err = %d\n", + j, k, rc); + } + } + + } + + return 0; +} + +static int create_tas2573_cali_status_ktrl(struct tasdevice_priv + *priv, struct snd_kcontrol_new *cali_ctrl) +{ + struct soc_bytes_ext *ext_cali_start; + char *cali_start_name; + + ext_cali_start = devm_kzalloc(priv->dev, + sizeof(*ext_cali_start), GFP_KERNEL); + if (!ext_cali_start) + return -ENOMEM; + + cali_start_name = devm_kstrdup(priv->dev, + "Calibration Status", GFP_KERNEL); + if (!cali_start_name) + return -ENOMEM; + /* + * package structure for tas2573 fct status: + * Pkg len (1 byte) + * Reg id (1 byte, constant 'r') + * book, page, register for fct status (total 36 bytes) + * Data Start Flag (1 byte, constant 'D') + * for (i = 0; i < Device-Sum; i++) { + * Device #i index_info (1 byte) + * Sine gain for Device #i (48 bytes) + * } + */ + ext_cali_start->max = 39 + priv->ndev * 49; + cali_ctrl->name = cali_start_name; + cali_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + cali_ctrl->info = snd_soc_bytes_info_ext; + cali_ctrl->put = NULL; + cali_ctrl->get = tas2573_calib_status_get; + cali_ctrl->private_value = (unsigned long)ext_cali_start; + + return 0; +} + +static int create_tas2573_cali_re_ktrl(struct tasdevice_priv + *priv, struct snd_kcontrol_new *cali_ctrl) +{ + struct soc_bytes_ext *ext_cali_start; + char *cali_start_name; + + ext_cali_start = devm_kzalloc(priv->dev, + sizeof(*ext_cali_start), GFP_KERNEL); + if (!ext_cali_start) + return -ENOMEM; + + cali_start_name = devm_kstrdup(priv->dev, + "Real-time Speaker Impedance", GFP_KERNEL); + if (!cali_start_name) + return -ENOMEM; + /* + * package structure for tas2573 real-time spk impedance: + * Pkg len (1 byte) + * Reg id (1 byte, constant 'r') + * book, page, register for fct status (total 3 bytes) + * Data Start Flag (1 byte, constant 'D') + * for (i = 0; i < Device-Sum; i++) { + * Device #i index_info (1 byte) + * Sine gain for Device #i (4 bytes) + * } + */ + ext_cali_start->max = 6 + priv->ndev * 5; + cali_ctrl->name = cali_start_name; + cali_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + cali_ctrl->info = snd_soc_bytes_info_ext; + cali_ctrl->put = NULL; + cali_ctrl->get = tas2573_re_get; + cali_ctrl->private_value = (unsigned long)ext_cali_start; + + return 0; +} + static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) { struct calidata *cali_data = &priv->cali_data; @@ -1504,17 +1732,43 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) char *cali_name; int rc, i; - rc = snd_soc_add_component_controls(priv->codec, - tasdevice_cali_controls, ARRAY_SIZE(tasdevice_cali_controls)); - if (rc < 0) { - dev_err(priv->dev, "%s: Add cali controls err rc = %d", - __func__, rc); - return rc; + switch (priv->chip_id) { + case TAS2563: { + rc = snd_soc_add_component_controls(priv->codec, + tasdevice_cali_controls, + ARRAY_SIZE(tasdevice_cali_controls)); + if (rc < 0) { + dev_err(priv->dev, "%s: Add cali controls err rc = %d", + __func__, rc); + return rc; + } + cali_ctrls = (struct snd_kcontrol_new *)tas2563_cali_controls; + nctrls = ARRAY_SIZE(tas2563_cali_controls); + for (i = 0; i < priv->ndev; i++) { + tasdev[i].cali_data_backup = + kmemdup(tas2563_cali_start_reg, + sizeof(tas2563_cali_start_reg), GFP_KERNEL); + if (!tasdev[i].cali_data_backup) + return -ENOMEM; + } } - - if (priv->chip_id == TAS2781) { + break; + case TAS2573: + cali_ctrls = (struct snd_kcontrol_new *)tas2573_cali_controls; + nctrls = ARRAY_SIZE(tas2573_cali_controls); + break; + case TAS2781: { struct fct_param_address *t = &(fmw->fct_par_addr); + rc = snd_soc_add_component_controls(priv->codec, + tasdevice_cali_controls, + ARRAY_SIZE(tasdevice_cali_controls)); + if (rc < 0) { + dev_err(priv->dev, "%s: Add cali controls err rc = %d", + __func__, rc); + return rc; + } + cali_ctrls = (struct snd_kcontrol_new *)tas2781_cali_controls; nctrls = ARRAY_SIZE(tas2781_cali_controls); for (i = 0; i < priv->ndev; i++) { @@ -1533,16 +1787,12 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) } } } - } else { - cali_ctrls = (struct snd_kcontrol_new *)tas2563_cali_controls; - nctrls = ARRAY_SIZE(tas2563_cali_controls); - for (i = 0; i < priv->ndev; i++) { - tasdev[i].cali_data_backup = - kmemdup(tas2563_cali_start_reg, - sizeof(tas2563_cali_start_reg), GFP_KERNEL); - if (!tasdev[i].cali_data_backup) - return -ENOMEM; - } + } + break; + default: + dev_err(priv->dev, "%s: Wrong chip id = %d", __func__, + priv->chip_id); + return -EINVAL; } rc = snd_soc_add_component_controls(priv->codec, cali_ctrls, nctrls); @@ -1554,10 +1804,17 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) /* index for cali_ctrls */ i = 0; - if (priv->chip_id == TAS2781) + switch (priv->chip_id) { + case TAS2573: + nctrls = 3; + break; + case TAS2781: nctrls = 2; - else + break; + default: nctrls = 1; + break; + } /* * Alloc kcontrol via devm_kzalloc(), which don't manually @@ -1610,12 +1867,24 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) * it, for the default value is 0, which means the first device. */ cali_data->data[0] = 0xff; - if (priv->chip_id == TAS2781) { + + switch (priv->chip_id) { + case TAS2573: + rc = create_tas2573_cali_status_ktrl(priv, &cali_ctrls[i]); + if (rc != 0) + return rc; + i++; + rc = create_tas2573_cali_re_ktrl(priv, &cali_ctrls[i]); + if (rc != 0) + return rc; + i++; + break; + case TAS2781: rc = create_tas2781_cali_start_ktrl(priv, &cali_ctrls[i]); if (rc != 0) return rc; i++; - + break; } return snd_soc_add_component_controls(priv->codec, cali_ctrls, @@ -1822,7 +2091,8 @@ static void tasdevice_fw_ready(const struct firmware *fmw, tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK; /* There is no calibration required for TAS58XX. */ - if (tas_priv->chip_id == TAS2563 || tas_priv->chip_id == TAS2781) { + if (tas_priv->chip_id == TAS2563 || tas_priv->chip_id == TAS2573 || + tas_priv->chip_id == TAS2781) { ret = tasdevice_create_cali_ctrls(tas_priv); if (ret) { dev_err(tas_priv->dev, "cali controls error\n"); From 995b96380ba5d98131acc162608da3538e1395d8 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 29 Sep 2026 17:59:20 +0800 Subject: [PATCH 453/924] ASoC: pcm6240: do not free an unrequested IRQ The driver obtains an IRQ number from device tree but never requests an IRQ handler for the PCM6240 device. Removing the device must not pass that number to free_irq(). Leave IRQ ownership to a matching request path if one is added later. Fixes: 1324eafd37aa ("ASoc: PCM6240: Create PCM6240 Family driver code") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260929095920.2092147-1-runyu.xiao@seu.edu.cn Signed-off-by: Mark Brown --- sound/soc/codecs/pcm6240.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c index 4b5569a5b062..db70a33d14b8 100644 --- a/sound/soc/codecs/pcm6240.c +++ b/sound/soc/codecs/pcm6240.c @@ -2019,8 +2019,6 @@ static const struct regmap_config pcmdevice_i2c_regmap = { static void pcmdevice_remove(struct pcmdevice_priv *pcm_dev) { - if (pcm_dev->irq) - free_irq(pcm_dev->irq, pcm_dev); mutex_destroy(&pcm_dev->codec_lock); } From 22185ffd7b31885d03fdeb20055a8707779a70f9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 30 Sep 2026 17:53:50 +0200 Subject: [PATCH 454/924] ALSA: usb-audio: Check mixer matrix size for UAC2/3 at parsing The matrix of input/output channels specified in a UAC2/3 mixer unit must fit to the upper limit 256. Add a sanity check and returns an error if an invalid size is detected. Link: https://lore.kernel.org/d46fcac6-bd7e-4fc4-95e1-4e8d39f92ad3@zipdox.net Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260930155356.348608-2-tiwai@suse.de --- sound/usb/mixer.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 33a6a1281410..ddfd7e01a3ef 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -2436,6 +2436,16 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, num_outs = err; input_pins = desc->bNrInPins; + if (state->mixer->protocol == UAC_VERSION_2 || + state->mixer->protocol == UAC_VERSION_3) { + if (input_pins * num_outs > 256) { + usb_audio_err(state->chip, + "invalid channels for MIXER UNIT %d: input=%d, output=%d\n", + unitid, input_pins, num_outs); + return -EINVAL; + } + } + num_ins = 0; ich = 0; for (pin = 0; pin < input_pins; pin++) { From 0458d97859840506c3688a963dcd4f72b4a23083 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 30 Sep 2026 17:53:51 +0200 Subject: [PATCH 455/924] ALSA: usb-audio: Fix UAC2 mixer unit request handling For a request for a Mixer Unit on UAC2 (also UAC3), the wValue is different from UAC1 and an incompatible value must be passed. Namely, UAC1 takes a word consisting of 1-based input channel in the high byte and 1-based output channel in the low byte. Meanwhile, UAC2/3 takes UAC2_MU_MIXER in the high byte and a MCN (0-based bit position of input/output channels) in the low byte. The current driver implementation blindly assumes the UAC1 way, hence it would cause a firmware error. This patch attempts to implement the conversion to UAC2 MCN at get_ctl_value_v2() and snd_usb_mixer_set_ctl_value() for mixer units. At the points above, the old wValue containing ICN and OCN is converted to the corresponding MCN, and it's used as the proper wValue. Reported-by: Zipdox Closes: https://lore.kernel.org/d46fcac6-bd7e-4fc4-95e1-4e8d39f92ad3@zipdox.net Fixes: 23caaf19b11e ("ALSA: usb-mixer: Add support for Audio Class v2.0") Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260930155356.348608-3-tiwai@suse.de --- sound/usb/mixer.c | 23 +++++++++++++++++++++++ sound/usb/mixer.h | 2 ++ 2 files changed, 25 insertions(+) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index ddfd7e01a3ef..a8bdd1696a20 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -335,6 +335,17 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, return -EINVAL; } +/* convert the given UAC1 wValue (ICN|OCN) to UAC2 MCN */ +static unsigned char to_mcn(const struct usb_mixer_elem_info *cval, + unsigned int validx) +{ + unsigned char m = (validx >> 8) & 0xff; /* 1-based input channel */ + unsigned char v = validx & 0xff; /* 1-based output channel */ + + /* num_inputs * num_outputs is guaranteed to be < 256 */ + return (m - 1) * cval->num_outputs + (v - 1); +} + static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) { @@ -347,6 +358,10 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, val_size = uac2_ctl_value_size(cval->val_type); + /* correct wValue for UAC2 mixer control with MCN */ + if (cval->v2_mixer) + validx = (UAC2_MU_MIXER << 8) | to_mcn(cval, validx); + if (request == UAC_GET_CUR) { bRequest = UAC2_CS_CUR; size = val_size; @@ -478,6 +493,10 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, } request = UAC2_CS_CUR; + + /* correct wValue for UAC2 mixer control with MCN */ + if (cval->v2_mixer) + validx = (UAC2_MU_MIXER << 8) | to_mcn(cval, validx); } value_set = convert_bytes_value(cval, value_set); @@ -2345,6 +2364,10 @@ static void build_mixer_unit_ctl(struct mixer_build *state, snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid); cval->control = in_ch + 1; /* based on 1 */ + if (state->mixer->protocol == UAC_VERSION_2 || + state->mixer->protocol == UAC_VERSION_3) + cval->v2_mixer = true; + cval->num_outputs = num_outs; cval->val_type = USB_MIXER_S16; for (i = 0; i < num_outs; i++) { __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol); diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 037b446d8b6f..cf45c39cbccc 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -97,6 +97,8 @@ struct usb_mixer_elem_info { u8 initialized; u8 min_mute; u8 get_cur_broken; + u8 num_outputs; + bool v2_mixer; void *private_data; }; From 8b4d98aad61c3a9a5e01e0840470f9d7f32bc339 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 30 Sep 2026 17:53:52 +0200 Subject: [PATCH 456/924] ALSA: usb-audio: Optimize min/max/res parse for UAC2 UAC2 feature and mixer units provide the mixer information about minimum and max channels as well as the resolution in a single UAC2_CS_RANGE request, but the current code tries to extract each of them in an old way of UAC1. This patch refactors the code to optimize the range info extraction for UAC2. Now the code for obtaining min/max/res info is done in get_ctl_range() function. For UAC1, this will call UAC_GET_MIN, UAC_GET_MAX and UAC_GET_RES requests, while it calls a single UAC2_CS_RANGE for UAC2/3. Link: https://lore.kernel.org/d46fcac6-bd7e-4fc4-95e1-4e8d39f92ad3@zipdox.net Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260930155356.348608-4-tiwai@suse.de --- sound/usb/mixer.c | 220 ++++++++++++++++++++++++++-------------------- 1 file changed, 126 insertions(+), 94 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index a8bdd1696a20..b6e22244e03a 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -304,8 +304,9 @@ static inline int mixer_ctrl_intf(struct usb_mixer_interface *mixer) return get_iface_desc(mixer->hostif)->bInterfaceNumber; } -static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, - int validx, int *value_ret) +/* send a request for UAC1 feature & mixer unit */ +static int request_get_ctl_v1(struct usb_mixer_elem_info *cval, + u8 request, int validx, int *value_ret) { struct snd_usb_audio *chip = cval->head.mixer->chip; unsigned char buf[2]; @@ -317,6 +318,8 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, if (pm.err < 0) return -EIO; + validx += cval->idx_off; + while (timeout-- > 0) { idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request, @@ -346,98 +349,73 @@ static unsigned char to_mcn(const struct usb_mixer_elem_info *cval, return (m - 1) * cval->num_outputs + (v - 1); } -static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, - int validx, int *value_ret) +/* send a request for UAC2 feature & mixer unit */ +static int request_get_ctl_v2(struct usb_mixer_elem_info *cval, + u8 request, int validx, unsigned char *buf, + int size) { struct snd_usb_audio *chip = cval->head.mixer->chip; - /* enough space for one range */ - unsigned char buf[sizeof(__u16) + 3 * sizeof(__u32)]; - unsigned char *val; - int idx = 0, ret, val_size, size; - __u8 bRequest; + int idx, ret; - val_size = uac2_ctl_value_size(cval->val_type); + CLASS(snd_usb_lock, pm)(chip); + if (pm.err) + return -EIO; + + validx += cval->idx_off; /* correct wValue for UAC2 mixer control with MCN */ if (cval->v2_mixer) validx = (UAC2_MU_MIXER << 8) | to_mcn(cval, validx); - if (request == UAC_GET_CUR) { - bRequest = UAC2_CS_CUR; - size = val_size; - } else { - bRequest = UAC2_CS_RANGE; - size = sizeof(__u16) + 3 * val_size; - } - - memset(buf, 0, sizeof(buf)); - - { - CLASS(snd_usb_lock, pm)(chip); - if (pm.err) - return -EIO; - - idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); - ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, - validx, idx, buf, size); - } - - if (ret < 0) { + memset(buf, 0, size); + idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + request, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, idx, buf, size); + if (ret < 0) usb_audio_dbg(chip, "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", request, validx, idx, cval->val_type); + + return ret; +} + +/* read the current value for UAC2 */ +static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, + int validx, int *value_ret) +{ + /* enough space for one value */ + unsigned char buf[sizeof(__u32)]; + int ret, val_size; + + val_size = uac2_ctl_value_size(cval->val_type); + + ret = request_get_ctl_v2(cval, UAC2_CS_CUR, validx, buf, val_size); + if (ret < 0) return ret; - } - - /* FIXME: how should we handle multiple triplets here? */ - - switch (request) { - case UAC_GET_CUR: - val = buf; - break; - case UAC_GET_MIN: - val = buf + sizeof(__u16); - break; - case UAC_GET_MAX: - val = buf + sizeof(__u16) + val_size; - break; - case UAC_GET_RES: - val = buf + sizeof(__u16) + val_size * 2; - break; - default: - return -EINVAL; - } *value_ret = convert_signed_value(cval, - snd_usb_combine_bytes(val, val_size)); - + snd_usb_combine_bytes(buf, val_size)); return 0; } -static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, - int validx, int *value_ret) -{ - validx += cval->idx_off; - - return (cval->head.mixer->protocol == UAC_VERSION_1) ? - get_ctl_value_v1(cval, request, validx, value_ret) : - get_ctl_value_v2(cval, request, validx, value_ret); -} - +/* read the current value */ static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, - int validx, int *value) + int validx, int *value_ret) { - return get_ctl_value(cval, UAC_GET_CUR, validx, value); + return (cval->head.mixer->protocol == UAC_VERSION_1) ? + request_get_ctl_v1(cval, UAC_GET_CUR, validx, value_ret) : + get_ctl_value_v2(cval, validx, value_ret); } /* channel = 0: master, 1 = first channel */ static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, int channel, int *value) { - return get_ctl_value(cval, UAC_GET_CUR, - (cval->control << 8) | channel, - value); + return get_cur_ctl_value(cval, + (cval->control << 8) | channel, + value); } int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, @@ -467,6 +445,81 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, return 0; } +/* extract the mixer min/max/res info from UAC1 feature / mixer unit */ +static int get_ctl_range_v1(struct usb_mixer_elem_info *cval, int validx) +{ + int last_valid_res; + + if (request_get_ctl_v1(cval, UAC_GET_MAX, validx, &cval->max) < 0 || + request_get_ctl_v1(cval, UAC_GET_MIN, validx, &cval->min) < 0) { + usb_audio_err(cval->head.mixer->chip, + "%d:%d: cannot get min/max values for control %d (id %d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->control, cval->head.id); + return -EAGAIN; /* handled by the caller later again */ + } + + if (request_get_ctl_v1(cval, UAC_GET_RES, validx, &cval->res) < 0) { + cval->res = 1; + return 0; + } + + last_valid_res = cval->res; + while (cval->res > 1) { + if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, + validx, cval->res / 2) < 0) + break; + cval->res /= 2; + } + if (request_get_ctl_v1(cval, UAC_GET_RES, validx, &cval->res) < 0) + cval->res = last_valid_res; + + return 0; +} + +/* extract the mixer min/max/res info from UAC2 feature / mixer unit */ +static int get_ctl_range_v2(struct usb_mixer_elem_info *cval, int validx) +{ + /* enough space for one range */ + unsigned char buf[sizeof(__u16) + 3 * sizeof(__u32)]; + unsigned char *val; + int val_size, size; + + val_size = uac2_ctl_value_size(cval->val_type); + size = sizeof(__u16) + 3 * val_size; + + if (request_get_ctl_v2(cval, UAC2_CS_RANGE, validx, buf, size) < 0) { + usb_audio_err(cval->head.mixer->chip, + "%d:%d: cannot get RANGE values for control %d (id %d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->control, cval->head.id); + return -EAGAIN; /* handled by the caller later again */ + } + + /* FIXME: how should we handle multiple triplets here? */ + val = buf + 2; + cval->min = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size)); + val += val_size; + cval->max = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size)); + val += val_size; + cval->res = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size)); + return 0; +} + +/* extract the mixer min/max/res info */ +static int get_ctl_range(struct usb_mixer_elem_info *cval, int validx) +{ + switch (cval->head.mixer->protocol) { + case UAC_VERSION_1: + return get_ctl_range_v1(cval, validx); + case UAC_VERSION_2: + case UAC_VERSION_3: + return get_ctl_range_v2(cval, validx); + default: + return -EINVAL; + } +} + /* * set a mixer value */ @@ -1363,32 +1416,11 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, break; } } - if (get_ctl_value(cval, UAC_GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 || - get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) { - usb_audio_err(cval->head.mixer->chip, - "%d:%d: cannot get min/max values for control %d (id %d)\n", - cval->head.id, mixer_ctrl_intf(cval->head.mixer), - cval->control, cval->head.id); - return -EAGAIN; - } - if (get_ctl_value(cval, UAC_GET_RES, - (cval->control << 8) | minchn, - &cval->res) < 0) { - cval->res = 1; - } else if (cval->head.mixer->protocol == UAC_VERSION_1) { - int last_valid_res = cval->res; - while (cval->res > 1) { - if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, - (cval->control << 8) | minchn, - cval->res / 2) < 0) - break; - cval->res /= 2; - } - if (get_ctl_value(cval, UAC_GET_RES, - (cval->control << 8) | minchn, &cval->res) < 0) - cval->res = last_valid_res; - } + ret = get_ctl_range(cval, (cval->control << 8) | minchn); + if (ret < 0) + return ret; + if (cval->res == 0) cval->res = 1; From 5367243a37f7f74d5f595ceac780508138f1e3b0 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Thu, 1 Oct 2026 10:40:49 +0800 Subject: [PATCH 457/924] ASoC: tas2781: Refactor register error log with unified macro Add reusable TAS_REG_ERR_LOG macro in tas2781 header, replace all duplicated error logging code in register read/write/bulk ops and update_bits functions. Add channel ID to error log for better debuggability. Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20261001024049.93-2-shenghao-ding@ti.com Signed-off-by: Mark Brown --- include/sound/tas2781.h | 7 +++++++ sound/soc/codecs/tas2781-comlib-i2c.c | 5 +---- sound/soc/codecs/tas2781-comlib.c | 20 ++++---------------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index 7367c76959aa..f4275226366d 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -49,6 +49,13 @@ #define TASDEVICE_REG(book, page, reg) (((book * 256 * 128) + \ (page * 128)) + reg) +#define TAS_REG_ERR_LOG(dev, chn, reg, ret) \ + dev_err((dev), "%s, C%uBx%02xPx%02xRx%02x E=%d\n", \ + __func__, chn, TASDEVICE_BOOK_ID(reg), \ + TASDEVICE_PAGE_ID(reg), \ + TASDEVICE_PAGE_REG(reg), \ + (ret)) + /* Software Reset, compatble with new device (TAS5825). */ #define TASDEVICE_REG_SWRESET TASDEVICE_REG(0x0, 0x0, 0x01) #define TASDEVICE_REG_SWRESET_RESET BIT(0) diff --git a/sound/soc/codecs/tas2781-comlib-i2c.c b/sound/soc/codecs/tas2781-comlib-i2c.c index dd86fc90a7d0..46777be765ef 100644 --- a/sound/soc/codecs/tas2781-comlib-i2c.c +++ b/sound/soc/codecs/tas2781-comlib-i2c.c @@ -131,10 +131,7 @@ int tasdevice_dev_update_bits( ret = regmap_update_bits(map, TASDEVICE_PGRG(reg), mask, value); if (ret < 0) - dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", - __func__, TASDEVICE_BOOK_ID(reg), - TASDEVICE_PAGE_ID(reg), - TASDEVICE_PAGE_REG(reg), ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else { dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, chn); diff --git a/sound/soc/codecs/tas2781-comlib.c b/sound/soc/codecs/tas2781-comlib.c index e4c8a04a9187..58e19d6d1f75 100644 --- a/sound/soc/codecs/tas2781-comlib.c +++ b/sound/soc/codecs/tas2781-comlib.c @@ -34,10 +34,7 @@ int tasdevice_dev_read(struct tasdevice_priv *tas_priv, ret = regmap_read(map, TASDEVICE_PGRG(reg), val); if (ret < 0) - dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", - __func__, TASDEVICE_BOOK_ID(reg), - TASDEVICE_PAGE_ID(reg), - TASDEVICE_PAGE_REG(reg), ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, @@ -65,10 +62,7 @@ int tasdevice_dev_bulk_read(struct tasdevice_priv *tas_priv, ret = regmap_bulk_read(map, TASDEVICE_PGRG(reg), data, len); if (ret < 0) - dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", - __func__, TASDEVICE_BOOK_ID(reg), - TASDEVICE_PAGE_ID(reg), - TASDEVICE_PAGE_REG(reg), ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, chn); @@ -94,10 +88,7 @@ int tasdevice_dev_write(struct tasdevice_priv *tas_priv, ret = regmap_write(map, TASDEVICE_PGRG(reg), value); if (ret < 0) - dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", - __func__, TASDEVICE_BOOK_ID(reg), - TASDEVICE_PAGE_ID(reg), - TASDEVICE_PAGE_REG(reg), ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, @@ -127,10 +118,7 @@ int tasdevice_dev_bulk_write( ret = regmap_bulk_write(map, TASDEVICE_PGRG(reg), data, len); if (ret < 0) - dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", - __func__, TASDEVICE_BOOK_ID(reg), - TASDEVICE_PAGE_ID(reg), - TASDEVICE_PAGE_REG(reg), ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, From 923ff8155ef6e83b73b852300d50f2053b969552 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 1 Oct 2026 17:02:19 +0800 Subject: [PATCH 458/924] ASoC: rt712: avoid duplicate reset in io_init The codec should be reset only once during io_init, regardless of whether the system performs a warm or cold reboot. Fixes: 4c9854ad3cdc ("ASoC: rt712-sdca: reset codec at io_init to fix silent headphone") Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20261001090219.797880-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c index a98537e23c21..20961e5760c6 100644 --- a/sound/soc/codecs/rt712-sdca.c +++ b/sound/soc/codecs/rt712-sdca.c @@ -2088,7 +2088,8 @@ int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave) pm_runtime_get_noresume(&slave->dev); - rt712_sdca_reset(rt712); + if (!rt712->first_hw_init) + rt712_sdca_reset(rt712); rt712_sdca_index_read(rt712, RT712_VENDOR_REG, RT712_JD_PRODUCT_NUM, &val); rt712->hw_id = (val & 0xf000) >> 12; @@ -2117,7 +2118,7 @@ int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave) * if set_jack callback occurred early than io_init, * we set up the jack detection function now */ - if (rt712->hs_jack) + if (rt712->hs_jack && (!rt712->first_hw_init)) rt712_sdca_jack_init(rt712); rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_SW_CONFIG1, 0x0001); From b61a2369085fd995c8ad44245d3fbd45b1216f71 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:18:22 +0200 Subject: [PATCH 459/924] ALSA: ump: Avoid dereference with max_src_size=0 safe_copy_string() refers the source pointer for a NUL terminator before verifying the source size. When max_src_size=0, it may dereference wrongly. Fix the verification order. Only hypothetical hardening, and we see no actual issue in the current code, as they are all valid sources. Fixes: e3f035edadcc ("ALSA: ump: Copy FB name string more safely") Link: https://patch.msgid.link/20261002081823.680131-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/ump.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/ump.c b/sound/core/ump.c index 3d1a2ed3b476..f6482a9bfe61 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -66,7 +66,7 @@ static void safe_copy_string(void *dst, size_t max_dst_size, if (!max_dst_size--) return; - for (s = src; max_dst_size && *s && max_src_size--; s++) { + for (s = src; max_dst_size && max_src_size-- && *s; s++) { if (!isascii(*s) || !isprint(*s)) continue; *d++ = *s; From d532e2f5c288556075dc429941861391118787a0 Mon Sep 17 00:00:00 2001 From: Yuri Zaporozhets Date: Thu, 1 Oct 2026 21:25:52 +0200 Subject: [PATCH 460/924] ASoC: dwc: Use OF match data again for StarFive JH7110 Commit d6d6c513f5d2 ("ASoC: dwc: Use ops to get platform data") made dw_i2s_probe() fetch its platform data with of_device_get_match_data(), and commit 52ea7c0543f8 ("ASoC: dwc: i2s: Add StarFive JH7110 SoC support") put the JH7110 configuration into the OF match table. Commit 9c97790a07dc ("ASoC: dwc: Fix non-DT instantiation") then went back to pdev->dev.platform_data so that non-DT users such as AMD work again. Since then the JH7110 match data is never read. The probe runs neither the JH7110 clock and reset setup (i2s_pd_init) nor uses its capability flags; it configures the DAI from COMP_PARAM_1 instead. On the VisionFive 2, i2stx0 drives the bit and frame clocks for HDMI audio, but COMP_PARAM_1 reports it as slave-only, so the sound card fails to probe: designware-i2s 120b0000.i2s: ASoC error (-22): at snd_soc_dai_set_fmt() on 120b0000.i2s asoc-simple-card sound-hdmi: probe with driver asoc-simple-card failed with error -22 Take the OF match data first and fall back to the platform data when there is none, so that both DT and non-DT users get their configuration. Tested on a VisionFive 2 v1.3B (v7.0.3 with local HDMI audio patches): the HDMI sound card now probes and plays through i2stx0. The non-DT path is build-tested only. Fixes: 9c97790a07dc ("ASoC: dwc: Fix non-DT instantiation") Assisted-by: LLM Signed-off-by: Yuri Zaporozhets Link: https://patch.msgid.link/20261001192552.2904-1-yuriz@vodafonemail.de Signed-off-by: Mark Brown --- sound/soc/dwc/dwc-i2s.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index ade89c196a79..dbda943cfaf3 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -922,13 +923,16 @@ static int jh7110_i2stx0_clk_cfg(struct i2s_clk_config_data *config) static int dw_i2s_probe(struct platform_device *pdev) { - const struct i2s_platform_data *pdata = pdev->dev.platform_data; + const struct i2s_platform_data *pdata = device_get_match_data(&pdev->dev); struct dw_i2s_dev *dev; struct resource *res; int ret, irq; struct snd_soc_dai_driver *dw_i2s_dai; const char *clk_id; + if (!pdata) + pdata = dev_get_platdata(&pdev->dev); + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; From 015f14e42b223c03e54c12be78247a3735081c21 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 1 Oct 2026 09:51:39 +0300 Subject: [PATCH 461/924] ASoC: codecs: aw88261: small cleanup in aw88261_set_tdm_slot() This code triggers a Smatch false positive: sound/soc/codecs/aw88261.c:858 aw88261_set_tdm_slot() error: uninitialized symbol 'chan'. This would only happen if both the tx and rx masks were zero which is a nonsense scenario and reviewing the callers shows that it never happens. However, if the rx_mask is zero then we're comparing the mask from RX against the chan from TX which is confusing: if ((rx_mask & ~BIT(chan)) != 0) { ^^ rx ^^^^ tx chan Of course, in that case the condition is going to be false because the rx_mask is zero. So it's fine. But it would be more clear to move the condition inside the if statement and it silences the static checker warning as well. Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/ar4C-0NIYXiAvxvv@stanley.mountain Signed-off-by: Mark Brown --- sound/soc/codecs/aw88261.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c index b8d06534bc8f..8671069f4c18 100644 --- a/sound/soc/codecs/aw88261.c +++ b/sound/soc/codecs/aw88261.c @@ -853,13 +853,13 @@ static int aw88261_set_tdm_slot(struct snd_soc_dai *dai, return -EINVAL; aw88261->rxl_slotvld_mask = chan << AW88261_I2S_RXL_SLOTVLD_START_BIT; - } - if ((rx_mask & ~BIT(chan)) != 0) { - if ((chan = __ffs(rx_mask & ~BIT(chan))) > 16) - return -EINVAL; + if ((rx_mask & ~BIT(chan)) != 0) { + if ((chan = __ffs(rx_mask & ~BIT(chan))) > 16) + return -EINVAL; - aw88261->rxr_slotvld_mask = chan << AW88261_I2S_RXR_SLOTVLD_START_BIT; + aw88261->rxr_slotvld_mask = chan << AW88261_I2S_RXR_SLOTVLD_START_BIT; + } } return 0; From 8a87a0a2881e241e7414fb5a00b39c57c698881b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:39 +0200 Subject: [PATCH 462/924] ALSA: galaxy: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-2-tiwai@suse.de --- sound/isa/galaxy/galaxy.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sound/isa/galaxy/galaxy.c b/sound/isa/galaxy/galaxy.c index b0f1562d0fc0..aa31744ed582 100644 --- a/sound/isa/galaxy/galaxy.c +++ b/sound/isa/galaxy/galaxy.c @@ -478,11 +478,11 @@ static void snd_galaxy_free(struct snd_card *card) galaxy_set_config(galaxy, galaxy->config); } -static int __snd_galaxy_probe(struct device *dev, unsigned int n) +static int snd_galaxy_probe(struct device *dev, unsigned int n) { struct snd_galaxy *galaxy; struct snd_wss *chip; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; u8 type; int err; @@ -595,14 +595,10 @@ static int __snd_galaxy_probe(struct device *dev, unsigned int n) return err; dev_set_drvdata(dev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_galaxy_probe(struct device *dev, unsigned int n) -{ - return snd_card_free_on_error(dev, __snd_galaxy_probe(dev, n)); -} - static struct isa_driver snd_galaxy_driver = { .match = snd_galaxy_match, .probe = snd_galaxy_probe, From 63be9992366864c01d97b12bbaeeed6120f97fd5 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:40 +0200 Subject: [PATCH 463/924] ALSA: sc6000: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-3-tiwai@suse.de --- sound/isa/sc6000.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c index cd3a63c7c2a7..f75533fe03a4 100644 --- a/sound/isa/sc6000.c +++ b/sound/isa/sc6000.c @@ -572,14 +572,14 @@ static void snd_sc6000_free(struct snd_card *card) sc6000_setup_board(card->dev, sc6000->vport, 0); } -static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) +static int snd_sc6000_probe(struct device *devptr, unsigned int dev) { static const int possible_irqs[] = { 5, 7, 9, 10, 11, -1 }; static const int possible_dmas[] = { 1, 3, 0, -1 }; int err; int xirq = irq[dev]; int xdma = dma[dev]; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_sc6000 *sc6000; struct snd_wss *chip; struct snd_opl3 *opl3; @@ -696,14 +696,10 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) return err; dev_set_drvdata(devptr, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_sc6000_probe(struct device *devptr, unsigned int dev) -{ - return snd_card_free_on_error(devptr, __snd_sc6000_probe(devptr, dev)); -} - #ifdef CONFIG_PM static int snd_sc6000_suspend(struct device *devptr, unsigned int dev, pm_message_t state) From 0c43bc1efd37ef72bc3f95e033cf9ebec35baf8e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:41 +0200 Subject: [PATCH 464/924] ALSA: ad1889: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-4-tiwai@suse.de --- sound/pci/ad1889.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/ad1889.c b/sound/pci/ad1889.c index f4ec404c0d15..42a8fa3f4fdc 100644 --- a/sound/pci/ad1889.c +++ b/sound/pci/ad1889.c @@ -837,12 +837,12 @@ snd_ad1889_create(struct snd_card *card, struct pci_dev *pci) } static int -__snd_ad1889_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +snd_ad1889_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { int err; static int devno; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ad1889 *chip; /* (1) */ @@ -892,17 +892,12 @@ __snd_ad1889_probe(struct pci_dev *pci, /* (7) */ pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ devno++; return 0; } -static int snd_ad1889_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_ad1889_probe(pci, pci_id)); -} - static const struct pci_device_id snd_ad1889_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_ANALOG_DEVICES, PCI_DEVICE_ID_AD1889JS) }, { 0, }, From 02f8c91bb85001997a91e24c1dd222afb0616073 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:42 +0200 Subject: [PATCH 465/924] ALSA: ali5451: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-5-tiwai@suse.de --- sound/pci/ali5451/ali5451.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c index f07c5f4c2380..8295eee9b977 100644 --- a/sound/pci/ali5451/ali5451.c +++ b/sound/pci/ali5451/ali5451.c @@ -2086,10 +2086,10 @@ static int snd_ali_create(struct snd_card *card, return 0; } -static int __snd_ali_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_ali_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ali *codec; int err; @@ -2129,15 +2129,10 @@ static int __snd_ali_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_ali_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_ali_probe(pci, pci_id)); -} - static struct pci_driver ali5451_driver = { .name = KBUILD_MODNAME, .id_table = snd_ali_ids, From 91adc3ff80c165ac6117c1e7ced91204a1870c31 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:43 +0200 Subject: [PATCH 466/924] ALSA: als4000: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-6-tiwai@suse.de --- sound/pci/als4000.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/als4000.c b/sound/pci/als4000.c index 6396aa6c3bf0..12bb868fb9ce 100644 --- a/sound/pci/als4000.c +++ b/sound/pci/als4000.c @@ -798,11 +798,11 @@ static void snd_card_als4000_free( struct snd_card *card ) snd_als4000_free_gameport(acard); } -static int __snd_card_als4000_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_card_als4000_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_card_als4000 *acard; unsigned long iobase; struct snd_sb *chip; @@ -918,16 +918,11 @@ static int __snd_card_als4000_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_card_als4000_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_als4000_probe(pci, pci_id)); -} - static int snd_als4000_suspend(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); From efd5983f3324298db2c81529bb472ef806c7ee0e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:44 +0200 Subject: [PATCH 467/924] ALSA: atiixp: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-7-tiwai@suse.de --- sound/pci/atiixp.c | 13 ++++--------- sound/pci/atiixp_modem.c | 13 ++++--------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/sound/pci/atiixp.c b/sound/pci/atiixp.c index b738295b41e5..e1d36cd7945b 100644 --- a/sound/pci/atiixp.c +++ b/sound/pci/atiixp.c @@ -1550,10 +1550,10 @@ static int snd_atiixp_init(struct snd_card *card, struct pci_dev *pci) } -static int __snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_atiixp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct atiixp *chip; int err; @@ -1598,15 +1598,10 @@ static int __snd_atiixp_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_atiixp_probe(pci, pci_id)); -} - static struct pci_driver atiixp_driver = { .name = KBUILD_MODNAME, .id_table = snd_atiixp_ids, diff --git a/sound/pci/atiixp_modem.c b/sound/pci/atiixp_modem.c index 8aaeb197ce45..4984a345b4b2 100644 --- a/sound/pci/atiixp_modem.c +++ b/sound/pci/atiixp_modem.c @@ -1182,10 +1182,10 @@ static int snd_atiixp_init(struct snd_card *card, struct pci_dev *pci) } -static int __snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_atiixp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct atiixp_modem *chip; int err; @@ -1225,15 +1225,10 @@ static int __snd_atiixp_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_atiixp_probe(pci, pci_id)); -} - static struct pci_driver atiixp_modem_driver = { .name = KBUILD_MODNAME, .id_table = snd_atiixp_ids, From ae650a6b04e439f120b9576081d36d505695788e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:45 +0200 Subject: [PATCH 468/924] ALSA: au88x0: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-8-tiwai@suse.de --- sound/pci/au88x0/au88x0.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sound/pci/au88x0/au88x0.c b/sound/pci/au88x0/au88x0.c index bb02945793f0..87a24f0c86e7 100644 --- a/sound/pci/au88x0/au88x0.c +++ b/sound/pci/au88x0/au88x0.c @@ -192,10 +192,10 @@ snd_vortex_create(struct snd_card *card, struct pci_dev *pci) // constructor -- see "Constructor" sub-section static int -__snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; vortex_t *chip; int err; @@ -303,18 +303,13 @@ __snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) return err; // (7) pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; vortex_connect_default(chip, 1); vortex_enable_int(chip); return 0; } -static int -snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_vortex_probe(pci, pci_id)); -} - // pci_driver definition static struct pci_driver vortex_driver = { .name = KBUILD_MODNAME, From 0f7ac8f776175b7d0be90531dc234f3cc38c0b45 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:46 +0200 Subject: [PATCH 469/924] ALSA: azt3328: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-9-tiwai@suse.de --- sound/pci/azt3328.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sound/pci/azt3328.c b/sound/pci/azt3328.c index ccca82417657..8cf94c41a54c 100644 --- a/sound/pci/azt3328.c +++ b/sound/pci/azt3328.c @@ -2403,10 +2403,10 @@ snd_azf3328_create(struct snd_card *card, } static int -__snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_azf3328 *chip; struct snd_opl3 *opl3; int err; @@ -2492,16 +2492,11 @@ __snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) snd_azf3328_gameport(chip, dev); pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_azf3328_probe(pci, pci_id)); -} - static inline void snd_azf3328_suspend_regs(const struct snd_azf3328 *chip, unsigned long io_addr, unsigned count, u32 *saved_regs) From 2ca72bae878d78394b7770c038d71f9f10d81aa1 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:47 +0200 Subject: [PATCH 470/924] ALSA: bt87x: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-10-tiwai@suse.de --- sound/pci/bt87x.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/bt87x.c b/sound/pci/bt87x.c index 383def1f2af7..db8f60261a0f 100644 --- a/sound/pci/bt87x.c +++ b/sound/pci/bt87x.c @@ -798,11 +798,11 @@ static int snd_bt87x_detect_card(struct pci_dev *pci) return SND_BT87X_BOARD_UNKNOWN; } -static int __snd_bt87x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_bt87x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_bt87x *chip; int err; enum snd_bt87x_boardid boardid; @@ -878,16 +878,11 @@ static int __snd_bt87x_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ ++dev; return 0; } -static int snd_bt87x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_bt87x_probe(pci, pci_id)); -} - /* default entries for all Bt87x cards - it's not exported */ /* driver_data is set to 0 to call detection */ static const struct pci_device_id snd_bt87x_default_ids[] = { From 3326e003e04a1a74751b054b515480eec8f9081d Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:48 +0200 Subject: [PATCH 471/924] ALSA: ca0106: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-11-tiwai@suse.de --- sound/pci/ca0106/ca0106_main.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sound/pci/ca0106/ca0106_main.c b/sound/pci/ca0106/ca0106_main.c index cf923f551211..6429c2bfc310 100644 --- a/sound/pci/ca0106/ca0106_main.c +++ b/sound/pci/ca0106/ca0106_main.c @@ -1710,11 +1710,11 @@ static int snd_ca0106_midi(struct snd_ca0106 *chip, unsigned int channel) } -static int __snd_ca0106_probe(struct pci_dev *pci, +static int snd_ca0106_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ca0106 *chip; int i, err; @@ -1767,16 +1767,11 @@ static int __snd_ca0106_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_ca0106_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_ca0106_probe(pci, pci_id)); -} - #ifdef CONFIG_PM_SLEEP static int snd_ca0106_suspend(struct device *dev) { From cbf84e22691e7560683c43a18415309ac445eb13 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:49 +0200 Subject: [PATCH 472/924] ALSA: cs4281: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-12-tiwai@suse.de --- sound/pci/cs4281.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index 27ceda6278c5..b831f1d50750 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -1811,11 +1811,11 @@ static void snd_cs4281_opl3_command(struct snd_opl3 *opl3, unsigned short cmd, udelay(30); } -static int __snd_cs4281_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_cs4281_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct cs4281 *chip; struct snd_opl3 *opl3; int err; @@ -1868,16 +1868,11 @@ static int __snd_cs4281_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_cs4281_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_cs4281_probe(pci, pci_id)); -} - /* * Power Management */ From 7d5c07fb6032a23806c1be04ef2236d85d13808b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:50 +0200 Subject: [PATCH 473/924] ALSA: cs5535audio: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-13-tiwai@suse.de --- sound/pci/cs5535audio/cs5535audio.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/cs5535audio/cs5535audio.c b/sound/pci/cs5535audio/cs5535audio.c index 0ebf6c02b1ef..e09f7bc9e23a 100644 --- a/sound/pci/cs5535audio/cs5535audio.c +++ b/sound/pci/cs5535audio/cs5535audio.c @@ -283,11 +283,11 @@ static int snd_cs5535audio_create(struct snd_card *card, return 0; } -static int __snd_cs5535audio_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_cs5535audio_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct cs5535audio *cs5535au; int err; @@ -329,16 +329,11 @@ static int __snd_cs5535audio_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_cs5535audio_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_cs5535audio_probe(pci, pci_id)); -} - static struct pci_driver cs5535audio_driver = { .name = KBUILD_MODNAME, .id_table = snd_cs5535audio_ids, From 60351c792a379304cc124912fd83e3d82228b4dc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:51 +0200 Subject: [PATCH 474/924] ALSA: echoaudio: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-14-tiwai@suse.de --- sound/pci/echoaudio/echoaudio.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c index 8b4637231deb..4dcad05fd57e 100644 --- a/sound/pci/echoaudio/echoaudio.c +++ b/sound/pci/echoaudio/echoaudio.c @@ -1934,11 +1934,11 @@ static int snd_echo_create(struct snd_card *card, } /* constructor */ -static int __snd_echo_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_echo_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct echoaudio *chip; char *dsp; int err; @@ -2097,17 +2097,11 @@ static int __snd_echo_probe(struct pci_dev *pci, dev_info(card->dev, "Card registered: %s\n", card->longname); pci_set_drvdata(pci, chip); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_echo_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_echo_probe(pci, pci_id)); -} - - static int snd_echo_suspend(struct device *dev) { struct echoaudio *chip = dev_get_drvdata(dev); From bd8ff9349a84d0823ff15160e585d7c2744fd4a1 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:52 +0200 Subject: [PATCH 475/924] ALSA: emu10k1x: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-15-tiwai@suse.de --- sound/pci/emu10k1/emu10k1x.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c index ed4630c8342b..03d277dd02d9 100644 --- a/sound/pci/emu10k1/emu10k1x.c +++ b/sound/pci/emu10k1/emu10k1x.c @@ -1447,11 +1447,11 @@ static int snd_emu10k1x_midi(struct emu10k1x *emu) return 0; } -static int __snd_emu10k1x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_emu10k1x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct emu10k1x *chip; int err; @@ -1506,16 +1506,11 @@ static int __snd_emu10k1x_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_emu10k1x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_emu10k1x_probe(pci, pci_id)); -} - // PCI IDs static const struct pci_device_id snd_emu10k1x_ids[] = { { PCI_VDEVICE(CREATIVE, 0x0006) }, /* Dell OEM version (EMU10K1) */ From 211a6ae74af7b0d366cd371561beb050cab4604c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:53 +0200 Subject: [PATCH 476/924] ALSA: ens137x: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-16-tiwai@suse.de --- sound/pci/ens1370.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c index 0b9eb55a2121..c2af7eab633c 100644 --- a/sound/pci/ens1370.c +++ b/sound/pci/ens1370.c @@ -2267,11 +2267,11 @@ static irqreturn_t snd_audiopci_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } -static int __snd_audiopci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_audiopci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct ensoniq *ensoniq; int err; @@ -2328,16 +2328,11 @@ static int __snd_audiopci_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_audiopci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_audiopci_probe(pci, pci_id)); -} - static struct pci_driver ens137x_driver = { .name = KBUILD_MODNAME, .id_table = snd_audiopci_ids, From f0585945867630e6ade013b703e436cd2e87ff8e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:54 +0200 Subject: [PATCH 477/924] ALSA: es1938: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-17-tiwai@suse.de --- sound/pci/es1938.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/es1938.c b/sound/pci/es1938.c index 217beb9376ac..a612368fd0b1 100644 --- a/sound/pci/es1938.c +++ b/sound/pci/es1938.c @@ -1683,11 +1683,11 @@ static int snd_es1938_mixer(struct es1938 *chip) } -static int __snd_es1938_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_es1938_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct es1938 *chip; struct snd_opl3 *opl3; int idx, err; @@ -1759,16 +1759,11 @@ static int __snd_es1938_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_es1938_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_es1938_probe(pci, pci_id)); -} - static struct pci_driver es1938_driver = { .name = KBUILD_MODNAME, .id_table = snd_es1938_ids, From 4bf0bf661065e0a98bf33735af03f833241ec548 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:55 +0200 Subject: [PATCH 478/924] ALSA: es1968: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-18-tiwai@suse.de --- sound/pci/es1968.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/es1968.c b/sound/pci/es1968.c index f04628c8cbb7..b0abbea2a992 100644 --- a/sound/pci/es1968.c +++ b/sound/pci/es1968.c @@ -2692,11 +2692,11 @@ static int snd_es1968_create(struct snd_card *card, /* */ -static int __snd_es1968_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_es1968_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct es1968 *chip; unsigned int i; int err; @@ -2795,16 +2795,11 @@ static int __snd_es1968_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_es1968_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_es1968_probe(pci, pci_id)); -} - static struct pci_driver es1968_driver = { .name = KBUILD_MODNAME, .id_table = snd_es1968_ids, From 89edb9020bfb6afceaf11bdcd14f29d734c872b1 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:56 +0200 Subject: [PATCH 479/924] ALSA: fm801: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-19-tiwai@suse.de --- sound/pci/fm801.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/fm801.c b/sound/pci/fm801.c index 9cc96b807dd7..507ea0fe22a9 100644 --- a/sound/pci/fm801.c +++ b/sound/pci/fm801.c @@ -1264,11 +1264,11 @@ static int snd_fm801_create(struct snd_card *card, return 0; } -static int __snd_card_fm801_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_card_fm801_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct fm801 *chip; struct snd_opl3 *opl3; int err; @@ -1325,16 +1325,11 @@ static int __snd_card_fm801_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_card_fm801_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_fm801_probe(pci, pci_id)); -} - static const unsigned char saved_regs[] = { FM801_PCM_VOL, FM801_I2S_VOL, FM801_FM_VOL, FM801_REC_SRC, FM801_PLY_CTRL, FM801_PLY_COUNT, FM801_PLY_BUF1, FM801_PLY_BUF2, From 38725733ee11a8127e758a58a13e5a4ef8e7f076 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:57 +0200 Subject: [PATCH 480/924] ALSA: ice1724: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-20-tiwai@suse.de --- sound/pci/ice1712/ice1724.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 859bb87393b4..651d92663567 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -2524,11 +2524,11 @@ static int snd_vt1724_create(struct snd_card *card, * */ -static int __snd_vt1724_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_vt1724_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ice1712 *ice; int pcm_dev = 0, err; const struct snd_ice1712_card_info *c; @@ -2663,16 +2663,11 @@ static int __snd_vt1724_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_vt1724_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_vt1724_probe(pci, pci_id)); -} - #ifdef CONFIG_PM_SLEEP static int snd_vt1724_suspend(struct device *dev) { From 8d09d03108d216e9155973dd19839298d851006a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:58 +0200 Subject: [PATCH 481/924] ALSA: intel8x0: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-21-tiwai@suse.de --- sound/pci/intel8x0.c | 14 +++++--------- sound/pci/intel8x0m.c | 13 ++++--------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c index e2ea9016a73e..339024182a26 100644 --- a/sound/pci/intel8x0.c +++ b/sound/pci/intel8x0.c @@ -3167,10 +3167,10 @@ static int check_default_spdif_aclink(struct pci_dev *pci) return 0; } -static int __snd_intel8x0_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_intel8x0_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct intel8x0 *chip; int err; struct shortname_table *name; @@ -3244,13 +3244,9 @@ static int __snd_intel8x0_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); - return 0; -} + card = NULL; /* probe succeeded, don't release as error */ -static int snd_intel8x0_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_intel8x0_probe(pci, pci_id)); + return 0; } static struct pci_driver intel8x0_driver = { diff --git a/sound/pci/intel8x0m.c b/sound/pci/intel8x0m.c index 27dbf6125427..38a15ebf94b2 100644 --- a/sound/pci/intel8x0m.c +++ b/sound/pci/intel8x0m.c @@ -1218,10 +1218,10 @@ static struct shortname_table { { 0 }, }; -static int __snd_intel8x0m_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_intel8x0m_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct intel8x0m *chip; int err; struct shortname_table *name; @@ -1262,15 +1262,10 @@ static int __snd_intel8x0m_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_intel8x0m_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_intel8x0m_probe(pci, pci_id)); -} - static struct pci_driver intel8x0m_driver = { .name = KBUILD_MODNAME, .id_table = snd_intel8x0m_ids, From 4d73d8903ad88403225a2080cac90aae234a8333 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:54:59 +0200 Subject: [PATCH 482/924] ALSA: lola: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-22-tiwai@suse.de --- sound/pci/lola/lola.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/lola/lola.c b/sound/pci/lola/lola.c index 34a3ba17deb4..1f7ffca57fc9 100644 --- a/sound/pci/lola/lola.c +++ b/sound/pci/lola/lola.c @@ -641,11 +641,11 @@ static int lola_create(struct snd_card *card, struct pci_dev *pci, int dev) return 0; } -static int __lola_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int lola_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct lola *chip; int err; @@ -687,16 +687,11 @@ static int __lola_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int lola_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __lola_probe(pci, pci_id)); -} - /* PCI IDs */ static const struct pci_device_id lola_ids[] = { { PCI_VDEVICE(DIGIGRAM, 0x0001) }, From c7a84de7bc974499cb0add7d9bcd69d4af2d47ed Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:55:00 +0200 Subject: [PATCH 483/924] ALSA: maestro3: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-23-tiwai@suse.de --- sound/pci/maestro3.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c index dd45ffa171e1..2114f9f16a34 100644 --- a/sound/pci/maestro3.c +++ b/sound/pci/maestro3.c @@ -2629,10 +2629,10 @@ snd_m3_create(struct snd_card *card, struct pci_dev *pci, /* */ static int -__snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_m3 *chip; int err; @@ -2690,16 +2690,11 @@ __snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) #endif pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_m3_probe(pci, pci_id)); -} - static struct pci_driver m3_driver = { .name = KBUILD_MODNAME, .id_table = snd_m3_ids, From 8532e8a0382e277aca2eaa9aac2969a033d533a5 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:55:01 +0200 Subject: [PATCH 484/924] ALSA: oxygen: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-24-tiwai@suse.de --- sound/pci/oxygen/oxygen_lib.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/sound/pci/oxygen/oxygen_lib.c b/sound/pci/oxygen/oxygen_lib.c index 6b096d654f9f..587785d5451b 100644 --- a/sound/pci/oxygen/oxygen_lib.c +++ b/sound/pci/oxygen/oxygen_lib.c @@ -572,7 +572,7 @@ static void oxygen_card_free(struct snd_card *card) mutex_destroy(&chip->mutex); } -static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, +int oxygen_pci_probe(struct pci_dev *pci, int index, char *id, struct module *owner, const struct pci_device_id *ids, int (*get_model)(struct oxygen *chip, @@ -580,7 +580,7 @@ static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, ) ) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct oxygen *chip; const struct pci_device_id *pci_id; int err; @@ -695,18 +695,9 @@ static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } - -int oxygen_pci_probe(struct pci_dev *pci, int index, char *id, - struct module *owner, - const struct pci_device_id *ids, - int (*get_model)(struct oxygen *chip, - const struct pci_device_id *id)) -{ - return snd_card_free_on_error(&pci->dev, - __oxygen_pci_probe(pci, index, id, owner, ids, get_model)); -} EXPORT_SYMBOL(oxygen_pci_probe); static int oxygen_pci_suspend(struct device *dev) From 7e047da12cb742603d1b92a62e31a7b8b2cc4388 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:55:02 +0200 Subject: [PATCH 485/924] ALSA: riptide: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-25-tiwai@suse.de --- sound/pci/riptide/riptide.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sound/pci/riptide/riptide.c b/sound/pci/riptide/riptide.c index 697fece744e7..0d9110558ce4 100644 --- a/sound/pci/riptide/riptide.c +++ b/sound/pci/riptide/riptide.c @@ -2020,10 +2020,10 @@ static void snd_riptide_joystick_remove(struct pci_dev *pci) #endif static int -__snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_riptide *chip; unsigned short val; int err; @@ -2117,16 +2117,11 @@ __snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_riptide_probe(pci, pci_id)); -} - static struct pci_driver driver = { .name = KBUILD_MODNAME, .id_table = snd_riptide_ids, From 9c33b177c70a1e87f22f8151d100a4e09d91db11 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:55:03 +0200 Subject: [PATCH 486/924] ALSA: rme32: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-26-tiwai@suse.de --- sound/pci/rme32.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sound/pci/rme32.c b/sound/pci/rme32.c index 1d5390621908..7ea9a71aaab9 100644 --- a/sound/pci/rme32.c +++ b/sound/pci/rme32.c @@ -1810,11 +1810,11 @@ static void snd_rme32_card_free(struct snd_card *card) } static int -__snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; struct rme32 *rme32; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) { @@ -1858,16 +1858,11 @@ __snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_rme32_probe(pci, pci_id)); -} - static struct pci_driver rme32_driver = { .name = KBUILD_MODNAME, .id_table = snd_rme32_ids, From 82d5474fb991c3a8fe4606033ce0a83aa33519b5 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:55:04 +0200 Subject: [PATCH 487/924] ALSA: rm96: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-27-tiwai@suse.de --- sound/pci/rme96.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/rme96.c b/sound/pci/rme96.c index 892fcc598557..926783f0410e 100644 --- a/sound/pci/rme96.c +++ b/sound/pci/rme96.c @@ -2356,12 +2356,12 @@ static void snd_rme96_card_free(struct snd_card *card) } static int -__snd_rme96_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +snd_rme96_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; struct rme96 *rme96; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; u8 val; @@ -2420,16 +2420,11 @@ __snd_rme96_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_rme96_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_rme96_probe(pci, pci_id)); -} - static struct pci_driver rme96_driver = { .name = KBUILD_MODNAME, .id_table = snd_rme96_ids, From c0e73adb26eedb18477526d3ab369ac0c6712229 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:55:05 +0200 Subject: [PATCH 488/924] ALSA: sis7019: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-28-tiwai@suse.de --- sound/pci/sis7019.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c index 55c1b11a2900..fe5bda5af6a5 100644 --- a/sound/pci/sis7019.c +++ b/sound/pci/sis7019.c @@ -1309,10 +1309,10 @@ static int sis_chip_create(struct snd_card *card, return 0; } -static int __snd_sis7019_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_sis7019_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct sis7019 *sis; int rc; @@ -1361,15 +1361,10 @@ static int __snd_sis7019_probe(struct pci_dev *pci, return rc; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_sis7019_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_sis7019_probe(pci, pci_id)); -} - static struct pci_driver sis7019_driver = { .name = KBUILD_MODNAME, .id_table = snd_sis7019_ids, From 5762f3aee294f26f969becc4ec227906f4e682c0 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:55:06 +0200 Subject: [PATCH 489/924] ALSA: sonicbibes: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-29-tiwai@suse.de --- sound/pci/sonicvibes.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/sonicvibes.c b/sound/pci/sonicvibes.c index a885d544acd6..b5bb20440c6e 100644 --- a/sound/pci/sonicvibes.c +++ b/sound/pci/sonicvibes.c @@ -1362,11 +1362,11 @@ static int snd_sonicvibes_midi(struct sonicvibes *sonic, return 0; } -static int __snd_sonic_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_sonic_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct sonicvibes *sonic; struct snd_rawmidi *midi_uart; struct snd_opl3 *opl3; @@ -1430,16 +1430,11 @@ static int __snd_sonic_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_sonic_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_sonic_probe(pci, pci_id)); -} - static struct pci_driver sonicvibes_driver = { .name = KBUILD_MODNAME, .id_table = snd_sonic_ids, From 47a57f2fe8c8300eb48fae09509fe535b76fd4c3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:55:07 +0200 Subject: [PATCH 490/924] ALSA: via82xx: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-30-tiwai@suse.de --- sound/pci/via82xx.c | 13 ++++--------- sound/pci/via82xx_modem.c | 13 ++++--------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index 58816dea4996..c13fc554c0c8 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -2428,10 +2428,10 @@ static int check_dxs_list(struct pci_dev *pci, int revision) return VIA_DXS_48K; }; -static int __snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct via82xx *chip; int chip_type = 0, card_type; unsigned int i; @@ -2536,15 +2536,10 @@ static int __snd_via82xx_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_via82xx_probe(pci, pci_id)); -} - static struct pci_driver via82xx_driver = { .name = KBUILD_MODNAME, .id_table = snd_via82xx_ids, diff --git a/sound/pci/via82xx_modem.c b/sound/pci/via82xx_modem.c index b32f84ac17cc..7934bc5d6463 100644 --- a/sound/pci/via82xx_modem.c +++ b/sound/pci/via82xx_modem.c @@ -1088,10 +1088,10 @@ static int snd_via82xx_create(struct snd_card *card, } -static int __snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct via82xx_modem *chip; int chip_type = 0, card_type; unsigned int i; @@ -1139,15 +1139,10 @@ static int __snd_via82xx_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_via82xx_probe(pci, pci_id)); -} - static struct pci_driver via82xx_modem_driver = { .name = KBUILD_MODNAME, .id_table = snd_via82xx_modem_ids, From 5e1599dad04aec00d7389daa091161096024ebb6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:55:08 +0200 Subject: [PATCH 491/924] ALSA: ymfpci: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-31-tiwai@suse.de --- sound/pci/ymfpci/ymfpci.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/pci/ymfpci/ymfpci.c b/sound/pci/ymfpci/ymfpci.c index d3fb047c9a45..5e8c7e318138 100644 --- a/sound/pci/ymfpci/ymfpci.c +++ b/sound/pci/ymfpci/ymfpci.c @@ -152,11 +152,11 @@ static inline int snd_ymfpci_create_gameport(struct snd_ymfpci *chip, int dev, i void snd_ymfpci_free_gameport(struct snd_ymfpci *chip) { } #endif /* SUPPORT_JOYSTICK */ -static int __snd_card_ymfpci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_card_ymfpci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct resource *fm_res = NULL; struct resource *mpu_res = NULL; struct snd_ymfpci *chip; @@ -344,16 +344,11 @@ static int __snd_card_ymfpci_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_card_ymfpci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_ymfpci_probe(pci, pci_id)); -} - static struct pci_driver ymfpci_driver = { .name = KBUILD_MODNAME, .id_table = snd_ymfpci_ids, From 1558c15e21f27b2a4c6f5460faf6f5c30281cf10 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:55:09 +0200 Subject: [PATCH 492/924] ALSA: intel-hdmi-audio: Use auto-cleanup for probe card error handling Instead of the manual call of snd_card_free_on_error(), use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-32-tiwai@suse.de --- sound/x86/intel_hdmi_audio.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sound/x86/intel_hdmi_audio.c b/sound/x86/intel_hdmi_audio.c index 91dfcf47e979..9f67244d4946 100644 --- a/sound/x86/intel_hdmi_audio.c +++ b/sound/x86/intel_hdmi_audio.c @@ -1634,9 +1634,9 @@ static void hdmi_lpe_audio_free(struct snd_card *card) * This function is called when the i915 driver creates the * hdmi-lpe-audio platform device. */ -static int __hdmi_lpe_audio_probe(struct platform_device *pdev) +static int hdmi_lpe_audio_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_intelhad_card *card_ctx; struct snd_intelhad *ctx; struct snd_pcm *pcm; @@ -1797,14 +1797,10 @@ static int __hdmi_lpe_audio_probe(struct platform_device *pdev) schedule_work(&ctx->hdmi_audio_wq); } + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int hdmi_lpe_audio_probe(struct platform_device *pdev) -{ - return snd_card_free_on_error(&pdev->dev, __hdmi_lpe_audio_probe(pdev)); -} - static const struct dev_pm_ops hdmi_lpe_audio_pm = { SYSTEM_SLEEP_PM_OPS(hdmi_lpe_audio_suspend, hdmi_lpe_audio_resume) }; From cb8e306fd99c70cc6f57d1a3ec39659dd4c5a6e6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 10:55:10 +0200 Subject: [PATCH 493/924] ALSA: Drop unused snd_card_free_on_error() Now that all users of snd_card_free_on_error() are gone by refactoring with auto-cleanup, let's drop the superfluous function. Also, update the documentation for reflecting the recommended error handling procedure, too. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002085517.693510-33-tiwai@suse.de --- include/sound/core.h | 1 - sound/core/init.c | 36 +++++++----------------------------- 2 files changed, 7 insertions(+), 30 deletions(-) diff --git a/include/sound/core.h b/include/sound/core.h index 15be947a730e..daa91a46a16b 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -314,7 +314,6 @@ void snd_card_disconnect(struct snd_card *card); void snd_card_disconnect_sync(struct snd_card *card); void snd_card_free(struct snd_card *card); void snd_card_free_when_closed(struct snd_card *card); -int snd_card_free_on_error(struct device *dev, int ret); void snd_card_set_id(struct snd_card *card, const char *id); int snd_card_register(struct snd_card *card); int snd_card_info_init(void); diff --git a/sound/core/init.c b/sound/core/init.c index a51b71812fd9..7878c0e6103c 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -216,11 +216,13 @@ static void __snd_card_release(struct device *dev, void *data) * is added automatically. In that way, the resource disconnection is assured * at first, then released in the expected order. * - * If an error happens at the probe before snd_card_register() is called and - * there have been other devres resources, you'd need to free the card manually - * via snd_card_free() call in the error; otherwise it may lead to UAF due to - * devres call orders. You can use snd_card_free_on_error() helper for - * handling it more easily. + * Caveat: if an error happens at the probe before snd_card_register() is + * called and there have been other devres resources that are referred at the + * card-level destructor call, you'd still need to free the card manually; + * otherwise it may lead to UAF due to devres call orders. Instead of a manual + * snd_card_free() call, you can use the automatic clean-up for the card + * pointer via __free(snd_card_free), too. In this case, don't forget to do + * NULL-clear the card pointer before the successful return! * * Return: zero if successful, or a negative error code */ @@ -250,30 +252,6 @@ int snd_devm_card_new(struct device *parent, int idx, const char *xid, } EXPORT_SYMBOL_GPL(snd_devm_card_new); -/** - * snd_card_free_on_error - a small helper for handling devm probe errors - * @dev: the managed device object - * @ret: the return code from the probe callback - * - * This function handles the explicit snd_card_free() call at the error from - * the probe callback. It's just a small helper for simplifying the error - * handling with the managed devices. - * - * Return: zero if successful, or a negative error code - */ -int snd_card_free_on_error(struct device *dev, int ret) -{ - struct snd_card *card; - - if (!ret) - return 0; - card = devres_find(dev, __snd_card_release, NULL, NULL); - if (card) - snd_card_free(card); - return ret; -} -EXPORT_SYMBOL_GPL(snd_card_free_on_error); - static int snd_card_init(struct snd_card *card, struct device *parent, int idx, const char *xid, struct module *module, size_t extra_size) From 9f6f4e97fd53908aecb0979507d9ff10a22288eb Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 17:04:19 +0200 Subject: [PATCH 494/924] ALSA: pcm: Optimize cpu_latency_qos_*() calls We have a few cpu_latency_qos_*() calls in PCM callbacks, but they seem suboptimal in a few aspects: - We call them at each time when the hw_params changed, but explicitly do remove and (re-)add; this could be cpu_latency_qos_update_request(), instead - The setup of cpu_latency_qos_*() doesn't have to be in hw_params at all; it's not the place where the actual parameters are set up, but rather better at the prepare callback. - The hw_free calls cpu_latency_qos_remove_request() unconditionally, which may be poking a WARNING when it wasn't queued. For addressing the issues above, this patch cleans up and reorganize the cpu_latency_qos_*() calls in the PCM core by a couple helper functions. Now, they are set up at prepare callback, and cleared at hw_free and release callbacks via the new helpers. Link: https://patch.msgid.link/20261002150420.879056-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 5abe16a03b34..6efaebc7f8b4 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -778,11 +778,32 @@ EXPORT_SYMBOL_GPL(snd_pcm_runtime_buffer_set_silence); #define is_oss_stream(substream) false #endif +static void pcm_qos_req_clear(struct snd_pcm_substream *substream) +{ + if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) + cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); +} + +static void pcm_qos_req_update(struct snd_pcm_substream *substream, int usecs) +{ + if (usecs < 0) { + pcm_qos_req_clear(substream); + return; + } + + if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) + cpu_latency_qos_update_request(&substream->latency_pm_qos_req, + usecs); + else + cpu_latency_qos_add_request(&substream->latency_pm_qos_req, + usecs); +} + static int snd_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_pcm_runtime *runtime; - int err, usecs; + int err; unsigned int bits; snd_pcm_uframes_t frames; @@ -889,12 +910,6 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, snd_pcm_timer_resolution_change(substream); snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP); - if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); - usecs = period_to_usecs(runtime); - if (usecs >= 0) - cpu_latency_qos_add_request(&substream->latency_pm_qos_req, - usecs); err = 0; _error: if (err) { @@ -971,7 +986,7 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream) goto unlock; result = do_hw_free(substream); snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN); - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); + pcm_qos_req_clear(substream); unlock: snd_pcm_buffer_access_unlock(runtime); return result; @@ -2005,6 +2020,7 @@ static int snd_pcm_do_prepare(struct snd_pcm_substream *substream, err = substream->ops->prepare(substream); if (err < 0) return err; + pcm_qos_req_update(substream, period_to_usecs(substream->runtime)); return snd_pcm_do_reset(substream, state); } @@ -2795,8 +2811,7 @@ void snd_pcm_release_substream(struct snd_pcm_substream *substream) substream->ops->close(substream); substream->hw_opened = 0; } - if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); + pcm_qos_req_clear(substream); if (substream->pcm_release) { substream->pcm_release(substream); substream->pcm_release = NULL; From d7711712c577bb88cac573918df517bb1d577b33 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 17:07:23 +0200 Subject: [PATCH 495/924] ALSA: als300: Avoid manual calls of snd_card_free() at probe card error handling Instead of the manual calls of snd_card_free() at the error handling, use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002150733.880051-2-tiwai@suse.de --- sound/pci/als300.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/pci/als300.c b/sound/pci/als300.c index 00f0720169c3..5832f4d38c4e 100644 --- a/sound/pci/als300.c +++ b/sound/pci/als300.c @@ -675,7 +675,7 @@ static int snd_als300_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_als300 *chip; int err, chip_type; @@ -696,7 +696,7 @@ static int snd_als300_probe(struct pci_dev *pci, err = snd_als300_create(card, pci, chip_type); if (err < 0) - goto error; + return err; strscpy(card->driver, "ALS300"); if (chip->chip_type == DEVICE_ALS300_PLUS) @@ -711,15 +711,12 @@ static int snd_als300_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver als300_driver = { From de097b51c7fd8e5b4ca86b4178aa5663becaf991 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 17:07:24 +0200 Subject: [PATCH 496/924] ALSA: aw2: Avoid manual calls of snd_card_free() at probe card error handling Instead of the manual calls of snd_card_free() at probe error handling, use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002150733.880051-3-tiwai@suse.de --- sound/pci/aw2/aw2-alsa.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/pci/aw2/aw2-alsa.c b/sound/pci/aw2/aw2-alsa.c index 60a87322eae4..513249b9bb78 100644 --- a/sound/pci/aw2/aw2-alsa.c +++ b/sound/pci/aw2/aw2-alsa.c @@ -251,7 +251,7 @@ static int snd_aw2_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct aw2 *chip; int err; @@ -273,7 +273,7 @@ static int snd_aw2_probe(struct pci_dev *pci, /* (3) Create main component */ err = snd_aw2_create(card, pci); if (err < 0) - goto error; + return err; /* initialize mutex */ mutex_init(&chip->mtx); @@ -292,17 +292,14 @@ static int snd_aw2_probe(struct pci_dev *pci, /* (6) Register card instance */ err = snd_card_register(card); if (err < 0) - goto error; + return err; /* (7) Set PCI driver data */ pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } /* open callback */ From 6bb77aa3524a8e7d674db9470df40e58719e6ad5 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 17:07:25 +0200 Subject: [PATCH 497/924] ALSA: cmipci: Avoid manual calls of snd_card_free() at probe card error handling Instead of the manual calls of snd_card_free() at probe error handling, use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002150733.880051-4-tiwai@suse.de --- sound/pci/cmipci.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c index 9d9f784e3a8c..d120b7bbe024 100644 --- a/sound/pci/cmipci.c +++ b/sound/pci/cmipci.c @@ -3170,7 +3170,7 @@ static int snd_cmipci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -3201,19 +3201,16 @@ static int snd_cmipci_probe(struct pci_dev *pci, err = snd_cmipci_create(card, pci, dev); if (err < 0) - goto error; + return err; err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } /* From 857e05376299b2689694fa5e80b4b5a7c016ef4c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 17:07:26 +0200 Subject: [PATCH 498/924] ALSA: cs46xx: Avoid manual calls of snd_card_free() at probe card error handling Instead of the manual calls of snd_card_free() at probe error handling, use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002150733.880051-5-tiwai@suse.de --- sound/pci/cs46xx/cs46xx.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/sound/pci/cs46xx/cs46xx.c b/sound/pci/cs46xx/cs46xx.c index 0cb7c2a2929f..a4a0b36290d9 100644 --- a/sound/pci/cs46xx/cs46xx.c +++ b/sound/pci/cs46xx/cs46xx.c @@ -55,7 +55,7 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_cs46xx *chip; int err; @@ -74,36 +74,36 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, err = snd_cs46xx_create(card, pci, external_amp[dev], thinkpad[dev]); if (err < 0) - goto error; + return err; card->private_data = chip; chip->accept_valid = mmap_valid[dev]; err = snd_cs46xx_pcm(chip, 0); if (err < 0) - goto error; + return err; #ifdef CONFIG_SND_CS46XX_NEW_DSP err = snd_cs46xx_pcm_rear(chip, 1); if (err < 0) - goto error; + return err; err = snd_cs46xx_pcm_iec958(chip, 2); if (err < 0) - goto error; + return err; #endif err = snd_cs46xx_mixer(chip, 2); if (err < 0) - goto error; + return err; #ifdef CONFIG_SND_CS46XX_NEW_DSP if (chip->nr_ac97_codecs ==2) { err = snd_cs46xx_pcm_center_lfe(chip, 3); if (err < 0) - goto error; + return err; } #endif err = snd_cs46xx_midi(chip, 0); if (err < 0) - goto error; + return err; err = snd_cs46xx_start_dsp(chip); if (err < 0) - goto error; + return err; snd_cs46xx_gameport(chip); @@ -117,15 +117,12 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver cs46xx_driver = { From 1f4de1bae3e28c1d510172c5403636afd1730cc7 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 17:07:27 +0200 Subject: [PATCH 499/924] ALSA: korg1212: Avoid manual calls of snd_card_free() at probe card error handling Instead of the manual calls of snd_card_free() at probe error handling, use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002150733.880051-6-tiwai@suse.de --- sound/pci/korg1212/korg1212.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/pci/korg1212/korg1212.c b/sound/pci/korg1212/korg1212.c index f4d8402f835f..bb442b556158 100644 --- a/sound/pci/korg1212/korg1212.c +++ b/sound/pci/korg1212/korg1212.c @@ -2216,7 +2216,7 @@ snd_korg1212_probe(struct pci_dev *pci, { static int dev; struct snd_korg1212 *korg1212; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) { @@ -2234,7 +2234,7 @@ snd_korg1212_probe(struct pci_dev *pci, err = snd_korg1212_create(card, pci); if (err < 0) - goto error; + return err; strscpy(card->driver, "korg1212"); strscpy(card->shortname, "korg1212"); @@ -2245,14 +2245,11 @@ snd_korg1212_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver korg1212_driver = { From 6149198d4b9d00c3e77cdb838fd12796b53b6a17 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 17:07:28 +0200 Subject: [PATCH 500/924] ALSA: lx6464es: Avoid manual calls of snd_card_free() at probe card error handling Instead of the manual calls of snd_card_free() at probe error handling, use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002150733.880051-7-tiwai@suse.de --- sound/pci/lx6464es/lx6464es.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/pci/lx6464es/lx6464es.c b/sound/pci/lx6464es/lx6464es.c index 3b40ec071379..881f462fa929 100644 --- a/sound/pci/lx6464es/lx6464es.c +++ b/sound/pci/lx6464es/lx6464es.c @@ -981,7 +981,7 @@ static int snd_lx6464es_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct lx6464es *chip; int err; @@ -1003,7 +1003,7 @@ static int snd_lx6464es_probe(struct pci_dev *pci, err = snd_lx6464es_create(card, pci); if (err < 0) { dev_err(card->dev, "error during snd_lx6464es_create\n"); - goto error; + return err; } strscpy(card->driver, "LX6464ES"); @@ -1020,16 +1020,13 @@ static int snd_lx6464es_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; dev_dbg(chip->card->dev, "initialization successful\n"); pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver lx6464es_driver = { From 77f66fd69b564ed19c1e84ef038bacd3dfd343d0 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 17:07:29 +0200 Subject: [PATCH 501/924] ALSA: hdsp: Avoid manual calls of snd_card_free() at probe card error handling Instead of the manual calls of snd_card_free() at probe error handling, use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002150733.880051-8-tiwai@suse.de --- sound/pci/rme9652/hdsp.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c index 0ade73057731..7dce548b9f36 100644 --- a/sound/pci/rme9652/hdsp.c +++ b/sound/pci/rme9652/hdsp.c @@ -5328,7 +5328,7 @@ static int snd_hdsp_probe(struct pci_dev *pci, { static int dev; struct hdsp *hdsp; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -5349,21 +5349,18 @@ static int snd_hdsp_probe(struct pci_dev *pci, hdsp->pci = pci; err = snd_hdsp_create(card, hdsp); if (err) - goto error; + return err; strscpy(card->shortname, "Hammerfall DSP"); sprintf(card->longname, "%s at 0x%lx, irq %d", hdsp->card_name, hdsp->port, hdsp->irq); err = snd_card_register(card); if (err) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver hdsp_driver = { From b06aba4d9c67fefec32e4b25e02272c697cf9364 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 17:07:30 +0200 Subject: [PATCH 502/924] ALSA: hdspm: Avoid manual calls of snd_card_free() at probe card error handling Instead of the manual calls of snd_card_free() at probe error handling, use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002150733.880051-9-tiwai@suse.de --- sound/pci/rme9652/hdspm.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c index 95fd82bb1c6d..cc4cf43e5a5b 100644 --- a/sound/pci/rme9652/hdspm.c +++ b/sound/pci/rme9652/hdspm.c @@ -6796,7 +6796,7 @@ static int snd_hdspm_probe(struct pci_dev *pci, { static int dev; struct hdspm *hdspm; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -6818,7 +6818,7 @@ static int snd_hdspm_probe(struct pci_dev *pci, err = snd_hdspm_create(card, hdspm); if (err < 0) - goto error; + return err; if (hdspm->io_type != MADIface) { snprintf(card->shortname, sizeof(card->shortname), "%s_%x", @@ -6837,16 +6837,12 @@ static int snd_hdspm_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); - + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver hdspm_driver = { From 14e702bcec7941c67fce9b233232354c4e7642a6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 2 Oct 2026 17:07:31 +0200 Subject: [PATCH 503/924] ALSA: rme9652: Avoid manual calls of snd_card_free() at probe card error handling Instead of the manual calls of snd_card_free() at probe error handling, use the auto-cleanup feature. Merely a code cleanup, and no functional changes. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261002150733.880051-10-tiwai@suse.de --- sound/pci/rme9652/rme9652.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/pci/rme9652/rme9652.c b/sound/pci/rme9652/rme9652.c index 3be30cb57a2e..46ed6b7a2c53 100644 --- a/sound/pci/rme9652/rme9652.c +++ b/sound/pci/rme9652/rme9652.c @@ -2472,7 +2472,7 @@ static int snd_rme9652_probe(struct pci_dev *pci, { static int dev; struct snd_rme9652 *rme9652; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -2494,7 +2494,7 @@ static int snd_rme9652_probe(struct pci_dev *pci, rme9652->pci = pci; err = snd_rme9652_create(card, rme9652, precise_ptr[dev]); if (err) - goto error; + return err; strscpy(card->shortname, rme9652->card_name); @@ -2502,14 +2502,11 @@ static int snd_rme9652_probe(struct pci_dev *pci, card->shortname, rme9652->port, rme9652->irq); err = snd_card_register(card); if (err) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver rme9652_driver = { From 12455e2b8c3ae5ca368f368d8169ca37e9f718be Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 1 Oct 2026 11:39:20 +0800 Subject: [PATCH 504/924] ALSA: hda: Fix speakers on ThinkBook Plus G3 IAP Lenovo ThinkBook Plus G3 IAP shares PCI SSID 17aa:3891 with Yoga Pro 7 14AHP9, but has a different codec SSID (0x17aa3875). Use HDA_CODEC_QUIRK to select ALC287_FIXUP_CS35L41_I2C_2 and add ACPI _SUB values 17AA3845/17AA3846 to the CS35L41 property table, since the BIOS provides no _DSD. Reported-by: Harry Nixon Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222053 Tested-by: Harry Nixon Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20261001033920.111061-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 4 ++++ sound/hda/codecs/side-codecs/cs35l41_hda_property.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 8e5baf02ff66..3b1e32592f36 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8384,6 +8384,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3871, "Lenovo ThinkBook 16p Gen 3 ARH", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + /* Lenovo ThinkBook Plus G3 IAP shares PCI SSID 17aa:3891 with Lenovo Yoga + * Pro 7 14AHP9; use codec SSID to distinguish them. + */ + HDA_CODEC_QUIRK(0x17aa, 0x3875, "ThinkBook Plus G3 IAP", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3877, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3878, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x387d, "Yoga S780-16 pro Quad AAC", ALC287_FIXUP_TAS2781_I2C), diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index c4f2bbed0748..272421d1bc8b 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -126,6 +126,8 @@ static const struct cs35l41_config cs35l41_config_table[] = { { "10433A60", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 1, 2, 0, 1000, 4500, 24 }, { "17AA3865", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA3866", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, + { "17AA3845", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, + { "17AA3846", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA386E", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 2, -1, 0, 0, 0 }, { "17AA386F", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA3874", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, @@ -579,6 +581,8 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "10433A40", generic_dsd_config }, { "CSC3551", "10433A50", generic_dsd_config }, { "CSC3551", "10433A60", generic_dsd_config }, + { "CSC3551", "17AA3845", generic_dsd_config }, + { "CSC3551", "17AA3846", generic_dsd_config }, { "CSC3551", "17AA3865", generic_dsd_config }, { "CSC3551", "17AA3866", generic_dsd_config }, { "CSC3551", "17AA386E", generic_dsd_config }, From 7d6adf283a83c9e4fce05a97599f8e6a006bd737 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 2 Oct 2026 12:39:52 +0100 Subject: [PATCH 505/924] ASoC: tas2781: Fix spelling mistake "config" -> "config" There are spelling mistakes in deb_dbg messages. Fix them. Signed-off-by: Colin Ian King Link: https://patch.msgid.link/20261002113953.1782731-1-colin.i.king@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2781-fmwlib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c index e8ceeb027e1b..182aa98e29c3 100644 --- a/sound/soc/codecs/tas2781-fmwlib.c +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -648,7 +648,7 @@ static int fw_parse_configuration_data_kernel( */ if (strnstr(config->name, "calib", 64)) { tas_fmw->calibration_config_id = i; - dev_dbg(tas_priv->dev, "%s: calib cofig = %d\n", + dev_dbg(tas_priv->dev, "%s: calib config = %d\n", __func__, tas_fmw->calibration_config_id); } /*skip extra 16 bytes*/ @@ -1438,7 +1438,7 @@ static int fw_parse_configuration_data( */ if (strnstr(config->name, "calib", 64)) { tas_fmw->calibration_config_id = i; - dev_dbg(tas_priv->dev, "%s: calib cofig id = %d\n", + dev_dbg(tas_priv->dev, "%s: calib config id = %d\n", __func__, tas_fmw->calibration_config_id); } From c9b0768669976a1beb0a1537b4257bd6b76dfbac Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 30 Sep 2026 20:15:33 +0900 Subject: [PATCH 506/924] selftests/alsa: Do not take another control's event as our own The wait_for_event() skips an event for a different control with a continue inside its do-while loop. But continue goes straight to the loop condition, and that tests the mask of the event just skipped. So a value event for another control ends the wait as if it were ours. A driver that notifies a second control from its put() queues that event first. The core only sends ours once put() returns. So the wait returns early and leaves ours in the queue. The next test then reports it as spurious. And event_missing passes for a control whose put() said nothing changed, as long as it notified a neighbour. To fix this, clear the mask before skipping the event. Fixes: b1446bda5645 ("kselftest: alsa: Check for event generation when we write to controls") Signed-off-by: HyeongJun An Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260930111534.3782264-2-sammiee5311@gmail.com --- tools/testing/selftests/alsa/mixer-test.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index 53a72753bb08..02136c436e16 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -267,6 +267,8 @@ static int wait_for_event(struct ctl_data *ctl, int timeout) if (ev_id != snd_ctl_elem_info_get_numid(ctl->info)) { ksft_print_msg("Event for unexpected ctl %s\n", snd_ctl_event_elem_get_name(event)); + /* The loop condition must not see the other control's mask */ + mask = 0; continue; } From 990385ba98336224bc000c7cf7b51c2fa442c780 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 30 Sep 2026 20:15:34 +0900 Subject: [PATCH 507/924] selftests/alsa: Check that a write does not silently change other controls A control whose register mask is wider than its field clobbers the field next to it. No event is sent for the neighbour, so nothing notices. The es8316 bug fixed in commit 6c1bb4031729 ("ASoC: codec: es8316: "DAC Soft Ramp Rate" is just a 2 bit control") was one. Add a test that writes each control's minimum and then its maximum. After each write it compares the other controls on the card with a snapshot. The minimum clears the field and the maximum sets its top bit, which is where a mask one bit too wide lands. Some drivers link controls on purpose, so a change announced with a value event is only logged. A silent one fails. On an HDA HDMI codec and snd-dummy, 39 controls in all, it reports nothing. A snd-dummy copy with an injected silent overlap fails, and the same overlap announced with a value event passes. Suggested-by: Mark Brown Link: https://lore.kernel.org/all/0fefbfe1-021b-46b2-8945-15956e75e37f@sirena.org.uk/ Signed-off-by: HyeongJun An Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260930111534.3782264-3-sammiee5311@gmail.com --- tools/testing/selftests/alsa/mixer-test.c | 233 +++++++++++++++++++++- 1 file changed, 232 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index 02136c436e16..966ef83354b9 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -28,7 +28,7 @@ #include "kselftest.h" #include "alsa-local.h" -#define TESTS_PER_CONTROL 7 +#define TESTS_PER_CONTROL 8 /* Suffixes of the SNDRV_CTL_NAME_IEC958() names, not exported to userspace */ #define IEC958_DEFAULT "Default" @@ -52,9 +52,14 @@ struct ctl_data { snd_ctl_elem_id_t *id; snd_ctl_elem_info_t *info; snd_ctl_elem_value_t *def_val; + snd_ctl_elem_value_t *snapshot; + bool snapshot_valid; + bool moved; int elem; int event_missing; int event_spurious; + int side_effects; + unsigned int ev_mask; struct card_data *card; struct ctl_data *next; }; @@ -159,6 +164,10 @@ static void find_controls(void) if (err < 0) ksft_exit_fail_msg("Out of memory\n"); + err = snd_ctl_elem_value_malloc(&ctl_data->snapshot); + if (err < 0) + ksft_exit_fail_msg("Out of memory\n"); + snd_ctl_elem_list_get_id(card_data->ctls, ctl, ctl_data->id); snd_ctl_elem_info_set_id(ctl_data->info, ctl_data->id); @@ -207,6 +216,20 @@ static void find_controls(void) snd_config_delete(config); } +/* The control on the same card that an event's numid refers to */ +static struct ctl_data *find_ctl_by_numid(struct card_data *card, + unsigned int numid) +{ + struct ctl_data *ctl; + + for (ctl = ctl_list; ctl != NULL; ctl = ctl->next) + if (ctl->card == card && + snd_ctl_elem_info_get_numid(ctl->info) == numid) + return ctl; + + return NULL; +} + /* * Block for up to timeout ms for an event, returns a negative value * on error, 0 for no event and 1 for an event. @@ -265,6 +288,17 @@ static int wait_for_event(struct ctl_data *ctl, int timeout) mask = snd_ctl_event_elem_get_mask(event); ev_id = snd_ctl_event_elem_get_numid(event); if (ev_id != snd_ctl_elem_info_get_numid(ctl->info)) { + struct ctl_data *other = find_ctl_by_numid(ctl->card, + ev_id); + + /* + * Remember that the driver announced this one. + * test_ctl_write_side_effects() uses that to tell a + * deliberate link from a silent register collision. + */ + if (other) + other->ev_mask |= mask; + ksft_print_msg("Event for unexpected ctl %s\n", snd_ctl_event_elem_get_name(event)); /* The loop condition must not see the other control's mask */ @@ -1149,6 +1183,202 @@ static void test_ctl_write_valid(struct ctl_data *ctl) ctl->card->card_name, ctl->elem); } +/* + * Build the smallest or the largest value the control offers. The smallest + * clears the control's register field and the largest sets its top bit, which + * is the bit a mask one bit too wide puts in its neighbour. + */ +static bool set_limit_value(struct ctl_data *ctl, snd_ctl_elem_value_t *val, + bool max) +{ + int i, count = snd_ctl_elem_info_get_count(ctl->info); + + snd_ctl_elem_value_set_id(val, ctl->id); + + switch (snd_ctl_elem_info_get_type(ctl->info)) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_boolean(val, i, max); + return true; + + case SND_CTL_ELEM_TYPE_INTEGER: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_integer(val, i, max ? + snd_ctl_elem_info_get_max(ctl->info) : + snd_ctl_elem_info_get_min(ctl->info)); + return true; + + case SND_CTL_ELEM_TYPE_INTEGER64: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_integer64(val, i, max ? + snd_ctl_elem_info_get_max64(ctl->info) : + snd_ctl_elem_info_get_min64(ctl->info)); + return true; + + case SND_CTL_ELEM_TYPE_ENUMERATED: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_enumerated(val, i, max ? + snd_ctl_elem_info_get_items(ctl->info) - 1 : 0); + return true; + + default: + /* Nothing sensible to write for the rest */ + return false; + } +} + +/* Note every control on the card that no longer reads as it did */ +static void find_moved_ctls(struct ctl_data *ctl, snd_ctl_elem_value_t *val) +{ + struct ctl_data *other; + int err; + + for (other = ctl_list; other != NULL; other = other->next) { + if (!other->snapshot_valid) + continue; + + /* + * The buffer is shared and compare() looks at all of it, so + * clear what the last control left in the slots this one + * does not use. + */ + snd_ctl_elem_value_clear(val); + snd_ctl_elem_value_set_id(val, other->id); + err = snd_ctl_elem_read(ctl->card->handle, val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n", + other->name, snd_strerror(err)); + continue; + } + + if (snd_ctl_elem_value_compare(other->snapshot, val)) + other->moved = true; + } +} + +/* + * Write one control and look for others on the same card that moved with it. + * A driver that links two controls on purpose tells userspace about both, so + * only an unannounced change is counted. That is what a control whose + * register mask covers bits belonging to its neighbour looks like from here. + */ +static void test_ctl_write_side_effects(struct ctl_data *ctl) +{ + struct ctl_data *other; + snd_ctl_elem_value_t *min_val, *max_val, *read_val; + int err; + + snd_ctl_elem_value_alloca(&min_val); + snd_ctl_elem_value_alloca(&max_val); + snd_ctl_elem_value_alloca(&read_val); + + /* Without a readable default there is nothing to put back */ + if (snd_ctl_elem_info_is_inactive(ctl->info) || + !snd_ctl_elem_info_is_writable(ctl->info) || + !snd_ctl_elem_info_is_readable(ctl->info) || + !set_limit_value(ctl, min_val, false) || + !set_limit_value(ctl, max_val, true)) { + ksft_test_result_skip("write_side_effects.%s.%d\n", + ctl->card->card_name, ctl->elem); + return; + } + + /* Drain first, a stale event would look like the driver announced it */ + drop_events(ctl); + + /* + * Record what the rest of the card reads as. A volatile control can + * move on its own so there is nothing to compare it against. + */ + for (other = ctl_list; other != NULL; other = other->next) { + other->snapshot_valid = false; + other->moved = false; + other->ev_mask = 0; + + if (other == ctl || other->card != ctl->card) + continue; + if (!snd_ctl_elem_info_is_readable(other->info) || + snd_ctl_elem_info_is_volatile(other->info)) + continue; + + snd_ctl_elem_value_clear(other->snapshot); + snd_ctl_elem_value_set_id(other->snapshot, other->id); + err = snd_ctl_elem_read(ctl->card->handle, other->snapshot); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n", + other->name, snd_strerror(err)); + continue; + } + + other->snapshot_valid = true; + } + + /* + * Compare against the snapshot after each write, before anything is + * put back. Restoring the control we wrote goes through the same + * mask, so doing it first would hide the change we are looking for. + */ + err = snd_ctl_elem_write(ctl->card->handle, min_val); + if (err >= 0) { + drop_events(ctl); + find_moved_ctls(ctl, read_val); + err = snd_ctl_elem_write(ctl->card->handle, max_val); + } + if (err < 0) { + ksft_print_msg("snd_ctl_elem_write() failed for %s: %s\n", + ctl->name, snd_strerror(err)); + } else { + drop_events(ctl); + find_moved_ctls(ctl, read_val); + } + + for (other = ctl_list; other != NULL; other = other->next) { + if (!other->moved) + continue; + + if (other->ev_mask & SND_CTL_EVENT_MASK_VALUE) { + ksft_print_msg("Writing %s changed %s, the driver said so\n", + ctl->name, other->name); + } else { + ksft_print_msg("Writing %s silently changed %s\n", + ctl->name, other->name); + ctl->side_effects++; + } + } + + /* + * The control we wrote goes back first so its mask stops moving the + * rest. A plain write keeps this out of the event counters, they + * belong to the tests that check them. + */ + snd_ctl_elem_write(ctl->card->handle, ctl->def_val); + + for (other = ctl_list; other != NULL; other = other->next) { + if (!other->snapshot_valid || + !snd_ctl_elem_info_is_writable(other->info)) + continue; + + snd_ctl_elem_value_clear(read_val); + snd_ctl_elem_value_set_id(read_val, other->id); + if (snd_ctl_elem_read(ctl->card->handle, read_val) < 0) + continue; + + if (snd_ctl_elem_value_compare(other->snapshot, read_val)) + snd_ctl_elem_write(ctl->card->handle, other->snapshot); + } + + /* Our own restores queue events, the next test must not see them */ + drop_events(ctl); + + if (err < 0) + ksft_test_result_skip("write_side_effects.%s.%d\n", + ctl->card->card_name, ctl->elem); + else + ksft_test_result(!ctl->side_effects, + "write_side_effects.%s.%d\n", + ctl->card->card_name, ctl->elem); +} + static bool test_ctl_write_invalid_value(struct ctl_data *ctl, snd_ctl_elem_value_t *val) { @@ -1392,6 +1622,7 @@ int main(void) test_ctl_name(ctl); test_ctl_write_default(ctl); test_ctl_write_valid(ctl); + test_ctl_write_side_effects(ctl); test_ctl_write_invalid(ctl); test_ctl_event_missing(ctl); test_ctl_event_spurious(ctl); From 6541871e8e63e721733cd120af4d9cd22c5ef62f Mon Sep 17 00:00:00 2001 From: Sung Byeongchan Date: Sat, 3 Oct 2026 19:18:54 +0900 Subject: [PATCH 508/924] ALSA: pcmtest: handle multiple wraps in random capture fill The random capture helpers assume that the data generated for one timer tick wraps the DMA ring at most once. However, b_rw describes 200 ms of audio because the timer runs at 5 Hz, so it can be several times larger than a small ring. For 48 kHz, S16_LE, four-channel capture with a 512-frame buffer, the DMA ring is 4096 bytes while one tick generates 76800 bytes. After filling the 4096-byte tail, the existing code issues a second 72704-byte write from the ring base, writing 68608 bytes beyond the ring. Both interleaved and non-interleaved capture are affected. KASAN reports the first write into the adjacent free page as a use-after-free in the random-byte generator. The underlying defect is an out-of-bounds write from the live DMA ring, not a stale DMA allocation. Split each random write repeatedly at the interleaved ring or per-channel block boundary. Keep the hardware-pointer update exactly once after the complete logical write. The issue reproduced on three of three clean boots. With this change, the same vulnerable geometry completed six times without a sanitizer finding. Boundary, pattern-fill, no-timer, open/close, and module-reload controls also completed normally. snd-pcmtest is a test driver, and selecting random fill requires an administrator-controlled module parameter. The written values come from the kernel random generator; no privilege escalation or attacker-controlled write primitive was demonstrated. Fixes: 315a3d57c64c ("ALSA: Implement the new Virtual PCM Test Driver") Cc: stable@vger.kernel.org Assisted-by: OpenAI Codex Signed-off-by: Sung Byeongchan Link: https://patch.msgid.link/20261003101854.1423146-1-tjdqudcks0424@naver.com Signed-off-by: Takashi Iwai --- sound/drivers/pcmtest.c | 42 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c index 5d5281e4deb7..186e982d42e1 100644 --- a/sound/drivers/pcmtest.c +++ b/sound/drivers/pcmtest.c @@ -280,39 +280,35 @@ static void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_ru fill_block_pattern_n(v_iter, runtime); } +static void fill_random_block(char *buf, size_t buf_size, size_t pos, + size_t count) +{ + while (count) { + size_t chunk = min(count, buf_size - pos); + + get_random_bytes(buf + pos, chunk); + count -= chunk; + pos = 0; + } +} + static void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) { unsigned int channels = runtime->channels; - // Remaining space in all channel buffers - size_t bytes_remain = runtime->dma_bytes - v_iter->buf_pos; + size_t pos = v_iter->buf_pos / channels; unsigned int i; - for (i = 0; i < channels; i++) { - if (v_iter->b_rw <= bytes_remain) { - //b_rw - count of bytes must be written for all channels at each timer tick - get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), - v_iter->b_rw / channels); - } else { - // Write to the end of buffer and start from the beginning of it - get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), - bytes_remain / channels); - get_random_bytes(runtime->dma_area + v_iter->chan_block * i, - (v_iter->b_rw - bytes_remain) / channels); - } - } + for (i = 0; i < channels; i++) + fill_random_block(runtime->dma_area + v_iter->chan_block * i, + v_iter->chan_block, pos, + v_iter->b_rw / channels); inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); } static void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) { - size_t in_cur_block = runtime->dma_bytes - v_iter->buf_pos; - - if (v_iter->b_rw <= in_cur_block) { - get_random_bytes(&runtime->dma_area[v_iter->buf_pos], v_iter->b_rw); - } else { - get_random_bytes(&runtime->dma_area[v_iter->buf_pos], in_cur_block); - get_random_bytes(runtime->dma_area, v_iter->b_rw - in_cur_block); - } + fill_random_block(runtime->dma_area, runtime->dma_bytes, + v_iter->buf_pos, v_iter->b_rw); inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); } From 07188c32ad967e1fd2a1ce43aecd1c7654f85c95 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 3 Oct 2026 12:57:08 +0200 Subject: [PATCH 509/924] ALSA: mips: hal2: Use devres card management The hal2 driver's remove callback is nothing but a simple snd_card_free() call, so let's use the devres for managing the card, for simplifying the handling. Now the card is created via snd_devm_card_new() that is auto-cleaned up at probe for errors. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261003105719.1070427-2-tiwai@suse.de --- sound/mips/hal2.c | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/sound/mips/hal2.c b/sound/mips/hal2.c index 84f265335cf9..6decbaa5800e 100644 --- a/sound/mips/hal2.c +++ b/sound/mips/hal2.c @@ -837,30 +837,24 @@ static int hal2_create(struct snd_card *card, struct snd_hal2 **rchip) static int hal2_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_hal2 *chip; int err; - err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = hal2_create(card, &chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = hal2_pcm_create(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = hal2_mixer_create(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } strscpy(card->driver, "SGI HAL2 Audio"); strscpy(card->shortname, "SGI HAL2 Audio"); @@ -869,24 +863,16 @@ static int hal2_probe(struct platform_device *pdev) SGI_HPCDMA_IRQ); err = snd_card_register(card); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } + platform_set_drvdata(pdev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static void hal2_remove(struct platform_device *pdev) -{ - struct snd_card *card = platform_get_drvdata(pdev); - - snd_card_free(card); -} - static struct platform_driver hal2_driver = { .probe = hal2_probe, - .remove = hal2_remove, .driver = { .name = "sgihal2", } From 10290ed901cc19802d8bc998dbb27e2de1a6ec27 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 3 Oct 2026 12:57:09 +0200 Subject: [PATCH 510/924] ALSA: mips: sgio2audio: Use devres card management The sgio2audio driver's remove callback is nothing but a simple snd_card_free() call, so let's use the devres for managing the card, for simplifying the handling. Now the card is created via snd_devm_card_new() that is auto-cleaned up at probe for errors. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261003105719.1070427-3-tiwai@suse.de --- sound/mips/sgio2audio.c | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/sound/mips/sgio2audio.c b/sound/mips/sgio2audio.c index 35648b0b97b9..25c1871f72c1 100644 --- a/sound/mips/sgio2audio.c +++ b/sound/mips/sgio2audio.c @@ -859,30 +859,24 @@ static int snd_sgio2audio_create(struct snd_card *card, static int snd_sgio2audio_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_sgio2audio *chip; int err; - err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = snd_sgio2audio_create(card, &chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = snd_sgio2audio_new_pcm(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = snd_sgio2audio_new_mixer(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } strscpy(card->driver, "SGI O2 Audio"); strscpy(card->shortname, "SGI O2 Audio"); @@ -892,24 +886,16 @@ static int snd_sgio2audio_probe(struct platform_device *pdev) MACEISA_AUDIO3_MERR_IRQ); err = snd_card_register(card); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } + platform_set_drvdata(pdev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static void snd_sgio2audio_remove(struct platform_device *pdev) -{ - struct snd_card *card = platform_get_drvdata(pdev); - - snd_card_free(card); -} - static struct platform_driver sgio2audio_driver = { .probe = snd_sgio2audio_probe, - .remove = snd_sgio2audio_remove, .driver = { .name = "sgio2audio", } From e81f85fd3ca663eb005fba7ce34080180e94ae53 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 3 Oct 2026 12:57:10 +0200 Subject: [PATCH 511/924] ALSA: mips: n64: Handle card removal properly The remove callback for snd-n64 is completely missing by some reason, so let's add it for consistency. Along with it, clean up the error handling at probe by replacing as well by the devres-managed card object and auto-cleanup. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261003105719.1070427-4-tiwai@suse.de --- sound/mips/snd-n64.c | 61 ++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/sound/mips/snd-n64.c b/sound/mips/snd-n64.c index f17e63f2ff5a..8cc001c58d5e 100644 --- a/sound/mips/snd-n64.c +++ b/sound/mips/snd-n64.c @@ -273,6 +273,15 @@ static const struct snd_pcm_ops n64audio_pcm_ops = { .close = n64audio_pcm_close, }; +static void n64audio_free(struct snd_card *card) +{ + struct n64audio *priv = card->private_data; + + if (priv->ring_base) + dma_free_coherent(card->dev, 32 * 1024, priv->ring_base, + priv->ring_base_dma); +} + /* * The target device is embedded and RAM-constrained. We save RAM * by initializing in __init code that gets dropped late in boot. @@ -280,14 +289,14 @@ static const struct snd_pcm_ops n64audio_pcm_ops = { */ static int __init n64audio_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_pcm *pcm; struct n64audio *priv; int err, irq; - err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, - SNDRV_DEFAULT_STR1, - THIS_MODULE, sizeof(*priv), &card); + err = snd_devm_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, + THIS_MODULE, sizeof(*priv), &card); if (err < 0) return err; @@ -296,29 +305,24 @@ static int __init n64audio_probe(struct platform_device *pdev) spin_lock_init(&priv->chan.lock); priv->card = card; + card->private_free = n64audio_free; priv->ring_base = dma_alloc_coherent(card->dev, 32 * 1024, &priv->ring_base_dma, GFP_DMA|GFP_KERNEL); - if (!priv->ring_base) { - err = -ENOMEM; - goto fail_card; - } + if (!priv->ring_base) + return -ENOMEM; priv->mi_reg_base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(priv->mi_reg_base)) { - err = PTR_ERR(priv->mi_reg_base); - goto fail_dma_alloc; - } + if (IS_ERR(priv->mi_reg_base)) + return PTR_ERR(priv->mi_reg_base); priv->ai_reg_base = devm_platform_ioremap_resource(pdev, 1); - if (IS_ERR(priv->ai_reg_base)) { - err = PTR_ERR(priv->ai_reg_base); - goto fail_dma_alloc; - } + if (IS_ERR(priv->ai_reg_base)) + return PTR_ERR(priv->ai_reg_base); err = snd_pcm_new(card, "N64 Audio", 0, 1, 0, &pcm); if (err < 0) - goto fail_dma_alloc; + return err; pcm->private_data = priv; strscpy(pcm->name, "N64 Audio"); @@ -331,28 +335,19 @@ static int __init n64audio_probe(struct platform_device *pdev) strscpy(card->longname, "N64 Audio"); irq = platform_get_irq(pdev, 0); - if (irq < 0) { - err = -EINVAL; - goto fail_dma_alloc; - } + if (irq < 0) + return -EINVAL; + if (devm_request_irq(&pdev->dev, irq, n64audio_isr, - IRQF_SHARED, "N64 Audio", priv)) { - err = -EBUSY; - goto fail_dma_alloc; - } + IRQF_SHARED, "N64 Audio", priv)) + return -EBUSY; err = snd_card_register(card); if (err < 0) - goto fail_dma_alloc; + return err; + card = NULL; /* probe succeeded, don't release as error */ return 0; - -fail_dma_alloc: - dma_free_coherent(card->dev, 32 * 1024, priv->ring_base, priv->ring_base_dma); - -fail_card: - snd_card_free(card); - return err; } static struct platform_driver n64audio_driver = { From 519dd2e50cd7c8626b911fad2dfca65df3058e67 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 3 Oct 2026 12:57:11 +0200 Subject: [PATCH 512/924] ALSA: parisc: harmony: Use devres card management The harmony driver's remove callback is nothing but a simple snd_card_free() call, so let's use the devres for managing the card, for simplifying the handling. Now the card is created via snd_devm_card_new() that is auto-cleaned up at probe for errors. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261003105719.1070427-5-tiwai@suse.de --- sound/parisc/harmony.c | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/sound/parisc/harmony.c b/sound/parisc/harmony.c index a9625aedf2a4..6cdd09f5855b 100644 --- a/sound/parisc/harmony.c +++ b/sound/parisc/harmony.c @@ -897,24 +897,24 @@ static int __init snd_harmony_probe(struct parisc_device *padev) { int err; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_harmony *h; - err = snd_card_new(&padev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&padev->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = snd_harmony_create(card, padev, &h); if (err < 0) - goto free_and_ret; + return err; err = snd_harmony_pcm_init(h); if (err < 0) - goto free_and_ret; + return err; err = snd_harmony_mixer_init(h); if (err < 0) - goto free_and_ret; + return err; strscpy(card->driver, "harmony"); strscpy(card->shortname, "Harmony"); @@ -923,27 +923,17 @@ snd_harmony_probe(struct parisc_device *padev) err = snd_card_register(card); if (err < 0) - goto free_and_ret; + return err; parisc_set_drvdata(padev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; - -free_and_ret: - snd_card_free(card); - return err; -} - -static void __exit -snd_harmony_remove(struct parisc_device *padev) -{ - snd_card_free(parisc_get_drvdata(padev)); } static struct parisc_driver snd_harmony_driver __refdata = { .name = "harmony", .id_table = snd_harmony_devtable, .probe = snd_harmony_probe, - .remove = __exit_p(snd_harmony_remove), }; static int __init From d622afd70f2dc66d787bd67f9cdfce47b4a45094 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 3 Oct 2026 12:57:12 +0200 Subject: [PATCH 513/924] ALSA: ctxfi: Use devres card management The ctxfi driver's remove callback is nothing but a simple snd_card_free() call, so let's use the devres for managing the card, for simplifying the handling. Now the card is created via snd_devm_card_new() that is auto-cleaned up at probe for errors. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261003105719.1070427-6-tiwai@suse.de --- sound/pci/ctxfi/xfi.c | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/sound/pci/ctxfi/xfi.c b/sound/pci/ctxfi/xfi.c index d8dd84d41c87..99319d75e963 100644 --- a/sound/pci/ctxfi/xfi.c +++ b/sound/pci/ctxfi/xfi.c @@ -56,7 +56,7 @@ static int ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct ct_atc *atc; int err; @@ -67,8 +67,8 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) dev++; return -ENOENT; } - err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, - 0, &card); + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); if (err) return err; if ((reference_rate != 48000) && (reference_rate != 44100)) { @@ -89,14 +89,14 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) err = ct_atc_create(card, pci, reference_rate, multiple, pci_id->driver_data, subsystem[dev], &atc); if (err < 0) - goto error; + return err; card->private_data = atc; /* Create alsa devices supported by this card */ err = ct_atc_create_alsa_devs(atc); if (err < 0) - goto error; + return err; strscpy(card->driver, "SB-XFi"); strscpy(card->shortname, "Creative X-Fi"); @@ -105,21 +105,13 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - -error: - snd_card_free(card); - return err; -} - -static void ct_card_remove(struct pci_dev *pci) -{ - snd_card_free(pci_get_drvdata(pci)); } #ifdef CONFIG_PM_SLEEP @@ -149,7 +141,6 @@ static struct pci_driver ct_driver = { .name = KBUILD_MODNAME, .id_table = ct_pci_dev_ids, .probe = ct_card_probe, - .remove = ct_card_remove, .driver = { .pm = CT_CARD_PM_OPS, }, From e0d90f5f23aa5ced7f77688c9b47048bdef2b783 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 3 Oct 2026 12:57:13 +0200 Subject: [PATCH 514/924] ALSA: powermac: Use devres card management The powermac driver's remove callback is nothing but a simple snd_card_free() call, so let's use the devres for managing the card, for simplifying the handling. Now the card is created via snd_devm_card_new() that is auto-cleaned up at probe for errors. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261003105719.1070427-7-tiwai@suse.de --- sound/ppc/powermac.c | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/sound/ppc/powermac.c b/sound/ppc/powermac.c index e685d245883e..30f4cd1a6d32 100644 --- a/sound/ppc/powermac.c +++ b/sound/ppc/powermac.c @@ -39,18 +39,18 @@ static struct platform_device *device; static int snd_pmac_probe(struct platform_device *devptr) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_pmac *chip; char *name_ext; int err; - err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = snd_pmac_new(card, &chip); if (err < 0) - goto __error; + return err; card->private_data = chip; switch (chip->model) { @@ -61,7 +61,7 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, chip->device_id, chip->subframe); err = snd_pmac_burgundy_init(chip); if (err < 0) - goto __error; + return err; break; case PMAC_DACA: strscpy(card->driver, "PMac DACA"); @@ -70,7 +70,7 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, chip->device_id, chip->subframe); err = snd_pmac_daca_init(chip); if (err < 0) - goto __error; + return err; break; case PMAC_TUMBLER: case PMAC_SNAPPER: @@ -81,10 +81,10 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, chip->device_id, chip->subframe); err = snd_pmac_tumbler_init(chip); if (err < 0) - goto __error; + return err; err = snd_pmac_tumbler_post_init(); if (err < 0) - goto __error; + return err; break; case PMAC_AWACS: case PMAC_SCREAMER: @@ -101,17 +101,16 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, name_ext, chip->revision); err = snd_pmac_awacs_init(chip); if (err < 0) - goto __error; + return err; break; default: dev_err(&devptr->dev, "unsupported hardware %d\n", chip->model); - err = -EINVAL; - goto __error; + return -EINVAL; } err = snd_pmac_pcm_new(chip); if (err < 0) - goto __error; + return err; chip->initialized = 1; if (enable_beep) @@ -119,20 +118,11 @@ static int snd_pmac_probe(struct platform_device *devptr) err = snd_card_register(card); if (err < 0) - goto __error; + return err; platform_set_drvdata(devptr, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; - -__error: - snd_card_free(card); - return err; -} - - -static void snd_pmac_remove(struct platform_device *devptr) -{ - snd_card_free(platform_get_drvdata(devptr)); } #ifdef CONFIG_PM_SLEEP @@ -160,7 +150,6 @@ static SIMPLE_DEV_PM_OPS(snd_pmac_pm, snd_pmac_driver_suspend, snd_pmac_driver_r static struct platform_driver snd_pmac_driver = { .probe = snd_pmac_probe, - .remove = snd_pmac_remove, .driver = { .name = SND_PMAC_DRIVER, .pm = SND_PMAC_PM_OPS, From 0b739c1882c63a7573710d01e3402c6a67a1538c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 3 Oct 2026 12:57:14 +0200 Subject: [PATCH 515/924] ALSA: sh: Use devres card management The SH DAC driver's remove callback is nothing but a simple snd_card_free() call, so let's use the devres for managing the card, for simplifying the handling. Now the card is created via snd_devm_card_new() that is auto-cleaned up at probe for errors. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261003105719.1070427-8-tiwai@suse.de --- sound/sh/sh_dac_audio.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/sound/sh/sh_dac_audio.c b/sound/sh/sh_dac_audio.c index 2d9a2d5b824e..5896cce424af 100644 --- a/sound/sh/sh_dac_audio.c +++ b/sound/sh/sh_dac_audio.c @@ -236,12 +236,6 @@ static int snd_sh_dac_pcm(struct snd_sh_dac *chip, int device) /* END OF PCM INTERFACE */ -/* driver .remove -- destructor */ -static void snd_sh_dac_remove(struct platform_device *devptr) -{ - snd_card_free(platform_get_drvdata(devptr)); -} - /* free -- it has been defined by create */ static int snd_sh_dac_free(struct snd_sh_dac *chip) { @@ -341,10 +335,10 @@ static int snd_sh_dac_create(struct snd_card *card, static int snd_sh_dac_probe(struct platform_device *devptr) { struct snd_sh_dac *chip; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; - err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) { dev_err(&devptr->dev, "cannot allocate the card\n"); return err; @@ -352,11 +346,11 @@ static int snd_sh_dac_probe(struct platform_device *devptr) err = snd_sh_dac_create(card, devptr, &chip); if (err < 0) - goto probe_error; + return err; err = snd_sh_dac_pcm(chip, 0); if (err < 0) - goto probe_error; + return err; strscpy(card->driver, "snd_sh_dac"); strscpy(card->shortname, "SuperH DAC audio driver"); @@ -364,16 +358,13 @@ static int snd_sh_dac_probe(struct platform_device *devptr) err = snd_card_register(card); if (err < 0) - goto probe_error; + return err; dev_info(&devptr->dev, "ALSA driver for SuperH DAC audio\n"); platform_set_drvdata(devptr, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; - -probe_error: - snd_card_free(card); - return err; } /* @@ -381,7 +372,6 @@ static int snd_sh_dac_probe(struct platform_device *devptr) */ static struct platform_driver sh_dac_driver = { .probe = snd_sh_dac_probe, - .remove = snd_sh_dac_remove, .driver = { .name = "dac_audio", }, From 1e3d2b51a28606163a0fa9dbb728b676280269e5 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 3 Oct 2026 12:57:15 +0200 Subject: [PATCH 516/924] ALSA: sparc: cs4231: Use devres card management The cs4231 driver's remove callback is nothing but a simple snd_card_free() call, so let's use the devres for managing the card, for simplifying the handling. Now the card is created via snd_devm_card_new() that is auto-cleaned up at probe for errors. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261003105719.1070427-9-tiwai@suse.de --- sound/sparc/cs4231.c | 49 +++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/sound/sparc/cs4231.c b/sound/sparc/cs4231.c index d9e5cca94c73..0489eac165c9 100644 --- a/sound/sparc/cs4231.c +++ b/sound/sparc/cs4231.c @@ -1497,8 +1497,8 @@ static int cs4231_attach_begin(struct platform_device *op, return -ENOENT; } - err = snd_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, - sizeof(struct snd_cs4231), &card); + err = snd_devm_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_cs4231), &card); if (err < 0) return err; @@ -1519,28 +1519,24 @@ static int cs4231_attach_finish(struct snd_card *card) err = snd_cs4231_pcm(card); if (err < 0) - goto out_err; + return err; err = snd_cs4231_mixer(card); if (err < 0) - goto out_err; + return err; err = snd_cs4231_timer(card); if (err < 0) - goto out_err; + return err; err = snd_card_register(card); if (err < 0) - goto out_err; + return err; dev_set_drvdata(&chip->op->dev, chip); dev++; return 0; - -out_err: - snd_card_free(card); - return err; } #ifdef SBUS_SUPPORT @@ -1771,7 +1767,7 @@ static int snd_cs4231_sbus_create(struct snd_card *card, static int cs4231_sbus_probe(struct platform_device *op) { struct resource *rp = &op->resource[0]; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; err = cs4231_attach_begin(op, &card); @@ -1785,12 +1781,14 @@ static int cs4231_sbus_probe(struct platform_device *op) op->archdata.irqs[0]); err = snd_cs4231_sbus_create(card, op, dev); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } - return cs4231_attach_finish(card); + err = cs4231_attach_finish(card); + if (err < 0) + return err; + card = NULL; /* probe succeeded, don't release as error */ + return 0; } #endif @@ -1968,7 +1966,7 @@ static int snd_cs4231_ebus_create(struct snd_card *card, static int cs4231_ebus_probe(struct platform_device *op) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; err = cs4231_attach_begin(op, &card); @@ -1981,12 +1979,15 @@ static int cs4231_ebus_probe(struct platform_device *op) op->archdata.irqs[0]); err = snd_cs4231_ebus_create(card, op, dev); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } - return cs4231_attach_finish(card); + err = cs4231_attach_finish(card); + if (err < 0) + return err; + + card = NULL; /* probe succeeded, don't release as error */ + return 0; } #endif @@ -2004,13 +2005,6 @@ static int cs4231_probe(struct platform_device *op) return -ENODEV; } -static void cs4231_remove(struct platform_device *op) -{ - struct snd_cs4231 *chip = dev_get_drvdata(&op->dev); - - snd_card_free(chip->card); -} - static const struct of_device_id cs4231_match[] = { { .name = "SUNW,CS4231", @@ -2030,7 +2024,6 @@ static struct platform_driver cs4231_driver = { .of_match_table = cs4231_match, }, .probe = cs4231_probe, - .remove = cs4231_remove, }; module_platform_driver(cs4231_driver); From 89bc6a3549f1c29e0c577b0c3ad8e3f810a3c1b5 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 3 Oct 2026 12:57:16 +0200 Subject: [PATCH 517/924] ALSA: sparc: dbri: Use devres card management The dbri driver's remove callback is nothing but a simple snd_card_free() call in addition to the snd_dbri_free() call, and the latter can be gracefully handled by card's private_free callback. For the code simplification, let's use the devres for managing the card. Now the card is created via snd_devm_card_new() that is auto-cleaned up at probe for errors. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261003105719.1070427-10-tiwai@suse.de --- sound/sparc/dbri.c | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c index ccaa36525f8d..9fad4102dc70 100644 --- a/sound/sparc/dbri.c +++ b/sound/sparc/dbri.c @@ -2576,11 +2576,16 @@ static void snd_dbri_free(struct snd_dbri *dbri) (void *)dbri->dma, dbri->dma_dvma); } +static void dbri_card_free(struct snd_card *card) +{ + snd_dbri_free(card->private_data); +} + static int dbri_probe(struct platform_device *op) { struct snd_dbri *dbri; struct resource *rp; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; static int dev; int irq; int err; @@ -2598,8 +2603,8 @@ static int dbri_probe(struct platform_device *op) return -ENODEV; } - err = snd_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, - sizeof(struct snd_dbri), &card); + err = snd_devm_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_dbri), &card); if (err < 0) return err; @@ -2609,21 +2614,20 @@ static int dbri_probe(struct platform_device *op) sprintf(card->longname, "%s at 0x%02lx:0x%016llx, irq %d", card->shortname, rp->flags & 0xffL, (unsigned long long)rp->start, irq); + card->private_free = dbri_card_free; err = snd_dbri_create(card, op, irq, dev); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } dbri = card->private_data; err = snd_dbri_pcm(card); if (err < 0) - goto _err; + return err; err = snd_dbri_mixer(card); if (err < 0) - goto _err; + return err; /* /proc file handling */ snd_dbri_proc(card); @@ -2631,27 +2635,15 @@ static int dbri_probe(struct platform_device *op) err = snd_card_register(card); if (err < 0) - goto _err; + return err; printk(KERN_INFO "audio%d at %p (irq %d) is DBRI(%c)+CS4215(%d)\n", dev, dbri->regs, dbri->irq, op->dev.of_node->name[9], dbri->mm.version); dev++; + card = NULL; /* probe succeeded, don't release as error */ return 0; - -_err: - snd_dbri_free(dbri); - snd_card_free(card); - return err; -} - -static void dbri_remove(struct platform_device *op) -{ - struct snd_card *card = dev_get_drvdata(&op->dev); - - snd_dbri_free(card->private_data); - snd_card_free(card); } static const struct of_device_id dbri_match[] = { @@ -2672,7 +2664,6 @@ static struct platform_driver dbri_sbus_driver = { .of_match_table = dbri_match, }, .probe = dbri_probe, - .remove = dbri_remove, }; module_platform_driver(dbri_sbus_driver); From af53cdc10cdea83e00dd6ae0f263ea4897f83947 Mon Sep 17 00:00:00 2001 From: Denis Yakovlev Date: Sat, 3 Oct 2026 16:33:25 +0300 Subject: [PATCH 518/924] ALSA: hda/realtek: Enable headset mic on HONOR MagicBook Pro 16 2024 The HONOR MagicBook Pro 16 2024 (DRA-XX, board M1020, ALC256, SSID 1ee7:204e) has a combined 3.5 mm headphone/microphone jack. The BIOS leaves the headset mic pin 0x19 at 0x411111f0, so the codec parser builds no capture path for it, and it leaves the jack switch (coefficient 0x45) in the TRS state, which cuts the microphone contact off from the codec. A wired headset microphone therefore records nothing. Configure 0x19 as a headset microphone (0x03a1113c, "headset mic without its own jack detect"), the same value already used for the sibling M1020 board (HONOR MRB-XXX) and by the VAIO quirks, and chain to the generic headset mode handler, which detects the CTIA/OMTP headset type on every jack event and programs the jack switch accordingly. Pin 0x1a is left untouched: there is no evidence that it is wired on this board. Fixes: 1e3e378d63be ("ALSA: hda/realtek: Enable bass speakers on HONOR MagicBook Pro 16 2024") Assisted-by: LLM Signed-off-by: Denis Yakovlev Link: https://patch.msgid.link/20261003133325.97507-1-mfthesun@vivaldi.net Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 3b1e32592f36..4f5537cabb00 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4477,6 +4477,7 @@ enum { ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS, ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC, + ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE, ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX, ALC287_FIXUP_ACER_MICMUTE_LED, ALC236_FIXUP_DELL_HP_POP_NOISE, @@ -7215,6 +7216,7 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { { 0x14, 0x90170111 }, /* bass speakers */ + { 0x19, 0x03a1113c }, /* headset mic, without its own jack detect */ { } }, .chained = true, @@ -7223,6 +7225,12 @@ static const struct hda_fixup alc269_fixups[] = { [ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC] = { .type = HDA_FIXUP_FUNC, .v.func = alc256_fixup_honor_dra_xx_share_dac, + .chained = true, + .chain_id = ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE + }, + [ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode, }, [ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX] = { .type = HDA_FIXUP_FUNC, From ded106036984a9dd766e7e1451b02e2d15c81438 Mon Sep 17 00:00:00 2001 From: Henrik Enquist Date: Sat, 3 Oct 2026 15:26:11 +0200 Subject: [PATCH 519/924] ALSA: aloop: Don't constrain playback params in notify mode With notify enabled (the pcm_notify module parameter or the "PCM Notify" control), the playback side of a cable may change format, rate and channels while a capture is running. loopback_check_format() then stops the capture and notifies the "PCM Slave" controls, so that the capture application can reopen with the new parameters. For this reason loopback_open() still gives a notify mode playback the unconstrained loopback_pcm_hardware. Since commit 898dfe4687f4 ("ALSA: aloop: Fix racy hw constraints adjustment") the hw rules refine against the shared cable->hw instead of the substream's own runtime->hw. This pins the playback side to the parameters of an open capture whatever the notify setting is. A player probing the device sees only the capture's rate, format and channels, and the format change path in loopback_check_format() can't be reached by a new playback. Skip the format, rate and channels rules for the playback substream when notify is enabled. Skip the period bytes rule as well: with a sound timer source, params_change() already takes the period size from a notify mode playback, and keeping the old capture's period bytes leaves a playback at another rate with a period that doesn't match the timer. The capture side stays constrained to the cable parameters. The capture stop this makes reachable again was hardened by commit 826af7fa62e3 ("ALSA: aloop: Fix racy access at PCM trigger") and commit e5c33cdc6f40 ("ALSA: aloop: Fix peer runtime UAF during format-change stop"), which need to be applied first when backporting. Reproducer, with notify enabled on the cable: arecord -D hw:Loopback,1,0 -r 44100 -f S32_LE -c 2 /dev/null & aplay -D hw:Loopback,0,0 96k.wav Before: aplay is forced to 44100 Hz and the capture keeps running. After: aplay runs at 96000 Hz, the capture is stopped and "PCM Slave Rate" reports 96000. Fixes: 898dfe4687f4 ("ALSA: aloop: Fix racy hw constraints adjustment") Reported-by: Pavel Hofman Closes: https://lore.kernel.org/all/b4af9071-f8d7-5b47-4d7a-c5743bd67394@ivitera.com/ Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Henrik Enquist Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261003-aloop-notify-fix-v1-1-ba5dfb831bf5@gmail.com --- sound/drivers/aloop.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index 81cb1f59f703..5f0e2dc96769 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -1073,6 +1073,18 @@ static unsigned int get_cable_index(struct snd_pcm_substream *substream) return !substream->stream; } +/* + * In notify mode the playback side may change format, rate, channels and, + * with a sound timer, the period size. A running capture that no longer + * matches is stopped by loopback_check_format(). + * call in loopback->cable_lock + */ +static bool loopback_playback_unconstrained(struct loopback_pcm *dpcm) +{ + return dpcm->substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + get_notify(dpcm); +} + static int rule_format(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { @@ -1082,6 +1094,8 @@ static int rule_format(struct snd_pcm_hw_params *params, snd_mask_none(&m); scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; m.bits[0] = (u_int32_t)cable->hw.formats; m.bits[1] = (u_int32_t)(cable->hw.formats >> 32); } @@ -1096,6 +1110,8 @@ static int rule_rate(struct snd_pcm_hw_params *params, struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; t.min = cable->hw.rate_min; t.max = cable->hw.rate_max; } @@ -1112,6 +1128,8 @@ static int rule_channels(struct snd_pcm_hw_params *params, struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; t.min = cable->hw.channels_min; t.max = cable->hw.channels_max; } @@ -1128,6 +1146,8 @@ static int rule_period_bytes(struct snd_pcm_hw_params *params, struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; t.min = cable->hw.period_bytes_min; t.max = cable->hw.period_bytes_max; } From 48eb74077beb6f2310b817ab2b033c0352cd1e49 Mon Sep 17 00:00:00 2001 From: Henrik Enquist Date: Sat, 3 Oct 2026 15:26:12 +0200 Subject: [PATCH 520/924] selftests/alsa: Add a test for snd-aloop notify mode Add a test for the hw constraints between the playback and capture devices of an snd-aloop cable, and for the "PCM Slave" controls that report the playback side's parameters to the capture application. With notify disabled, a playback opened while a capture is set up must be pinned to the capture's parameters. With notify enabled, it may pick others. Starting it stops the running capture, and the "PCM Slave Format", "Rate", "Channels" and "Access Mode" controls report the new parameters, each with a value event. A capture opened second is pinned to the playback either way. The tests only check hw_refine results, stream states and control values, so they don't depend on timing. They skip if snd-aloop isn't loaded or the devices are in use. Without "ALSA: aloop: Don't constrain playback params in notify mode", the two notify mode tests fail. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Henrik Enquist Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261003-aloop-notify-fix-v1-2-ba5dfb831bf5@gmail.com --- tools/testing/selftests/alsa/.gitignore | 1 + tools/testing/selftests/alsa/Makefile | 2 +- tools/testing/selftests/alsa/aloop-test.c | 345 ++++++++++++++++++++++ 3 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/alsa/aloop-test.c diff --git a/tools/testing/selftests/alsa/.gitignore b/tools/testing/selftests/alsa/.gitignore index 3dd8e1176b89..7b0e1e9ebf1b 100644 --- a/tools/testing/selftests/alsa/.gitignore +++ b/tools/testing/selftests/alsa/.gitignore @@ -1,3 +1,4 @@ +aloop-test global-timer mixer-test pcm-test diff --git a/tools/testing/selftests/alsa/Makefile b/tools/testing/selftests/alsa/Makefile index 8dab90ad22bb..afd64a679dc1 100644 --- a/tools/testing/selftests/alsa/Makefile +++ b/tools/testing/selftests/alsa/Makefile @@ -16,7 +16,7 @@ LDLIBS+=-lpthread OVERRIDE_TARGETS = 1 -TEST_GEN_PROGS := mixer-test pcm-test test-pcmtest-driver utimer-test +TEST_GEN_PROGS := aloop-test mixer-test pcm-test test-pcmtest-driver utimer-test TEST_GEN_PROGS_EXTENDED := libatest.so global-timer diff --git a/tools/testing/selftests/alsa/aloop-test.c b/tools/testing/selftests/alsa/aloop-test.c new file mode 100644 index 000000000000..a58b9fdbdc17 --- /dev/null +++ b/tools/testing/selftests/alsa/aloop-test.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Tests for the hw constraints between the two ends of an snd-aloop cable, + * with and without the "PCM Notify" control, and for the "PCM Slave" + * controls that report the playback side's parameters. + * + * Needs snd-aloop loaded with the default card id "Loopback". The tests use + * the cable between hw:Loopback,0,0 (playback) and hw:Loopback,1,0 (capture). + */ +#include +#include +#include +#include +#include +#include "kselftest_harness.h" + +#define FRAMES 1024 +#define MAX_CHANNELS 4 + +struct stream_params { + snd_pcm_access_t access; + snd_pcm_format_t format; + unsigned int channels; + unsigned int rate; +}; + +/* The playback params differ from the capture params in every field. */ +static const struct stream_params capture_params = { + SND_PCM_ACCESS_RW_INTERLEAVED, SND_PCM_FORMAT_S16_LE, 2, 44100 +}; + +static const struct stream_params playback_params = { + SND_PCM_ACCESS_RW_NONINTERLEAVED, SND_PCM_FORMAT_S32_LE, 4, 96000 +}; + +struct probe_result { + unsigned int rate_min, rate_max; + unsigned int channels_min, channels_max; + bool format_ok; +}; + +/* Value events seen on the cable's controls */ +enum { + EV_ACTIVE = 1 << 0, + EV_FORMAT = 1 << 1, + EV_RATE = 1 << 2, + EV_CHANNELS = 1 << 3, + EV_ACCESS = 1 << 4, +}; + +FIXTURE(aloop) { + char play_name[32]; + char capt_name[32]; + snd_ctl_t *ctl; + bool saved_notify; + bool notify_saved; +}; + +/* The cable's controls are on the capture side device. */ +static void cable_ctl_id(snd_ctl_elem_value_t *value, const char *name) +{ + snd_ctl_elem_value_set_interface(value, SND_CTL_ELEM_IFACE_PCM); + snd_ctl_elem_value_set_name(value, name); + snd_ctl_elem_value_set_device(value, 1); + snd_ctl_elem_value_set_subdevice(value, 0); +} + +static long cable_ctl_get(snd_ctl_t *ctl, const char *name) +{ + snd_ctl_elem_value_t *value; + + snd_ctl_elem_value_alloca(&value); + cable_ctl_id(value, name); + if (snd_ctl_elem_read(ctl, value) < 0) + return -1; + if (!strcmp(name, "PCM Slave Access Mode")) + return snd_ctl_elem_value_get_enumerated(value, 0); + return snd_ctl_elem_value_get_integer(value, 0); +} + +static int set_notify(snd_ctl_t *ctl, bool on) +{ + snd_ctl_elem_value_t *value; + + snd_ctl_elem_value_alloca(&value); + cable_ctl_id(value, "PCM Notify"); + snd_ctl_elem_value_set_boolean(value, 0, on); + return snd_ctl_elem_write(ctl, value); +} + +/* Read all pending control events and return the EV_* bits for the cable's controls. */ +static unsigned int read_events(snd_ctl_t *ctl) +{ + static const struct { + const char *name; + unsigned int bit; + } names[] = { + { "PCM Slave Active", EV_ACTIVE }, + { "PCM Slave Format", EV_FORMAT }, + { "PCM Slave Rate", EV_RATE }, + { "PCM Slave Channels", EV_CHANNELS }, + { "PCM Slave Access Mode", EV_ACCESS }, + }; + snd_ctl_event_t *event; + unsigned int seen = 0; + int i; + + snd_ctl_event_alloca(&event); + while (snd_ctl_read(ctl, event) > 0) { + if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM || + !(snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE) || + snd_ctl_event_elem_get_device(event) != 1 || + snd_ctl_event_elem_get_subdevice(event) != 0) + continue; + for (i = 0; i < ARRAY_SIZE(names); i++) + if (!strcmp(snd_ctl_event_elem_get_name(event), names[i].name)) + seen |= names[i].bit; + } + return seen; +} + +/* Open and configure a stream. snd_pcm_hw_params() also prepares it. */ +static int open_pcm(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, + const struct stream_params *p) +{ + unsigned int buffer_time = 100000; + snd_pcm_hw_params_t *hw; + int err; + + snd_pcm_hw_params_alloca(&hw); + err = snd_pcm_open(pcm, name, stream, 0); + if (err < 0) + return err; + err = snd_pcm_hw_params_any(*pcm, hw); + if (err >= 0) + err = snd_pcm_hw_params_set_access(*pcm, hw, p->access); + if (err >= 0) + err = snd_pcm_hw_params_set_format(*pcm, hw, p->format); + if (err >= 0) + err = snd_pcm_hw_params_set_channels(*pcm, hw, p->channels); + if (err >= 0) + err = snd_pcm_hw_params_set_rate(*pcm, hw, p->rate, 0); + if (err >= 0) + err = snd_pcm_hw_params_set_buffer_time_near(*pcm, hw, &buffer_time, NULL); + if (err >= 0) + err = snd_pcm_hw_params(*pcm, hw); + if (err < 0) { + snd_pcm_close(*pcm); + *pcm = NULL; + } + return err; +} + +/* What a client probing the device sees, and whether it may use the given format */ +static int probe_pcm(const char *name, snd_pcm_stream_t stream, snd_pcm_format_t format, + struct probe_result *res) +{ + snd_pcm_hw_params_t *hw; + snd_pcm_t *pcm; + int err; + + snd_pcm_hw_params_alloca(&hw); + err = snd_pcm_open(&pcm, name, stream, 0); + if (err < 0) + return err; + err = snd_pcm_hw_params_any(pcm, hw); + if (err >= 0) + err = snd_pcm_hw_params_get_rate_min(hw, &res->rate_min, NULL); + if (err >= 0) + err = snd_pcm_hw_params_get_rate_max(hw, &res->rate_max, NULL); + if (err >= 0) + err = snd_pcm_hw_params_get_channels_min(hw, &res->channels_min); + if (err >= 0) + err = snd_pcm_hw_params_get_channels_max(hw, &res->channels_max); + if (err >= 0) + res->format_ok = !snd_pcm_hw_params_test_format(pcm, hw, format); + snd_pcm_close(pcm); + return err; +} + +static int start_playback(snd_pcm_t *pcm, const struct stream_params *p) +{ + static char silence[FRAMES * MAX_CHANNELS * 4]; + void *bufs[MAX_CHANNELS]; + snd_pcm_sframes_t written; + unsigned int i; + + if (p->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { + for (i = 0; i < p->channels; i++) + bufs[i] = silence + i * FRAMES * 4; + written = snd_pcm_writen(pcm, bufs, FRAMES); + } else { + written = snd_pcm_writei(pcm, silence, FRAMES); + } + if (written < 0) + return written; + if (snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED) + return snd_pcm_start(pcm); + return 0; +} + +FIXTURE_SETUP(aloop) { + char ctl_name[32]; + snd_pcm_t *pcm; + int card, err; + + card = snd_card_get_index("Loopback"); + if (card < 0) + SKIP(return, "No Loopback card, snd-aloop is probably not loaded"); + + sprintf(ctl_name, "hw:%d", card); + sprintf(self->play_name, "hw:%d,0,0", card); + sprintf(self->capt_name, "hw:%d,1,0", card); + + err = snd_pcm_open(&pcm, self->capt_name, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + if (err == -EBUSY) + SKIP(return, "%s is in use", self->capt_name); + ASSERT_EQ(err, 0); + snd_pcm_close(pcm); + err = snd_pcm_open(&pcm, self->play_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (err == -EBUSY) + SKIP(return, "%s is in use", self->play_name); + ASSERT_EQ(err, 0); + snd_pcm_close(pcm); + + ASSERT_EQ(snd_ctl_open(&self->ctl, ctl_name, SND_CTL_NONBLOCK), 0); + ASSERT_EQ(snd_ctl_subscribe_events(self->ctl, 1), 0); + self->saved_notify = cable_ctl_get(self->ctl, "PCM Notify"); + self->notify_saved = true; +} + +FIXTURE_TEARDOWN(aloop) { + if (self->notify_saved) + set_notify(self->ctl, self->saved_notify); + if (self->ctl) + snd_ctl_close(self->ctl); +} + +/* Without notify, a playback opened while a capture is set up is pinned to its parameters. */ +TEST_F(aloop, playback_constrained_without_notify) { + struct probe_result res; + snd_pcm_t *capt; + + ASSERT_EQ(set_notify(self->ctl, false), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + + ASSERT_EQ(probe_pcm(self->play_name, SND_PCM_STREAM_PLAYBACK, playback_params.format, + &res), 0); + EXPECT_EQ(res.rate_min, capture_params.rate); + EXPECT_EQ(res.rate_max, capture_params.rate); + EXPECT_EQ(res.channels_min, capture_params.channels); + EXPECT_EQ(res.channels_max, capture_params.channels); + EXPECT_FALSE(res.format_ok); + + snd_pcm_close(capt); +} + +/* With notify, the playback side is free to pick other parameters. */ +TEST_F(aloop, playback_unconstrained_with_notify) { + struct probe_result res; + snd_pcm_t *capt; + + ASSERT_EQ(set_notify(self->ctl, true), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + + ASSERT_EQ(probe_pcm(self->play_name, SND_PCM_STREAM_PLAYBACK, playback_params.format, + &res), 0); + EXPECT_LE(res.rate_min, capture_params.rate); + EXPECT_GE(res.rate_max, playback_params.rate); + EXPECT_LE(res.channels_min, capture_params.channels); + EXPECT_GE(res.channels_max, playback_params.channels); + EXPECT_TRUE(res.format_ok); + + snd_pcm_close(capt); +} + +/* + * With notify, starting a playback with different parameters stops the + * running capture, and the "PCM Slave" controls report the new parameters + * with a value event for each one that changed. + */ +TEST_F(aloop, params_change_stops_capture_with_notify) { + snd_pcm_t *capt, *play; + unsigned int events; + + /* + * The controls keep the last playback's parameters, and only notify on + * a change. Start a playback with the capture's parameters while no + * capture is open, so that every control changes below. + */ + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &capture_params), 0); + ASSERT_EQ(start_playback(play, &capture_params), 0); + snd_pcm_close(play); + + ASSERT_EQ(set_notify(self->ctl, true), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + ASSERT_EQ(snd_pcm_start(capt), 0); + ASSERT_EQ(snd_pcm_state(capt), SND_PCM_STATE_RUNNING); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 0); + read_events(self->ctl); + + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0) + TH_LOG("Playback refused other parameters while the capture is running"); + ASSERT_EQ(start_playback(play, &playback_params), 0); + + /* loopback_check_format() stops the capture from the playback's start trigger. */ + EXPECT_NE(snd_pcm_state(capt), SND_PCM_STATE_RUNNING); + + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 1); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Format"), playback_params.format); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Rate"), playback_params.rate); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Channels"), playback_params.channels); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Access Mode"), 1); + + events = read_events(self->ctl); + EXPECT_TRUE(events & EV_ACTIVE); + EXPECT_TRUE(events & EV_FORMAT); + EXPECT_TRUE(events & EV_RATE); + EXPECT_TRUE(events & EV_CHANNELS); + EXPECT_TRUE(events & EV_ACCESS); + + snd_pcm_close(play); + snd_pcm_close(capt); +} + +/* With notify, a capture opened second is still pinned to the playback's parameters. */ +TEST_F(aloop, capture_constrained_with_notify) { + struct probe_result res; + snd_pcm_t *play; + + ASSERT_EQ(set_notify(self->ctl, true), 0); + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0); + + ASSERT_EQ(probe_pcm(self->capt_name, SND_PCM_STREAM_CAPTURE, capture_params.format, + &res), 0); + EXPECT_EQ(res.rate_min, playback_params.rate); + EXPECT_EQ(res.rate_max, playback_params.rate); + EXPECT_EQ(res.channels_min, playback_params.channels); + EXPECT_EQ(res.channels_max, playback_params.channels); + EXPECT_FALSE(res.format_ok); + + snd_pcm_close(play); +} + +TEST_HARNESS_MAIN From 3bc4fc135ec741c5657cba6b2300bf8bf81179d2 Mon Sep 17 00:00:00 2001 From: Georg Wenzel Date: Sun, 4 Oct 2026 09:12:00 +0200 Subject: [PATCH 521/924] ALSA: hda/tas2781: Enable bass speakers support Configure the driver to correctly initialize and enable the bass speakers on devices utilizing the TAS2781 amplifier. Without this change, only the tweeters or main speakers are active, leading to thin audio output. Set up the required register sequences and routing to ensure the bass channels are properly powered and mixed into the audio stream. Signed-off-by: Georg Wenzel Link: https://patch.msgid.link/20261004071202.89116-1-g.wenzel@gmail.com Signed-off-by: Takashi Iwai --- include/sound/tas2781.h | 12 +++ .../hda/codecs/side-codecs/tas2781_hda_i2c.c | 98 +++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index b763da613769..ccc0142fbad5 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -91,6 +91,18 @@ /* prm_TE_1_Beta1 */ #define TAS2563_TE_DT_REG TASDEVICE_REG(0x00, 0x0f, 0x70) +/* TAS2563 ASI (Audio Serial Interface) Register Definitions */ +#define TAS2563_ASI_CFG_REG TASDEVICE_REG(0x00, 0x00, 0x06) +#define TAS2563_ASI_SR_REG TASDEVICE_REG(0x00, 0x00, 0x07) +#define TAS2563_ASI_FS_REG TASDEVICE_REG(0x00, 0x00, 0x08) +#define TAS2563_ASI_DA_REG TASDEVICE_REG(0x00, 0x00, 0x0a) +#define TAS2563_ASI_TDM_REG TASDEVICE_REG(0x00, 0x00, 0x0b) +#define TAS2563_ASI_DL_REG TASDEVICE_REG(0x00, 0x00, 0x0c) +#define TAS2563_ASI_SLOT_REG TASDEVICE_REG(0x00, 0x00, 0x30) + +/* TAS2563 ASI Apply Register (triggers configuration update) */ +#define TAS2563_ASI_APPLY_REG TASDEVICE_REG(0x00, 0x00, 0x02) + #define TAS2781_PRM_INT_MASK_REG TASDEVICE_REG(0x00, 0x00, 0x3b) #define TAS2781_PRM_CLK_CFG_REG TASDEVICE_REG(0x00, 0x00, 0x5c) #define TAS2781_PRM_RSVD_REG TASDEVICE_REG(0x00, 0x01, 0x19) diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index 5e5f46c9fb6b..cef33159eba4 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -46,6 +46,83 @@ #define TAS2563_CAL_TLIM TASDEVICE_REG(0, 0x10, 0x14) #define TAS2563_CAL_R0 TASDEVICE_REG(0, 0x0f, 0x34) +/* + * TAS2563 Post-Firmware ASI Configuration. + * + * The DSP firmware loading can overwrite the ASI (Audio Serial Interface) + * registers that configure the I2S/TDM audio path to the amplifier. + * This function re-applies the correct ASI configuration after firmware load, + * matching the reference initialization sequence used by the vendor script: + * - Software reset + * - ASI_CFG = 0x09 (I2S mode, master/slave config) + * - ASI_SR = 0x02 (sample rate divider for 48 kHz) + * - ASI_DA = 0xf1 (data alignment / slot assignment) + * - ASI_TDM = 0x02 (TDM mode select) + * - ASI_DL = 0x00 (delay line off) + * - ASI_SLOT = 0x99 (slot enable mask for 2-channel stereo) + * - CHNL_0 = 0x02 (channel mapping) + * - ASI_FS = 0x1e or 0x2e (frame sync, device-specific: bit4 selects + * which of the two devices is the "primary" for FS generation) + * - ASI_APPLY= 0x00 (trigger configuration update) + */ +static void tas2563_post_fw_init(struct tasdevice_priv *tas_priv) +{ + int i, ret; + + dev_info(tas_priv->dev, "%s: ndev=%d, chip_id=%d\n", + __func__, tas_priv->ndev, tas_priv->chip_id); + + for (i = 0; i < tas_priv->ndev; i++) { + struct tasdevice *tasdev = &tas_priv->tasdevice[i]; + u8 fs_val; + + dev_info(tas_priv->dev, + "%s: device[%d] dev_addr=0x%02x\n", + __func__, i, tasdev->dev_addr); + + /* Step 1: Software reset (matches script: 0x01 = 0x01) */ + ret = tasdevice_dev_write(tas_priv, i, TASDEVICE_REG_SWRESET, + TASDEVICE_REG_SWRESET_RESET); + dev_info(tas_priv->dev, + "%s: chn=%d SWRESET ret=%d\n", __func__, i, ret); + usleep_range(1000, 2000); + + /* Step 2: ASI configuration registers */ + ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_CFG_REG, 0x09); + dev_info(tas_priv->dev, "%s: chn=%d ASI_CFG=0x09 ret=%d\n", __func__, i, ret); + + ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_SR_REG, 0x02); + dev_info(tas_priv->dev, "%s: chn=%d ASI_SR=0x02 ret=%d\n", __func__, i, ret); + + ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_DA_REG, 0xf1); + dev_info(tas_priv->dev, "%s: chn=%d ASI_DA=0xf1 ret=%d\n", __func__, i, ret); + + ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_TDM_REG, 0x02); + dev_info(tas_priv->dev, "%s: chn=%d ASI_TDM=0x02 ret=%d\n", __func__, i, ret); + + ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_DL_REG, 0x00); + dev_info(tas_priv->dev, "%s: chn=%d ASI_DL=0x00 ret=%d\n", __func__, i, ret); + + ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_SLOT_REG, 0x99); + dev_info(tas_priv->dev, "%s: chn=%d ASI_SLOT=0x99 ret=%d\n", __func__, i, ret); + + ret = tasdevice_dev_write(tas_priv, i, TAS2781_PRM_CHNL_0_REG, 0x02); + dev_info(tas_priv->dev, "%s: chn=%d CHNL_0=0x02 ret=%d\n", __func__, i, ret); + + /* Step 3: Device-specific frame sync (0x1e for dev0, 0x2e for dev1) */ + fs_val = 0x1e | ((i & 0x01) << 4); + ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_FS_REG, fs_val); + dev_info(tas_priv->dev, "%s: chn=%d ASI_FS=0x%02x ret=%d\n", + __func__, i, fs_val, ret); + + /* Step 4: Apply ASI configuration */ + ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_APPLY_REG, 0x00); + dev_info(tas_priv->dev, "%s: chn=%d ASI_APPLY ret=%d\n", __func__, i, ret); + + usleep_range(1000, 2000); + } +} + enum device_chip_id { HDA_TAS2563, HDA_TAS2770, @@ -172,6 +249,13 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) pm_runtime_get_sync(dev); scoped_guard(mutex, &tas_hda->priv->codec_lock) { tasdevice_tuning_switch(tas_hda->priv, 0, false); + /* Re-apply ASI configuration for TAS2563 after + * tuning switch - DSP firmware loading can + * overwrite ASI registers. + */ + if (tas_hda->priv->chip_id == TAS2563) + tas2563_post_fw_init(tas_hda->priv); + tas_hda->priv->playback_started = true; } break; @@ -475,6 +559,12 @@ static void tasdevice_dspfw_init(void *context) tas_priv->rcabin.init_profile_id, TASDEVICE_BIN_BLK_PRE_POWER_UP); + /* Apply ASI configuration for TAS2563 after firmware load. + * DSP firmware loading can overwrite ASI registers. + */ + if (tas_hda->priv->chip_id == TAS2563) + tas2563_post_fw_init(tas_hda->priv); + /* If calibrated data occurs error, dsp will still works with default * calibrated data inside algo. */ @@ -659,6 +749,7 @@ static int tas2781_hda_i2c_probe(struct i2c_client *clt) */ device_name = "INT8866"; hda_priv->hda_chip_id = HDA_TAS2563; + tas_hda->priv->chip_id = TAS2563; hda_priv->save_calibration = tas2563_save_calibration; tas_hda->priv->global_addr = TAS2563_GLOBAL_ADDR; } else if (strstarts(dev_name(&clt->dev), "i2c-TXNW5825")) { @@ -782,6 +873,13 @@ static int tas2781_system_resume(struct device *dev) tas_hda->priv->rcabin.init_profile_id, TASDEVICE_BIN_BLK_PRE_POWER_UP); + /* Re-apply ASI configuration for TAS2563 after system sleep + * resume — amplifiers may lose their register state during + * suspend-to-ram or suspend-to-disk regardless of playback state. + */ + if (tas_hda->priv->chip_id == TAS2563) + tas2563_post_fw_init(tas_hda->priv); + if (tas_hda->priv->playback_started) tasdevice_tuning_switch(tas_hda->priv, 0, false); From 664824782df63013ba8686b649c625cac4793248 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Sat, 26 Sep 2026 14:25:58 +0200 Subject: [PATCH 522/924] ASoC: aw88399: skip BSTS check when boost converter is bypassed The sysst check in aw_dev_check_sysst() requires BSTS (boost finished) to assert, but on hardware where the boost converter is bypassed via external power supply (BSTCTRL10 register 0x69, EN_HVBAT bit 2 set), BSTS (SYSST register 0x01, bit 9) will never assert. The vendor's generic driver (aw883xx) handles this by reading EN_HVBAT and conditionally masking out BSTS from the sysst check. The in-tree driver missed this logic when the original code was ported upstream. Add the same conditional check, matching the vendor driver's aw_dev_sysst_check() implementation. Fixes: 8ade6cc7e261 ("ASoC: codecs: Add aw88399 amplifier driver") Signed-off-by: Marco Giunta Link: https://patch.msgid.link/SN6PR19MB2303FF1922BF5F876378066FFC8F2@SN6PR19MB2303.namprd19.prod.outlook.com Signed-off-by: Mark Brown --- include/sound/aw88399.h | 5 +++++ sound/soc/codecs/aw88399-lib.c | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h index a1e0de6be8ea..974b5774ac2b 100644 --- a/include/sound/aw88399.h +++ b/include/sound/aw88399.h @@ -449,6 +449,11 @@ #define AW88399_NOISE_GATE_EN_MASK \ (~(((1<regmap, AW88399_BSTCTRL10_REG, ®_val); + if (ret) + return ret; + + if (reg_val & (~AW88399_EPS_EN_MASK)) + check_val &= ~AW88399_BSTS_FINISHED_VALUE; + /* * On some hardware the BSTS (boost-finished) status bit does not * reliably assert even when audio output is working normally. From c5f6155364056de8111b288f6b8c8a84f12f53e5 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Sat, 26 Sep 2026 14:25:59 +0200 Subject: [PATCH 523/924] ASoC: aw88399: remove bsts_unreliable workaround Now that the shared library correctly skips the BSTS check when the boost converter is bypassed, the per-instance bsts_unreliable flag added as a workaround in the HDA side codec driver is unnecessary. Remove it from the library, the HDA driver, and the shared header. This also simplifies testing the HDA side codec driver on new devices, as new models no longer need a quirk entry to bypass the BSTS check. Users can test via the model= module parameter in alc269 without a custom kernel. Signed-off-by: Marco Giunta Link: https://patch.msgid.link/SN6PR19MB2303CC64AF7DEC3C5C98A512FC8F2@SN6PR19MB2303.namprd19.prod.outlook.com Signed-off-by: Mark Brown --- include/sound/aw88399.h | 1 - sound/hda/codecs/side-codecs/aw88399_hda.c | 38 +++++----------------- sound/hda/codecs/side-codecs/aw88399_hda.h | 1 - sound/soc/codecs/aw88399-lib.c | 13 ++------ 4 files changed, 10 insertions(+), 43 deletions(-) diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h index 974b5774ac2b..38d9bd6abd8b 100644 --- a/include/sound/aw88399.h +++ b/include/sound/aw88399.h @@ -603,7 +603,6 @@ struct aw88399 { unsigned int crc_init_val; unsigned int vcalb_init_val; unsigned int dither_st; - bool bsts_unreliable; bool fw_needs_reload; }; diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c index 11cef4923024..69d113af8ae3 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda.c @@ -118,7 +118,6 @@ static int aw88399_hda_init(struct aw88399_hda *aw88399) mutex_init(&core->lock); core->reset_gpio = aw88399->reset_gpio; core->regmap = aw88399->regmap; - core->bsts_unreliable = aw88399->bsts_unreliable; aw88399_hw_reset(core); @@ -155,41 +154,20 @@ static int aw88399_swap_channels(struct aw88399_hda *aw88399) return 0; } -static int aw88399_skip_bsts_check(struct aw88399_hda *aw88399) -{ - /* - * BSTS (boost-finished) status bit does not reliably report on - * some hardware. On certain Lenovo Legion laptops, both amps - * report BSTS=0 (boost not finished) during normal playback - * despite clean audio output. Skip BSTS in the startup status - * check to avoid false init failures. - */ - aw88399->bsts_unreliable = true; - dev_dbg(aw88399->dev, "BSTS status check disabled\n"); - return 0; -} - -static int aw88399_apply_legion_quirks(struct aw88399_hda *aw88399) -{ - aw88399_swap_channels(aw88399); - aw88399_skip_bsts_check(aw88399); - return 0; -} - struct aw88399_prop_model { const char *ssid; int (*apply_prop)(struct aw88399_hda *aw88399); }; static const struct aw88399_prop_model aw88399_prop_model_table[] = { - { "17AA3906", aw88399_apply_legion_quirks }, - { "17AA3907", aw88399_apply_legion_quirks }, - { "17AA3927", aw88399_apply_legion_quirks }, - { "17AA3928", aw88399_apply_legion_quirks }, - { "17AA3936", aw88399_apply_legion_quirks }, - { "17AA3937", aw88399_apply_legion_quirks }, - { "17AA3938", aw88399_apply_legion_quirks }, - { "17AA3939", aw88399_apply_legion_quirks }, + { "17AA3906", aw88399_swap_channels }, + { "17AA3907", aw88399_swap_channels }, + { "17AA3927", aw88399_swap_channels }, + { "17AA3928", aw88399_swap_channels }, + { "17AA3936", aw88399_swap_channels }, + { "17AA3937", aw88399_swap_channels }, + { "17AA3938", aw88399_swap_channels }, + { "17AA3939", aw88399_swap_channels }, { } }; diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.h b/sound/hda/codecs/side-codecs/aw88399_hda.h index ca0aa5279298..af71083124bd 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.h +++ b/sound/hda/codecs/side-codecs/aw88399_hda.h @@ -19,7 +19,6 @@ struct aw88399_hda { struct gpio_desc *reset_gpio; struct aw_device *aw_dev; struct aw88399 *core; - bool bsts_unreliable; const char *acpi_subsystem_id; int index; diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index 1ba0147cf4df..75a533c9b752 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -167,9 +167,8 @@ int aw_dev_check_syspll(struct aw_device *aw_dev) } EXPORT_SYMBOL_GPL(aw_dev_check_syspll); -static int aw_dev_check_sysst(struct aw88399 *aw88399) +static int aw_dev_check_sysst(struct aw_device *aw_dev) { - struct aw_device *aw_dev = aw88399->aw_pa; unsigned int check_val; unsigned int reg_val; int ret, i; @@ -196,14 +195,6 @@ static int aw_dev_check_sysst(struct aw88399 *aw88399) if (reg_val & (~AW88399_EPS_EN_MASK)) check_val &= ~AW88399_BSTS_FINISHED_VALUE; - /* - * On some hardware the BSTS (boost-finished) status bit does not - * reliably assert even when audio output is working normally. - * Allow per-instance bypass when flagged by the side-codec driver. - */ - if (aw88399->bsts_unreliable) - check_val &= ~AW88399_BSTS_FINISHED_VALUE; - for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); if (ret) @@ -732,7 +723,7 @@ static int aw88399_dev_start(struct aw88399 *aw88399) usleep_range(AW88399_1000_US, AW88399_1000_US + 50); /* check i2s status */ - ret = aw_dev_check_sysst(aw88399); + ret = aw_dev_check_sysst(aw_dev); if (ret) { dev_err(aw_dev->dev, "sysst check failed"); goto sysst_check_fail; From 9a98f7e955d35e0831fb179e88e98cf9bc0e001e Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 22:50:13 +0800 Subject: [PATCH 524/924] ASoC: rt5682-sdw: always cancel jack work on remove rt5682_sdw_remove() cancels jack_detect_work only when hw_init is true. rt5682_update_status() clears hw_init when the SoundWire slave becomes unattached, but jack_detect_work may already have been queued by rt5682_interrupt_callback() or the jack initialization path. A remove after that status update can therefore skip the cancellation and leave delayed work pending after the codec private data has been freed. The work object is initialized during probe, so cancel it unconditionally instead of relying on the mutable hw_init state. Fixes: ac63716da307 ("ASoC: Realtek/Maxim SoundWire codecs: disable pm_runtime on remove") Signed-off-by: Jiale Yao Link: https://patch.msgid.link/20260925145019.2290367-2-yaojiale02@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5682-sdw.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt5682-sdw.c b/sound/soc/codecs/rt5682-sdw.c index 23c7d2a9dbee..5f2897c58ad7 100644 --- a/sound/soc/codecs/rt5682-sdw.c +++ b/sound/soc/codecs/rt5682-sdw.c @@ -704,8 +704,7 @@ static void rt5682_sdw_remove(struct sdw_slave *slave) { struct rt5682_priv *rt5682 = dev_get_drvdata(&slave->dev); - if (rt5682->hw_init) - cancel_delayed_work_sync(&rt5682->jack_detect_work); + cancel_delayed_work_sync(&rt5682->jack_detect_work); pm_runtime_disable(&slave->dev); } From 4bf78e3d9e324f9f2e39feecedd4f70b05baeb16 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 22:50:14 +0800 Subject: [PATCH 525/924] ASoC: rt711-sdw: always cancel jack work on remove rt711_sdw_remove() cancels the jack and calibration work only when hw_init is true. rt711_update_status() clears hw_init when the SoundWire slave becomes unattached, but work may already have been queued by rt711_interrupt_callback() or the jack initialization path. A remove after that status update can therefore skip the cancellation and leave work pending after the codec private data has been freed. The work objects are initialized during probe, so cancel them unconditionally instead of relying on the mutable hw_init state. Fixes: ac63716da307 ("ASoC: Realtek/Maxim SoundWire codecs: disable pm_runtime on remove") Signed-off-by: Jiale Yao Link: https://patch.msgid.link/20260925145019.2290367-3-yaojiale02@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt711-sdw.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/rt711-sdw.c b/sound/soc/codecs/rt711-sdw.c index 4ad2b1da1954..798d181d5c90 100644 --- a/sound/soc/codecs/rt711-sdw.c +++ b/sound/soc/codecs/rt711-sdw.c @@ -461,11 +461,9 @@ static void rt711_sdw_remove(struct sdw_slave *slave) { struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev); - if (rt711->hw_init) { - cancel_delayed_work_sync(&rt711->jack_detect_work); - cancel_delayed_work_sync(&rt711->jack_btn_check_work); - cancel_work_sync(&rt711->calibration_work); - } + cancel_delayed_work_sync(&rt711->jack_detect_work); + cancel_delayed_work_sync(&rt711->jack_btn_check_work); + cancel_work_sync(&rt711->calibration_work); pm_runtime_disable(&slave->dev); From ecc2fca6decefe27d8cf40a4c9815798eec24d30 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 22:50:15 +0800 Subject: [PATCH 526/924] ASoC: rt711-sdca: always cancel jack work on remove rt711_sdca_sdw_remove() cancels the jack work only when hw_init is true. rt711_sdca_update_status() clears hw_init when the SoundWire slave becomes unattached, but jack work may already have been queued by rt711_sdca_interrupt_callback() or the jack initialization path. A remove after that status update can therefore skip the cancellation and leave delayed work pending after the codec private data has been freed. The work objects are initialized during probe, so cancel them unconditionally instead of relying on the mutable hw_init state. Fixes: ac63716da307 ("ASoC: Realtek/Maxim SoundWire codecs: disable pm_runtime on remove") Signed-off-by: Jiale Yao Link: https://patch.msgid.link/20260925145019.2290367-4-yaojiale02@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt711-sdca-sdw.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/rt711-sdca-sdw.c b/sound/soc/codecs/rt711-sdca-sdw.c index e292b28b029e..f74b193c720d 100644 --- a/sound/soc/codecs/rt711-sdca-sdw.c +++ b/sound/soc/codecs/rt711-sdca-sdw.c @@ -369,10 +369,8 @@ static void rt711_sdca_sdw_remove(struct sdw_slave *slave) { struct rt711_sdca_priv *rt711 = dev_get_drvdata(&slave->dev); - if (rt711->hw_init) { - cancel_delayed_work_sync(&rt711->jack_detect_work); - cancel_delayed_work_sync(&rt711->jack_btn_check_work); - } + cancel_delayed_work_sync(&rt711->jack_detect_work); + cancel_delayed_work_sync(&rt711->jack_btn_check_work); pm_runtime_disable(&slave->dev); From c7d07df95ee7ce7859e6c873a76d19ea9c64b6fd Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 22:50:16 +0800 Subject: [PATCH 527/924] ASoC: rt712-sdca: always cancel jack work on remove rt712_sdca_sdw_remove() cancels the jack work only when hw_init is true. rt712_sdca_update_status() clears hw_init when the SoundWire slave becomes unattached, but jack work may already have been queued by rt712_sdca_interrupt_callback() or the jack initialization path. A remove after that status update can therefore skip the cancellation and leave delayed work pending after the codec private data has been freed. The work objects are initialized during probe, so cancel them unconditionally instead of relying on the mutable hw_init state. Fixes: 6c39710da75c ("ASoC: rt712-sdca: Add RT712 SDCA driver for Jack and Amp topology") Signed-off-by: Jiale Yao Link: https://patch.msgid.link/20260925145019.2290367-5-yaojiale02@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca-sdw.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c index f2f91dd087bf..0f0447d1a05b 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.c +++ b/sound/soc/codecs/rt712-sdca-sdw.c @@ -386,10 +386,8 @@ static void rt712_sdca_sdw_remove(struct sdw_slave *slave) { struct rt712_sdca_priv *rt712 = dev_get_drvdata(&slave->dev); - if (rt712->hw_init) { - cancel_delayed_work_sync(&rt712->jack_detect_work); - cancel_delayed_work_sync(&rt712->jack_btn_check_work); - } + cancel_delayed_work_sync(&rt712->jack_detect_work); + cancel_delayed_work_sync(&rt712->jack_btn_check_work); pm_runtime_disable(&slave->dev); From 958d025d62352b81dbbd7e975442dd211176f4e1 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 22:50:17 +0800 Subject: [PATCH 528/924] ASoC: rt721-sdca: always cancel jack work on remove rt721_sdca_sdw_remove() cancels the jack work only when hw_init is true. rt721_sdca_update_status() clears hw_init when the SoundWire slave becomes unattached, but jack work may already have been queued by rt721_sdca_interrupt_callback() or the jack initialization path. A remove after that status update can therefore skip the cancellation and leave delayed work pending after the codec private data has been freed. The work objects are initialized during probe, so cancel them unconditionally instead of relying on the mutable hw_init state. Fixes: 86ce355c1f9a ("ASoC: rt721-sdca: Add RT721 SDCA driver") Signed-off-by: Jiale Yao Link: https://patch.msgid.link/20260925145019.2290367-6-yaojiale02@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca-sdw.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c index a8c7b42898db..08c562c98cd7 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.c +++ b/sound/soc/codecs/rt721-sdca-sdw.c @@ -436,10 +436,8 @@ static void rt721_sdca_sdw_remove(struct sdw_slave *slave) { struct rt721_sdca_priv *rt721 = dev_get_drvdata(&slave->dev); - if (rt721->hw_init) { - cancel_delayed_work_sync(&rt721->jack_detect_work); - cancel_delayed_work_sync(&rt721->jack_btn_check_work); - } + cancel_delayed_work_sync(&rt721->jack_detect_work); + cancel_delayed_work_sync(&rt721->jack_btn_check_work); if (rt721->first_hw_init) pm_runtime_disable(&slave->dev); From 9d29132d81a7f269177be676c3b81cc575899975 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 22:50:18 +0800 Subject: [PATCH 529/924] ASoC: rt722-sdca: always cancel jack work on remove rt722_sdca_sdw_remove() cancels the jack work only when hw_init is true. rt722_sdca_update_status() clears hw_init when the SoundWire slave becomes unattached, but jack work may already have been queued by rt722_sdca_interrupt_callback() or the jack initialization path. A remove after that status update can therefore skip the cancellation and leave delayed work pending after the codec private data has been freed. The work objects are initialized during probe, so cancel them unconditionally instead of relying on the mutable hw_init state. Fixes: 7f5d6036ca00 ("ASoC: rt722-sdca: Add RT722 SDCA driver") Signed-off-by: Jiale Yao Link: https://patch.msgid.link/20260925145019.2290367-7-yaojiale02@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt722-sdca-sdw.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/rt722-sdca-sdw.c b/sound/soc/codecs/rt722-sdca-sdw.c index 5bc9aed58d14..078255fbe1fc 100644 --- a/sound/soc/codecs/rt722-sdca-sdw.c +++ b/sound/soc/codecs/rt722-sdca-sdw.c @@ -471,10 +471,8 @@ static void rt722_sdca_sdw_remove(struct sdw_slave *slave) { struct rt722_sdca_priv *rt722 = dev_get_drvdata(&slave->dev); - if (rt722->hw_init) { - cancel_delayed_work_sync(&rt722->jack_detect_work); - cancel_delayed_work_sync(&rt722->jack_btn_check_work); - } + cancel_delayed_work_sync(&rt722->jack_detect_work); + cancel_delayed_work_sync(&rt722->jack_btn_check_work); if (rt722->first_hw_init) pm_runtime_disable(&slave->dev); From c53791d673ed4da6759d276078627ec4d0c6536b Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 29 Sep 2026 06:13:49 +0000 Subject: [PATCH 530/924] ASoC: amd: ps: fix snd_acp63_remove() teardown ordering The ACP IRQ handlers dereference the SoundWire, SoundWire DMA, and PDM child platform devices, but snd_acp63_remove() unregisters them while the interrupt is still held by devres, which frees it only after remove returns. An interrupt in this window is a use-after-free. Mask the ACP interrupt sources and call devm_free_irq() before the first child device is unregistered. The masking goes through the new acp_hw_ops->disable_interrupts callback, keeping the remove path platform-agnostic. The interrupt line is shared, and acp_hw_deinit() clears the sources only after the children are gone, which would leave the line raised with no handler left to ack it. The window predates the tagged refactor, which only reshaped the dereferences. devm_free_irq() waits for in-flight handlers, but not for work the hardirq has already queued: acp63_irq_handler() schedules amd_sdw_irq_thread on the SoundWire manager. Draining that work in the manager's remove path is a soundwire-side change and follows separately. This issue was found by an in-house static analysis tool. Fixes: eaf825037d6d ("ASoC: amd: ps: refactor acp child platform device creation code") Cc: stable@vger.kernel.org Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Reviewed-by: Vijendar Mukunda Link: https://patch.msgid.link/20260929061349.3879-1-fanwu01@zju.edu.cn Signed-off-by: Mark Brown --- sound/soc/amd/ps/acp63.h | 8 ++++++++ sound/soc/amd/ps/pci-ps.c | 2 ++ sound/soc/amd/ps/ps-common.c | 2 ++ 3 files changed, 12 insertions(+) diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 62cb6bef17ab..d0cfbd4dafc1 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -294,6 +294,7 @@ struct acp63_dev_data; * struct acp_hw_ops - ACP PCI driver platform specific ops * @acp_init: ACP initialization * @acp_deinit: ACP de-initialization + * @disable_interrupts: disable ACP interrupt sources * @acp_get_config: function to read the acp pin configuration * @acp_sdw_dma_irq_thread: ACP SoundWire DMA interrupt thread * acp_suspend: ACP system level suspend callback @@ -304,6 +305,7 @@ struct acp63_dev_data; struct acp_hw_ops { int (*acp_init)(void __iomem *acp_base, struct device *dev); int (*acp_deinit)(void __iomem *acp_base, struct device *dev); + void (*disable_interrupts)(void __iomem *acp_base); void (*acp_get_config)(struct pci_dev *pci, struct acp63_dev_data *acp_data); void (*acp_sdw_dma_irq_thread)(struct acp63_dev_data *acp_data); int (*acp_suspend)(struct device *dev); @@ -397,6 +399,12 @@ static inline int acp_hw_deinit(struct acp63_dev_data *adata, struct device *dev return -EOPNOTSUPP; } +static inline void acp_hw_disable_interrupts(struct acp63_dev_data *adata) +{ + if (adata && adata->hw_ops && adata->hw_ops->disable_interrupts) + ACP_HW_OPS(adata, disable_interrupts)(adata->acp63_base); +} + static inline void acp_hw_get_config(struct pci_dev *pci, struct acp63_dev_data *adata) { if (adata && adata->hw_ops && adata->hw_ops->acp_get_config) diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index 729f9aaba69e..b604db4942dd 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -738,6 +738,8 @@ static void snd_acp63_remove(struct pci_dev *pci) int ret; adata = pci_get_drvdata(pci); + acp_hw_disable_interrupts(adata); + devm_free_irq(&pci->dev, pci->irq, adata); if (adata->sdw) { amd_sdw_exit(adata); platform_device_unregister(adata->sdw_dma_dev); diff --git a/sound/soc/amd/ps/ps-common.c b/sound/soc/amd/ps/ps-common.c index 7b4966b75dc6..ea71cdf19c1e 100644 --- a/sound/soc/amd/ps/ps-common.c +++ b/sound/soc/amd/ps/ps-common.c @@ -248,6 +248,7 @@ void acp63_hw_init_ops(struct acp_hw_ops *hw_ops) { hw_ops->acp_init = acp63_init; hw_ops->acp_deinit = acp63_deinit; + hw_ops->disable_interrupts = acp63_disable_interrupts; hw_ops->acp_get_config = acp63_get_config; hw_ops->acp_sdw_dma_irq_thread = acp63_sdw_dma_irq_thread; hw_ops->acp_suspend = snd_acp63_suspend; @@ -484,6 +485,7 @@ void acp70_hw_init_ops(struct acp_hw_ops *hw_ops) { hw_ops->acp_init = acp70_init; hw_ops->acp_deinit = acp70_deinit; + hw_ops->disable_interrupts = acp70_disable_interrupts; hw_ops->acp_get_config = acp70_get_config; hw_ops->acp_sdw_dma_irq_thread = acp70_sdw_dma_irq_thread; hw_ops->acp_suspend = snd_acp70_suspend; From 80ffb82a72d80b4a633621dfc45c7cd233a2fe8c Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 29 Sep 2026 04:30:41 +0000 Subject: [PATCH 531/924] ASoC: codecs: twl4030: tidyup twl4030_soc_probe() twl4030_soc_probe() (Z) gets drvdata from Component dev (A), and set it to Component dev again (B) (= snd_soc_component_set_drvdata() calls dev_set_drvdata() inside). It is point less. Let's remove (A)(B) and rename twl4030_init_chip() (C) to twl4030_soc_probe() (Z). (Z) static int twl4030_soc_probe(...) { (A) struct twl4030_priv *twl4030 = dev_get_drvdata(component->dev); (B) snd_soc_component_set_drvdata(component, twl4030); (C) return twl4030_init_chip(component); } Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87se2sel1q.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 442624cf2a31..7117af5a1a56 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -275,7 +275,7 @@ static int twl4030_get_hw_params(struct device *dev, return 0; } -static int twl4030_init_chip(struct snd_soc_component *component) +static int twl4030_soc_probe(struct snd_soc_component *component) { struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); struct twl4030_board_params *board_params = twl4030->board_params; @@ -2172,15 +2172,6 @@ static struct snd_soc_dai_driver twl4030_dai[] = { }, }; -static int twl4030_soc_probe(struct snd_soc_component *component) -{ - struct twl4030_priv *twl4030 = dev_get_drvdata(component->dev); - - snd_soc_component_set_drvdata(component, twl4030); - - return twl4030_init_chip(component); -} - static const struct snd_soc_component_driver soc_component_dev_twl4030 = { .probe = twl4030_soc_probe, .read = twl4030_read, From 9a773d9d21fa909e8babbe11e93c811cf1a6d22d Mon Sep 17 00:00:00 2001 From: Hongyan Xu Date: Mon, 28 Sep 2026 21:40:54 +0800 Subject: [PATCH 532/924] ASoC: mt6359-accdet: manage private workqueues with devres The threaded jack-detect interrupts queue work on two private workqueues. Neither queue is destroyed on device removal, so queued callbacks can retain pointers to devm-allocated state after it is released. Probe error paths also leak the queues. Create and initialize both workqueues before requesting interrupts, then add a managed cleanup action. Since the managed IRQs are acquired afterwards, devres releases and synchronizes them before draining and destroying the workqueues on both removal and probe failure. Fixes: eef07b9e0925 ("ASoC: mediatek: mt6359: add MT6359 accdet jack driver") Signed-off-by: Hongyan Xu Link: https://patch.msgid.link/20260928134054.312-1-getshell@seu.edu.cn Signed-off-by: Mark Brown --- sound/soc/codecs/mt6359-accdet.c | 50 ++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/sound/soc/codecs/mt6359-accdet.c b/sound/soc/codecs/mt6359-accdet.c index e1190c644021..54ec1c6bd64b 100644 --- a/sound/soc/codecs/mt6359-accdet.c +++ b/sound/soc/codecs/mt6359-accdet.c @@ -914,6 +914,14 @@ int mt6359_accdet_enable_jack_detect(struct snd_soc_component *component, } EXPORT_SYMBOL_GPL(mt6359_accdet_enable_jack_detect); +static void mt6359_accdet_destroy_workqueues(void *data) +{ + struct mt6359_accdet *priv = data; + + destroy_workqueue(priv->jd_workqueue); + destroy_workqueue(priv->accdet_workqueue); +} + static int mt6359_accdet_probe(struct platform_device *pdev) { struct mt6359_accdet *priv; @@ -955,6 +963,26 @@ static int mt6359_accdet_probe(struct platform_device *pdev) } mutex_init(&priv->res_lock); + priv->accdet_workqueue = create_singlethread_workqueue("accdet"); + INIT_WORK(&priv->accdet_work, mt6359_accdet_work); + if (!priv->accdet_workqueue) { + dev_err(&pdev->dev, "Failed to create accdet workqueue\n"); + return -ENOMEM; + } + + priv->jd_workqueue = create_singlethread_workqueue("mt6359_accdet_jd"); + INIT_WORK(&priv->jd_work, mt6359_accdet_jd_work); + if (!priv->jd_workqueue) { + dev_err(&pdev->dev, "Failed to create jack detect workqueue\n"); + destroy_workqueue(priv->accdet_workqueue); + return -ENOMEM; + } + + ret = devm_add_action_or_reset(&pdev->dev, + mt6359_accdet_destroy_workqueues, priv); + if (ret) + return ret; + priv->accdet_irq = platform_get_irq(pdev, 0); if (priv->accdet_irq >= 0) { ret = devm_request_threaded_irq(&pdev->dev, priv->accdet_irq, @@ -1000,22 +1028,6 @@ static int mt6359_accdet_probe(struct platform_device *pdev) } } - priv->accdet_workqueue = create_singlethread_workqueue("accdet"); - INIT_WORK(&priv->accdet_work, mt6359_accdet_work); - if (!priv->accdet_workqueue) { - dev_err(&pdev->dev, "Failed to create accdet workqueue\n"); - ret = -1; - goto err_accdet_wq; - } - - priv->jd_workqueue = create_singlethread_workqueue("mt6359_accdet_jd"); - INIT_WORK(&priv->jd_work, mt6359_accdet_jd_work); - if (!priv->jd_workqueue) { - dev_err(&pdev->dev, "Failed to create jack detect workqueue\n"); - ret = -1; - goto err_eint_wq; - } - platform_set_drvdata(pdev, priv); ret = devm_snd_soc_register_component(&pdev->dev, &mt6359_accdet_soc_driver, @@ -1034,12 +1046,6 @@ static int mt6359_accdet_probe(struct platform_device *pdev) mt6359_accdet_jack_report(priv); return 0; - -err_eint_wq: - destroy_workqueue(priv->accdet_workqueue); -err_accdet_wq: - dev_err(&pdev->dev, "%s error. now exit.!\n", __func__); - return ret; } static struct platform_driver mt6359_accdet_driver = { From 6f4cbf5f913fc1c01de3776147ced4fb7680f29b Mon Sep 17 00:00:00 2001 From: Justin Bacle Date: Sun, 4 Oct 2026 21:20:50 +0200 Subject: [PATCH 533/924] ALSA: usb-audio: add sample-rate quirk for Yamaha 01V96i The Yamaha 01V96i exposes its audio streaming interfaces as vendor-specific USB class 0xff and ships no class-specific endpoint descriptor. parse_uac_endpoint_attributes() therefore returns no attributes for the device, and without UAC_EP_CS_ATTR_SAMPLE_RATE the driver never issues the SET_CUR sampling-frequency request the hardware needs to start its USB audio stream. The playback and capture PCMs are reported as RUNNING and their pointers advance, but the mixer passes no audio in either direction. The vendor (Yamaha Steinberg) driver on Windows sends this request at startup; the generic driver does not. Add the 01V96i to snd_usb_audioformat_attributes_quirk() to force the attribute on, so the stream is initialised the same way. Confirmed against a usbmon capture of the Windows vendor driver: SET_CUR CS_SAMPLING_FREQ_CONTROL to endpoints 0x07 and 0x86 is the only non-standard control transfer it issues that completes successfully. Verified on a mainline 7.2.9 kernel with a loopback test: playing a 440 Hz tone on USB channels 1/2 and recording USB channels 9/10 returns the tone unchanged at 44100, 48000, 88200 and 96000 Hz. Tested-by: Justin Bacle Signed-off-by: Justin Bacle Link: https://patch.msgid.link/20261004192102.59312-1-justin.bacle@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 38d78b8345f1..3ef983e23a2c 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2332,6 +2332,21 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, int stream) { switch (chip->usb_id) { + case USB_ID(0x0499, 0x5500): /* Yamaha 01V96i */ + /* + * The 01V96i declares its audio streaming interfaces as + * vendor-specific (class 0xff) and provides no class-specific + * endpoint descriptor. parse_uac_endpoint_attributes() therefore + * returns no attributes, and the driver never issues the SET_CUR + * sampling-frequency request the hardware needs to engage its USB + * stream: playback and capture PCMs report RUNNING, but the desk + * passes no audio. + * + * Force the attribute on so the stream is initialised the same way + * the Windows driver does it. + */ + fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE; + break; case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */ /* Optoplay sets the sample rate attribute although * it seems not supporting it in fact. From 58e7bca5cf264e910ad9199cfc40246e0a2834f4 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 3 Oct 2026 21:07:38 +0200 Subject: [PATCH 534/924] ASoC: codecs: es8375: use devm_clk_get_optional() for mclk es8375_read_device_properities() gets "mclk" with devm_clk_get() and then tests the result for NULL to print "assuming static mclk". A missing clock makes devm_clk_get() return -ENOENT, so the probe fails before that test can match. The binding does not require clocks, its example has none, and everest,mclk-src lets the codec run from SCLK instead of MCLK. Use devm_clk_get_optional(), as es8311, es8316, es8323, es8326 and es8389 do for the same clock. Found with an experimental clang warning about NULL tests on functions that return error pointers. Tested only in QEMU with i2c-stub in place of the codec, not on real hardware. Fixes: de2b3119f9f7 ("ASoC: codecs: add support for ES8375") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20261003190738.77564-1-kmehltretter@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8375.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/es8375.c b/sound/soc/codecs/es8375.c index 80707d2768c6..bed872eafc95 100644 --- a/sound/soc/codecs/es8375.c +++ b/sound/soc/codecs/es8375.c @@ -677,7 +677,7 @@ static int es8375_read_device_properities(struct device *dev, struct es8375_priv return ret; } - es8375->mclk = devm_clk_get(dev, "mclk"); + es8375->mclk = devm_clk_get_optional(dev, "mclk"); if (IS_ERR(es8375->mclk)) return dev_err_probe(dev, PTR_ERR(es8375->mclk), "unable to get mclk\n"); From 157f7cba77a9dd3c3de9219c0d0fa2bb93d1a144 Mon Sep 17 00:00:00 2001 From: Mizuno Miotsuki Date: Fri, 18 Sep 2026 12:57:19 +0000 Subject: [PATCH 535/924] ASoC: codecs: rk3308: add support for codec revision B The RK3308 codec driver detects codec revision B (chip ID 0x3308), but currently rejects it during probe. Revision B needs an additional DAC output VCM charge and depop sequence using DAC_ANA_CON14 and DAC_ANA_CON15. Model the shared revision-B DAC output VCM as a playback-only DAPM supply used by both HPOUT and LINEOUT. Charge the reference while both CON15 channels are connected to VCM, then establish DC_FROM_INTERNAL as the playback baseline. LINEOUT events switch each enabled channel to LINEOUT_FROM_INTERNAL after its output driver is enabled and return it to DC_FROM_INTERNAL after disable. When the last analogue output powers down, return both channels to VCM and disable the shared reference. All new register writes are conditional on codec revision B. Capture-only streams do not traverse the new supply, so they do not touch DAC_ANA_CON14 or DAC_ANA_CON15. Revisions A and C gain no additional MMIO writes. For HPOUT-only playback, keep CON15 at DC_FROM_INTERNAL, matching the newer Rockchip 6.1 downstream driver. Physical HPOUT validation is still needed. Tested on a DFRobot UNIHIKER M10 V1.2 with codec revision B. Revision-B probe, the board MICBIAS1 route, and physical onboard microphone capture passed. A short capture-startup transient is also present with the Rockchip vendor driver unless a longer wait is added, so it is not treated as a regression and no generic 250 ms mute delay is added here. Capture-only isolation from DAC_ANA_CON14/15 and playback PCM, DAPM, and LINEOUT register endpoints were verified with the preceding clean LINEOUT-only candidate. The shared-output revision is compile-tested only. Physical LINEOUT and HPOUT audio were not tested because suitable hardware endpoints were unavailable. Signed-off-by: Mizuno Miotsuki Link: https://patch.msgid.link/20260918125711.94361-2-founder@crepuse.com Signed-off-by: Mark Brown --- sound/soc/codecs/rk3308_codec.c | 102 +++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c index abed31ba660c..a3d41cd8150d 100644 --- a/sound/soc/codecs/rk3308_codec.c +++ b/sound/soc/codecs/rk3308_codec.c @@ -192,6 +192,90 @@ static int rk3308_codec_pop_sound_set(struct snd_soc_dapm_widget *w, return 0; } +static int rk3308_codec_dac_output_vcm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component); + unsigned int val; + + if (rk3308->codec_ver != ACODEC_VERSION_B) + return 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Keep the DAC outputs at VCM while their reference is charged. */ + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, + RK3308_DAC_LINEOUT_POP_SOUND_L_MSK | + RK3308_DAC_LINEOUT_POP_SOUND_R_MSK, + RK3308_DAC_L_SEL_DC_FROM_VCM | + RK3308_DAC_R_SEL_DC_FROM_VCM); + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_CURRENT_CHARGE_MSK, 1); + regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_VCM_LINEOUT_EN); + for (val = 1; val <= 0xf; val++) { + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_CURRENT_CHARGE_MSK, val); + usleep_range(200, 400); + } + msleep(20); + break; + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, + RK3308_DAC_LINEOUT_POP_SOUND_L_MSK | + RK3308_DAC_LINEOUT_POP_SOUND_R_MSK, + RK3308_DAC_L_SEL_DC_FROM_INTERNAL | + RK3308_DAC_R_SEL_DC_FROM_INTERNAL); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, + RK3308_DAC_LINEOUT_POP_SOUND_L_MSK | + RK3308_DAC_LINEOUT_POP_SOUND_R_MSK, + RK3308_DAC_L_SEL_DC_FROM_VCM | + RK3308_DAC_R_SEL_DC_FROM_VCM); + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_CURRENT_CHARGE_MSK, 1); + regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_VCM_LINEOUT_EN); + break; + default: + break; + } + + return 0; +} + +static int rk3308_codec_lineout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component); + unsigned int mask = RK3308_DAC_LINEOUT_POP_SOUND_L_MSK << w->shift; + unsigned int val; + + if (rk3308->codec_ver != ACODEC_VERSION_B) + return 0; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = RK3308_DAC_L_SEL_LINEOUT_FROM_INTERNAL << w->shift; + break; + case SND_SOC_DAPM_POST_PMD: + val = RK3308_DAC_L_SEL_DC_FROM_INTERNAL << w->shift; + break; + default: + return 0; + } + + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, mask, val); + usleep_range(20, 40); + + return 0; +} + static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = { SND_SOC_DAPM_INPUT("MIC1"), SND_SOC_DAPM_INPUT("MIC2"), @@ -322,6 +406,10 @@ static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = { SND_SOC_DAPM_OUT_DRV_E("HPOUT_POP_SOUND_R", SND_SOC_NOPM, 4, 0, NULL, 0, rk3308_codec_pop_sound_set, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("DAC_OUTPUT_VCM", SND_SOC_NOPM, 0, 0, + rk3308_codec_dac_output_vcm_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_OUT_DRV("L_HPOUT_EN", RK3308_DAC_ANA_CON03, 1, 0, NULL, 0), SND_SOC_DAPM_OUT_DRV("R_HPOUT_EN", RK3308_DAC_ANA_CON03, 5, 0, NULL, 0), SND_SOC_DAPM_OUT_DRV("L_HPOUT_WORK", RK3308_DAC_ANA_CON03, 2, 0, NULL, 0), @@ -329,8 +417,12 @@ static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("HPOUT_L"), SND_SOC_DAPM_OUTPUT("HPOUT_R"), - SND_SOC_DAPM_OUT_DRV("L_LINEOUT_EN", RK3308_DAC_ANA_CON04, 0, 0, NULL, 0), - SND_SOC_DAPM_OUT_DRV("R_LINEOUT_EN", RK3308_DAC_ANA_CON04, 4, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV_E("L_LINEOUT_EN", RK3308_DAC_ANA_CON04, 0, 0, + NULL, 0, rk3308_codec_lineout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV_E("R_LINEOUT_EN", RK3308_DAC_ANA_CON04, 4, 0, + NULL, 0, rk3308_codec_lineout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_OUTPUT("LINEOUT_L"), SND_SOC_DAPM_OUTPUT("LINEOUT_R"), }; @@ -466,6 +558,8 @@ static const struct snd_soc_dapm_route rk3308_codec_dapm_routes[] = { { "HPOUT_R", NULL, "DAC_BUF_REF_R" }, { "L_HPOUT_EN", NULL, "DAC_L_HPMIX_SEL" }, { "R_HPOUT_EN", NULL, "DAC_R_HPMIX_SEL" }, + { "L_HPOUT_EN", NULL, "DAC_OUTPUT_VCM" }, + { "R_HPOUT_EN", NULL, "DAC_OUTPUT_VCM" }, { "L_HPOUT_WORK", NULL, "L_HPOUT_EN" }, { "R_HPOUT_WORK", NULL, "R_HPOUT_EN" }, { "HPOUT_POP_SOUND_L", NULL, "L_HPOUT_WORK" }, @@ -475,6 +569,8 @@ static const struct snd_soc_dapm_route rk3308_codec_dapm_routes[] = { { "L_LINEOUT_EN", NULL, "DAC_L_HPMIX_SEL" }, { "R_LINEOUT_EN", NULL, "DAC_R_HPMIX_SEL" }, + { "L_LINEOUT_EN", NULL, "DAC_OUTPUT_VCM" }, + { "R_LINEOUT_EN", NULL, "DAC_OUTPUT_VCM" }, { "LINEOUT_L", NULL, "L_LINEOUT_EN" }, { "LINEOUT_R", NULL, "R_LINEOUT_EN" }, }; @@ -866,7 +962,7 @@ static int rk3308_codec_get_version(struct rk3308_codec_priv *rk3308) break; case 0x3308: rk3308->codec_ver = ACODEC_VERSION_B; - return dev_err_probe(rk3308->dev, -EINVAL, "Chip version B not supported\n"); + break; case 0x3308c: rk3308->codec_ver = ACODEC_VERSION_C; break; From b42663cecdf4403a230baafb31bae136e90fd142 Mon Sep 17 00:00:00 2001 From: Vadym Shevchuk <0xsheff@gmail.com> Date: Mon, 5 Oct 2026 15:59:53 +0300 Subject: [PATCH 536/924] ALSA: usb-audio: Add quirk for Shanling UP4 The Shanling UP4 (0a12:1244, CSR-based, UAC1, full speed) sets the MaxPacketsOnly bit in its AS isochronous endpoint descriptor. With UAC_EP_CS_ATTR_FILL_MAX set, the driver fills every 1 ms packet up to wMaxPacketSize, i.e. 48 frames, regardless of the sample rate. At 44.1 kHz the device expects 44/45 frames per packet and outputs only silence. 48 kHz works because both packet sizes are the same there. Measured with aplay on hw:UP4,0, 4 s of S16_LE stereo: before: 44.1 kHz consumed at ~48030 frames/s, done in 3.68 s, silent after: 44.1 kHz consumed at ~44220 frames/s, done in 4.00 s, audible 48 kHz plays fine both before and after the change. Regular music playback at 44.1 kHz through PipeWire sounds fine as well. Clear the attribute for this device, as is already done for the MOTU MicroBook IIc. Since the packet sizes are equal at 48 kHz, this does not change anything for other devices that may share this CSR USB ID and only use 48 kHz. Link: https://lore.kernel.org/all/CAPHAwzdUNRbZjBQuYKLf18EK_QGq0gR6Vx3YMZ=ffYKBogUtLA@mail.gmail.com/ Signed-off-by: Vadym Shevchuk <0xsheff@gmail.com> Link: https://patch.msgid.link/20261005125953.190316-1-0xsheff@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 3ef983e23a2c..170f68db74fa 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2381,6 +2381,14 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, */ fp->attributes &= ~UAC_EP_CS_ATTR_FILL_MAX; break; + case USB_ID(0x0a12, 0x1244): /* Shanling UP4 */ + /* + * MaxPacketsOnly is set, but the device expects nominal-size + * packets. Filling 48 frames per packet at 44.1 kHz results + * in silence. No effect at 48 kHz, where both sizes match. + */ + fp->attributes &= ~UAC_EP_CS_ATTR_FILL_MAX; + break; case USB_ID(0x1224, 0x2a25): /* Jieli Technology USB PHY 2.0 */ /* mic works only when ep packet size is set to wMaxPacketSize */ fp->attributes |= UAC_EP_CS_ATTR_FILL_MAX; From 2964a4aae2e04ecccd1512cf44aec025d1f77992 Mon Sep 17 00:00:00 2001 From: Baoquan He Date: Thu, 3 Sep 2026 19:18:26 +0800 Subject: [PATCH 537/924] ALSA: n64: don't use GFP_DMA when calling dma_alloc_coherent() dma_alloc_coherent() allocates the DMA buffer with the device's addressing limitation in mind; the DMA core picks the zone from the device's coherent DMA mask and ignores GFP_DMA passed by the caller. Remove the redundant GFP_DMA flag. Link: https://lore.kernel.org/20260903111836.1777265-4-hebaoquan@kylinos.cn Signed-off-by: Baoquan He Signed-off-by: Andrew Morton Cc: Christoph Hellwig Cc: Harry Yoo Signed-off-by: Takashi Iwai --- sound/mips/snd-n64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/mips/snd-n64.c b/sound/mips/snd-n64.c index 8cc001c58d5e..da889570cebc 100644 --- a/sound/mips/snd-n64.c +++ b/sound/mips/snd-n64.c @@ -308,7 +308,7 @@ static int __init n64audio_probe(struct platform_device *pdev) card->private_free = n64audio_free; priv->ring_base = dma_alloc_coherent(card->dev, 32 * 1024, &priv->ring_base_dma, - GFP_DMA|GFP_KERNEL); + GFP_KERNEL); if (!priv->ring_base) return -ENOMEM; From 1b207c85551fd74854d632116849295b2305245f Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 4 Oct 2026 00:34:51 -0400 Subject: [PATCH 538/924] ASoC: stm: stm32_i2s: request IRQ after regmap initialization The ISR accesses the managed regmap, but probe requests the IRQ before creating it. An early interrupt can reach an uninitialized regmap. Reverse-order cleanup can free the regmap while the IRQ remains active. Request the IRQ after regmap initialization so probe unwind and removal free and synchronize it before releasing the regmap. The teardown ordering issue was found by static analysis. Fixes: e4e6ec7b127c ("ASoC: stm32: Add I2S driver") Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20261004043451.1138613-1-mhun512@gmail.com Signed-off-by: Mark Brown --- sound/soc/stm/stm32_i2s.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c index a59ad9442372..22196f789ae4 100644 --- a/sound/soc/stm/stm32_i2s.c +++ b/sound/soc/stm/stm32_i2s.c @@ -1197,7 +1197,7 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, struct device_node *np = pdev->dev.of_node; struct reset_control *rst; struct resource *res; - int irq, ret; + int ret; if (!np) return -ENODEV; @@ -1243,16 +1243,6 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, return ret; } - /* Get irqs */ - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, 0, - dev_name(&pdev->dev), i2s); - if (ret) - return ret; - /* Reset */ rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); if (IS_ERR(rst)) @@ -1277,7 +1267,7 @@ static int stm32_i2s_probe(struct platform_device *pdev) { struct stm32_i2s_data *i2s; u32 val; - int ret; + int irq, ret; i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); if (!i2s) @@ -1303,6 +1293,15 @@ static int stm32_i2s_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), "Regmap init error\n"); + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, 0, + dev_name(&pdev->dev), i2s); + if (ret) + return ret; + ret = snd_dmaengine_pcm_register(&pdev->dev, &stm32_i2s_pcm_config, 0); if (ret) return ret; From ca552e3140e736ed6704fe5d47439d8f2d40b42e Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sat, 3 Oct 2026 00:10:45 -0400 Subject: [PATCH 539/924] ASoC: codecs: rt5682s: disable jack detection work on shutdown The jack detection and button check works can queue each other. The shutdown path cancels jack_detect_work first, but jd_check_work can queue it again after that cancellation returns. Canceling jd_check_work then leaves jack_detect_work pending or running while the device is reset and, on removal, its managed resources are released. Disable and drain both delayed works so neither callback can revive the other after its cancellation. The I2C remove callback uses the same shutdown path. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: c07ac3ee76e5 ("ASoC: rt5682s: Fix the wrong jack type detected") Cc: stable@vger.kernel.org # 6.10+ Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20261003041045.650207-1-mhun512@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5682s.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c index 0f80e4c32f57..49ed7793d3a2 100644 --- a/sound/soc/codecs/rt5682s.c +++ b/sound/soc/codecs/rt5682s.c @@ -3318,8 +3318,8 @@ static void rt5682s_i2c_shutdown(struct i2c_client *client) struct rt5682s_priv *rt5682s = i2c_get_clientdata(client); disable_irq(client->irq); - cancel_delayed_work_sync(&rt5682s->jack_detect_work); - cancel_delayed_work_sync(&rt5682s->jd_check_work); + disable_delayed_work_sync(&rt5682s->jack_detect_work); + disable_delayed_work_sync(&rt5682s->jd_check_work); rt5682s_reset(rt5682s); } From 0504e71cc7b8fab13dc5c3387ba22d9f2500c37c Mon Sep 17 00:00:00 2001 From: Sheetal Date: Mon, 5 Oct 2026 08:55:59 +0000 Subject: [PATCH 540/924] ASoC: tegra: Fix ASRC Stream6 input threshold control The Stream6 Input Threshold control points at stream index 4, which is already used by Stream5. The threshold get and put callbacks derive the lane ID from the register offset, so the duplicate index makes Stream6 operate on lane 4 and leaves lane 5 inaccessible from userspace. Use stream index 5 for Stream6. Fixes: a2df8c2d5b36 ("ASoC: tegra: Add Tegra186 based ASRC driver") Reviewed-by: Thierry Reding Signed-off-by: Sheetal Link: https://patch.msgid.link/20261005085559.2134401-1-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra186_asrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c index 7f360dfaf8b1..d8ae5d997615 100644 --- a/sound/soc/tegra/tegra186_asrc.c +++ b/sound/soc/tegra/tegra186_asrc.c @@ -828,7 +828,7 @@ static const struct snd_kcontrol_new tegra186_asrc_controls[] = { tegra186_asrc_put_input_threshold), SOC_SINGLE_EXT("Stream6 Input Threshold", - ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 4), 0, 3, 0, + ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 5), 0, 3, 0, tegra186_asrc_get_input_threshold, tegra186_asrc_put_input_threshold), From 8acd6469f42784f38872ad83b14c41152d986b63 Mon Sep 17 00:00:00 2001 From: Scott Gauche Date: Mon, 5 Oct 2026 11:40:17 -0400 Subject: [PATCH 541/924] ALSA: hda/realtek: Fix audio after Windows reboot on ASUS Q423SA The ASUS Vivobook S 14 Q423SA-U5512 (DMI family S5406SA) with the ALC294 codec has working analog audio when booted directly after a full shutdown. After a warm reboot from Windows 11, audio works briefly during Linux startup, then both the internal speakers and headphones become silent. PCM playback continues without errors. Coefficient 0x1b is 0x4e4b on a direct cold boot, but 0x4a4b after the Windows warm reboot. Restoring bit 10 changes it back to 0x4e4b and immediately enables both analog outputs. Speaker playback, headphone playback, jack switching, cold boot, and suspend/resume were tested successfully. The same 0x4a4b state and immediate recovery with 0x4e4b were independently confirmed on another S5406SA with subsystem 1043:10c4. The same coefficient write is already used by ALC294_FIXUP_ASUS_COEF_1B for the ASUS UX431DA. That fixup cannot be reused here because it chains to ALC289_FIXUP_ASUS_GA401 and applies unrelated DAC routing and microphone pin configuration. Add a model-specific fixup for subsystem 1043:10c4 and chain it to the existing ALC2XX_FIXUP_HEADSET_MIC fixup instead. Link: https://github.com/thesofproject/sof/issues/11051 Fixes: f2d08f3651fb ("ALSA: hda/realtek: Fix headset mic on ASUS Vivobook S14 S5406SA") Signed-off-by: Scott Gauche Link: https://patch.msgid.link/20261005154017.1164657-1-scott-dev@scottgauche.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 4f5537cabb00..3a3244fe3a8f 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4289,6 +4289,7 @@ enum { ALC294_FIXUP_ASUS_I2C_HEADSET_MIC, ALC294_FIXUP_ASUS_SPI_HEADSET_MIC, ALC294_FIXUP_ASUS_SPK, + ALC294_FIXUP_ASUS_Q423SA, ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE, ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE, ALC255_FIXUP_ACER_HEADSET_MIC, @@ -5772,6 +5773,17 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC }, + [ALC294_FIXUP_ASUS_Q423SA] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Restore bit 10 lost after reboot from Windows */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x1b }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x4e4b }, + { } + }, + .chained = true, + .chain_id = ALC2XX_FIXUP_HEADSET_MIC, + }, [ALC295_FIXUP_CHROME_BOOK] = { .type = HDA_FIXUP_FUNC, .v.func = alc295_fixup_chromebook, @@ -7965,7 +7977,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK), SND_PCI_QUIRK(0x1043, 0x10a4, "ASUS TP3407SA", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), - SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS Vivobook S14 S5406SA", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS S5406SA/Q423SA", ALC294_FIXUP_ASUS_Q423SA), SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x10d3, "ASUS K6500ZC", ALC294_FIXUP_ASUS_SPK), SND_PCI_QUIRK(0x1043, 0x1154, "ASUS TP3607SH", ALC287_FIXUP_TAS2781_I2C), From 5cc1749b5f154a2cb97439cf084afce788f36102 Mon Sep 17 00:00:00 2001 From: Denis Yakovlev Date: Mon, 5 Oct 2026 23:38:36 +0300 Subject: [PATCH 542/924] ALSA: hda/realtek: Drop unneeded DAC override for HONOR MagicBook Pro 16 2024 Commit 1e3e378d63be ("ALSA: hda/realtek: Enable bass speakers on HONOR MagicBook Pro 16 2024") restricted the speaker pin 0x1b to DAC 0x02, on the assumption that the second speaker pair would otherwise end up on DAC 0x03, as channels 3/4 that a stereo stream never reaches. That assumption was wrong: without the override, pin 0x1b is still connected to DAC 0x02, the only DAC that pin 0x14 can use, so the override changes nothing. With and without it the codec routing is identical and the speakers behave the same with nothing plugged in, with headphones and with a CTIA headset inserted; without it they also keep working after suspend/resume. That commit also got the speaker roles the wrong way round. Pin 0x1b, which the BIOS does configure, drives the bottom speakers, which carry the low and mid frequencies; pin 0x14 drives the tweeters next to the keyboard, and those are the speakers that stay silent without the quirk. Measured with the internal microphones, enabling one pin at a time: at 1 kHz the 0x14 pair alone is more than 30 dB below the 0x1b pair alone, while at 6 kHz the two are within 10 dB of each other. Drop the DAC override and correct the pin comment, so the quirk only sets pin configurations, like the one for the sibling M1020 board (HONOR MRB-XXX). Fixes: 1e3e378d63be ("ALSA: hda/realtek: Enable bass speakers on HONOR MagicBook Pro 16 2024") Assisted-by: LLM Signed-off-by: Denis Yakovlev Link: https://patch.msgid.link/20261005203836.11074-1-mfthesun@vivaldi.net Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 3a3244fe3a8f..447c0ba358d8 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -2763,17 +2763,6 @@ static void alc294_fixup_bass_speaker_15(struct hda_codec *codec, } } -/* route Speaker (0x1b) to DAC 0x02, the only DAC reachable from Bass Speaker (0x14) */ -static void alc256_fixup_honor_dra_xx_share_dac(struct hda_codec *codec, - const struct hda_fixup *fix, int action) -{ - if (action == HDA_FIXUP_ACT_PRE_PROBE) { - static const hda_nid_t conn[] = { 0x02 }; - - snd_hda_override_conn_list(codec, 0x1b, ARRAY_SIZE(conn), conn); - } -} - /* Hook to update amp GPIO4 for automute */ static void alc280_hp_gpio4_automute_hook(struct hda_codec *codec, struct hda_jack_callback *jack) @@ -4477,7 +4466,6 @@ enum { ALC236_FIXUP_HP_DMIC, ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS, - ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC, ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE, ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX, ALC287_FIXUP_ACER_MICMUTE_LED, @@ -7227,17 +7215,11 @@ static const struct hda_fixup alc269_fixups[] = { [ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { - { 0x14, 0x90170111 }, /* bass speakers */ + { 0x14, 0x90170111 }, /* tweeters (top speakers) */ { 0x19, 0x03a1113c }, /* headset mic, without its own jack detect */ { } }, .chained = true, - .chain_id = ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC - }, - [ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC] = { - .type = HDA_FIXUP_FUNC, - .v.func = alc256_fixup_honor_dra_xx_share_dac, - .chained = true, .chain_id = ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE }, [ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE] = { From 9867312e9fd2a29f69c9b425e10a7be555b773d3 Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Tue, 6 Oct 2026 09:53:15 +0530 Subject: [PATCH 543/924] ASoC: amd: acp-legacy-mach: skip dai link for amp reference stream Amps can provide reference stream to hosts DSP which can be used for input audio proocessing. But legacy AMD soc doesn't support such a feature. So skip adding the dai link for the same. Signed-off-by: Niranjan H Y Suggested-by: Vijendar Mukunda Reviewed-by: Bard Liao Reviewed-by: Vijendar Mukunda Link: https://patch.msgid.link/20261006042315.1038-1-niranjan.hy@ti.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-legacy-mach.c | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index 1a05d4288a46..a8b62817b234 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -265,6 +265,14 @@ static int create_sdw_dailink(struct snd_soc_card *card, if (!soc_end->dai_info->direction[stream]) continue; + /* + * skip adding reference stream for amp type + * as legacy AMD doesn't use the DSP. + */ + if (soc_end->dai_info->dai_type == SOC_SDW_DAI_TYPE_AMP && + stream == SNDRV_PCM_STREAM_CAPTURE) + continue; + int link_num = ffs(soc_end->link_mask) - 1; cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, @@ -448,6 +456,30 @@ static int soc_card_dai_links_create(struct snd_soc_card *card) sdw_be_num = ret; + /* + * Legacy AMD has no DSP, so AMP reference capture streams are not + * supported. Subtract them from num_devs so create_sdw_dailink() + * uses the correct count directly, and decrement sdw_be_num when + * all capture endpoints on a DAI are AMP type. + */ + for (int i = 0; i < num_ends; i++) { + struct asoc_sdw_dailink *d = &soc_dais[i]; + struct asoc_sdw_endpoint *e; + + if (!d->initialised) + break; + if (!d->num_devs[SNDRV_PCM_STREAM_CAPTURE]) + continue; + + list_for_each_entry(e, &d->endpoints, list) { + if (e->dai_info->direction[SNDRV_PCM_STREAM_CAPTURE] && + e->dai_info->dai_type == SOC_SDW_DAI_TYPE_AMP) + d->num_devs[SNDRV_PCM_STREAM_CAPTURE]--; + } + if (!d->num_devs[SNDRV_PCM_STREAM_CAPTURE]) + sdw_be_num--; + } + /* enable dmic */ if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) dmic_num = 1; From 8afbacbcaebc41628a979aaf3fc07866e581fb9b Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 29 Sep 2026 04:31:06 +0000 Subject: [PATCH 544/924] ASoC: codecs: cpcap: tidyup cpcap_soc_probe() cpcap_soc_probe() (Z) gets drvdata from Component dev (A), and set it to Component dev (B) again. (= snd_soc_component_set_drvdata() calls dev_set_drvdata() inside). It is point less. Let's remove (B). (Z) static int cpcap_soc_probe(...) { ... (A) struct cpcap_audio *cpcap = dev_get_drvdata(component->dev); ... (B) snd_soc_component_set_drvdata(component, cpcap); ... } Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87qzicel11.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/cpcap.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index dda68165b817..eaaf141dea57 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -1625,7 +1625,6 @@ static int cpcap_soc_probe(struct snd_soc_component *component) struct cpcap_audio *cpcap = dev_get_drvdata(component->dev); int err; - snd_soc_component_set_drvdata(component, cpcap); cpcap->component = component; err = snd_soc_card_jack_new(card, "Headphones", From be2172758f2b7c801ae80b9c8d1510b31eeffdaa Mon Sep 17 00:00:00 2001 From: Ajay Kumar Nandam Date: Wed, 23 Sep 2026 22:03:38 +0530 Subject: [PATCH 545/924] ASoC: qcom: q6apm: clear g_apm on driver removal The global g_apm pointer is set during apm_probe() but never cleared in apm_remove(). After the driver is removed the devm-managed struct q6apm is freed, leaving g_apm dangling. A subsequent call to q6apm_is_adsp_ready() dereferences the freed pointer. Clear g_apm in apm_remove() before the component is unregistered so that q6apm_is_adsp_ready() returns false instead of triggering a use-after-free. Fixes: 5477518b8a0e ("ASoC: qdsp6: audioreach: add q6apm support") Signed-off-by: Ajay Kumar Nandam Link: https://patch.msgid.link/20260923-vmid-v4-v6-1-3d61a0319b1c@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index f167b9dae3fa..eef4b496d2aa 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -900,6 +900,7 @@ static int apm_probe(gpr_device_t *gdev) static void apm_remove(gpr_device_t *gdev) { + g_apm = NULL; of_platform_depopulate(&gdev->dev); snd_soc_unregister_component(&gdev->dev); } From d3ad9ee48425f06b2ae512a9d76c8ba855decdb9 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Nandam Date: Wed, 23 Sep 2026 22:03:39 +0530 Subject: [PATCH 546/924] ASoC: qcom: qdsp6: generalize GPR service domain AudioReach builds APM and PRM command packets with the GPR destination domain hardcoded to GPR_DOMAIN_ID_ADSP. This assumes audio is always served by the ADSP, which is true for all currently supported targets. On platforms such as Qualcomm Shikra, audio is served by the modem DSP (mDSP) instead. The GPR node in DT already describes which DSP backs the service via its qcom,domain property (e.g. GPR_DOMAIN_ID_MODEM), and the GPR core exposes it as gdev->domain_id. But the AudioReach packet builders ignore this and always target the ADSP, so every APM/PRM command is routed to the wrong DSP on mDSP targets and audio does not function. Fix this by reading the GPR destination domain from gdev->domain_id and stamping it in the send helpers (q6apm_send_cmd_sync, audioreach_graph_send_cmd_sync, q6prm_send_cmd_sync) just before dispatch. This centralizes the domain decision at the send layer rather than threading it through every packet-allocation call site. For the small number of async data-path sends that bypass the sync helpers (write, read, compr, EOS), the domain is stamped inline before gpr_send_port_pkt(). When no domain is available the helper falls back to GPR_DOMAIN_ID_ADSP, so all existing ADSP targets remain unchanged. Co-developed-by: Pratyush Meduri Signed-off-by: Pratyush Meduri Signed-off-by: Ajay Kumar Nandam Link: https://patch.msgid.link/20260923-vmid-v4-v6-2-3d61a0319b1c@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/audioreach.c | 12 +++++++++--- sound/soc/qcom/qdsp6/audioreach.h | 22 +++++++++++++++------- sound/soc/qcom/qdsp6/q6apm.c | 8 +++++++- sound/soc/qcom/qdsp6/q6apm.h | 2 +- sound/soc/qcom/qdsp6/q6prm.c | 2 ++ 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index cce0ad31ff0c..d2dc5d37bccf 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -593,10 +593,10 @@ EXPORT_SYMBOL_GPL(audioreach_alloc_graph_pkt); int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, - const struct gpr_pkt *pkt, uint32_t rsp_opcode) + struct gpr_pkt *pkt, uint32_t rsp_opcode) { - const struct gpr_hdr *hdr = &pkt->hdr; + struct gpr_hdr *hdr = &pkt->hdr; int rc; mutex_lock(cmd_lock); @@ -636,10 +636,12 @@ int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, } EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync); -int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt, +int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, struct gpr_pkt *pkt, uint32_t rsp_opcode) { + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return audioreach_send_cmd_sync(graph->dev, NULL, &graph->result, &graph->lock, graph->port, &graph->cmd_wait, pkt, rsp_opcode); } @@ -994,6 +996,8 @@ int audioreach_compr_set_param(struct q6apm_graph *graph, if (rc) return rc; + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(audioreach_compr_set_param); @@ -1609,6 +1613,8 @@ int audioreach_shared_memory_send_eos(struct q6apm_graph *graph) eos->policy = WR_SH_MEM_EP_EOS_POLICY_LAST; + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(audioreach_shared_memory_send_eos); diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index 35541b2d8c99..587f14ce71d2 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -1002,14 +1002,19 @@ struct audioreach_module_config { }; /* Packet Allocation routines */ -void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, uint32_t - token); +static inline u16 audioreach_gpr_dest_domain(gpr_device_t *gdev) +{ + return gdev && gdev->domain_id ? gdev->domain_id : GPR_DOMAIN_ID_ADSP; +} + +void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, + uint32_t token); void audioreach_set_default_channel_mapping(u8 *ch_map, int num_channels); void *audioreach_alloc_cmd_pkt(int payload_size, uint32_t opcode, uint32_t token, uint32_t src_port, uint32_t dest_port); void *audioreach_alloc_apm_pkt(int pkt_size, uint32_t opcode, uint32_t token, - uint32_t src_port); + uint32_t src_port); void *audioreach_alloc_pkt(int payload_size, uint32_t opcode, uint32_t token, uint32_t src_port, uint32_t dest_port); @@ -1020,10 +1025,13 @@ int audioreach_tplg_init(struct snd_soc_component *component); /* Module specific */ void audioreach_graph_free_buf(struct q6apm_graph *graph); -int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, - struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, - const struct gpr_pkt *pkt, uint32_t rsp_opcode); -int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt, +int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, + struct gpr_ibasic_rsp_result_t *result, + struct mutex *cmd_lock, gpr_port_t *port, + wait_queue_head_t *cmd_wait, + struct gpr_pkt *pkt, uint32_t rsp_opcode); +int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, + struct gpr_pkt *pkt, uint32_t rsp_opcode); int audioreach_set_media_format(struct q6apm_graph *graph, const struct audioreach_module *module, diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index eef4b496d2aa..ea6680925f21 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -29,11 +29,13 @@ struct apm_graph_mgmt_cmd { static struct q6apm *g_apm; -int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt, +int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, uint32_t rsp_opcode) { gpr_device_t *gdev = apm->gdev; + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(gdev); + return audioreach_send_cmd_sync(&gdev->dev, gdev, &apm->result, &apm->lock, NULL, &apm->wait, pkt, rsp_opcode); } @@ -502,6 +504,8 @@ int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts, mutex_unlock(&graph->lock); + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(q6apm_write_async); @@ -536,6 +540,8 @@ int q6apm_read(struct q6apm_graph *graph) mutex_unlock(&graph->lock); + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(q6apm_read); diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 5cb51ca491dc..9092359ccf90 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -147,7 +147,7 @@ int q6apm_alloc_fragments(struct q6apm_graph *graph, int q6apm_free_fragments(struct q6apm_graph *graph, unsigned int dir); int q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int graph_id); /* Helpers */ -int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt, +int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, uint32_t rsp_opcode); /* Callback for graph specific */ diff --git a/sound/soc/qcom/qdsp6/q6prm.c b/sound/soc/qcom/qdsp6/q6prm.c index 1f3ce4cc0837..2042ff1e8ff1 100644 --- a/sound/soc/qcom/qdsp6/q6prm.c +++ b/sound/soc/qcom/qdsp6/q6prm.c @@ -57,6 +57,8 @@ struct prm_cmd_release_rsc { static int q6prm_send_cmd_sync(struct q6prm *prm, struct gpr_pkt *pkt, uint32_t rsp_opcode) { + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(prm->gdev); + return audioreach_send_cmd_sync(prm->dev, prm->gdev, &prm->result, &prm->lock, NULL, &prm->wait, pkt, rsp_opcode); } From 7ff0bdaf929229b0bc1f3786fa7590ace4b62796 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Nandam Date: Wed, 23 Sep 2026 22:03:40 +0530 Subject: [PATCH 547/924] ASoC: dt-bindings: qcom,q6apm-dai: add memory-region and relax iommus On modem-DSP (mDSP) platforms the DSP runs in a stage-2 protected context and cannot use the SMMU. PCM buffers must live in reserved-memory carveouts that are SCM-assigned to the appropriate VMIDs. Add a memory-region property (1-2 entries) for the control-path and data-path carveouts, and require q6apm-dais nodes to describe exactly one access path: iommus for SMMU-backed systems or memory-region for mDSP systems. Remove iommus from the common required list since mDSP platforms have neither IOMMU nor VMID DT properties. Signed-off-by: Ajay Kumar Nandam Link: https://patch.msgid.link/20260923-vmid-v4-v6-3-3d61a0319b1c@oss.qualcomm.com Signed-off-by: Mark Brown --- .../bindings/sound/qcom,q6apm-dai.yaml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml b/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml index 9e5b30d9c6e6..c1f9fc9eab64 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml @@ -20,9 +20,24 @@ properties: minItems: 1 maxItems: 2 + memory-region: + minItems: 1 + items: + - description: + Control-path buffer (audio_heap_mem) for platforms where the DSP + runs in a stage-2 protected context (e.g. modem-DSP). + - description: + Data-path buffer pool from which per-stream DMA buffers are + allocated. + required: - compatible - - iommus + +oneOf: + - required: + - iommus + - required: + - memory-region additionalProperties: false From bb8afd437d79161592663142a67179565dce4f41 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Nandam Date: Wed, 23 Sep 2026 22:03:41 +0530 Subject: [PATCH 548/924] ASoC: qcom: q6apm-dai: add SCM buffer assignment for mDSP platforms On platforms such as Qualcomm Shikra, audio is served by the modem DSP (mDSP) which runs in a stage-2 protected context. Unlike ADSP targets where SMMU-mapped system RAM is directly accessible, the mDSP cannot reach the PCM buffers unless they are explicitly SCM-assigned to the appropriate Virtual Machine IDs (VMIDs). Without this assignment, audio does not function on these platforms. At probe time, the driver reads the GPR domain_id from the parent APM device to determine which DSP serves audio. When domain_id matches GPR_DOMAIN_ID_ADSP the existing SMMU/iommus path is taken and no new code is exercised. When domain_id matches GPR_DOMAIN_ID_MODEM, the driver verifies that qcom_scm is available (deferring otherwise) and that iommus is absent (the two mechanisms are mutually exclusive), then enables the SCM assignment path. In the SCM path the driver parses the optional memory-region entries in DT. The first entry (memory-region[0] / audio_heap_mem) is the control-path carveout used by the DSP firmware for command traffic; since the mDSP operates on stage-2 protected memory, this carveout must be SCM-assigned at probe time itself for the firmware to function. It is SCM-assigned to HLOS (retained as source owner, RW) plus MSS_MSA and LPASS (consumer VMIDs, both RW) and restored to HLOS-only ownership via devm_add_action_or_reset() at device removal. The second entry (memory-region[1]) is the data-path buffer pool from which per-stream DMA buffers are carved out. This pool is attached via of_reserved_mem_device_init_by_idx() so that PCM buffers allocate directly from the carveout instead of system RAM. The data-path pool (memory-region[1]) is SCM-assigned to the consumer VMIDs as a single whole-pool operation at probe time, rather than per-stream in pcm_new()/compr_open(). qcom_scm_assign_mem() consumes an entry in a small fixed-size TZ memory-protection table that is shared platform-wide; assigning individual per-stream slices (up to Q6APM_POOL_MAX_STREAMS times) exhausts that table and hangs the SMC call, which was observed as a crash while bringing up the sound card on Shikra. Since the whole pool is already accessible to the mDSP once assigned, pcm_new()/pcm_free()/compr_open()/compr_free() skip the per-buffer SCM assign/unassign entirely when the data-path pool is in use (has_reserved_mem), and only fall back to per-buffer assignment when use_scm_assign is set without a data-path pool present. Compressed audio streams follow the same pattern: the DMA buffer allocated in compr_open() is SCM-assigned immediately after allocation and unassigned in compr_free() before the buffer is freed, unless it was carved from the pre-assigned data-path pool. The VMIDs are static per SoC and hardcoded in the driver (HLOS, MSS_MSA, LPASS) rather than read from DT, following the upstream pattern used by rmtfs_mem and qcom_q6v5_pas. Buffer constraints are capped at reserved_buf_size when the data-path pool is present, and snd_pcm_set_fixed_buffer_all() is used for both paths so the carveout is not subject to the preallocate_dma module parameter. All new code paths are gated on use_scm_assign (false when domain_id is not GPR_DOMAIN_ID_MODEM), ensuring existing ADSP/iommus targets are completely unaffected. Co-developed-by: Mohit Sharma Signed-off-by: Mohit Sharma Signed-off-by: Ajay Kumar Nandam Link: https://patch.msgid.link/20260923-vmid-v4-v6-4-3d61a0319b1c@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/Kconfig | 1 + sound/soc/qcom/qdsp6/q6apm-dai.c | 317 +++++++++++++++++++++++++++++-- 2 files changed, 297 insertions(+), 21 deletions(-) diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index e6e24f3b9922..991feb317940 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -102,6 +102,7 @@ config SND_SOC_QDSP6_ASM_DAI config SND_SOC_QDSP6_APM_DAI tristate select SND_SOC_COMPRESS + select QCOM_SCM config SND_SOC_QDSP6_APM_LPASS_DAI tristate diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 033197bdd870..b0b10ba51c99 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -1,20 +1,25 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2021, Linaro Limited -#include +#include +#include +#include #include +#include +#include #include +#include #include +#include #include #include -#include -#include #include #include +#include +#include +#include #include #include -#include -#include #include "q6apm.h" #define DRV_NAME "q6apm-dai" @@ -36,6 +41,16 @@ #define COMPR_PLAYBACK_MIN_NUM_FRAGMENTS (4) #define SID_MASK_DEFAULT 0xF +#define Q6APM_MAX_SCM_REGIONS 16 +#define Q6APM_POOL_MAX_STREAMS 8 + +struct q6apm_scm_region { + phys_addr_t addr; + size_t size; + u64 src_perms; + bool assigned; +}; + static const struct snd_compr_codec_caps q6apm_compr_caps = { .num_descriptors = 1, .descriptor[0].max_ch = 2, @@ -84,9 +99,103 @@ struct q6apm_dai_rtd { }; struct q6apm_dai_data { + struct device *dev; long long sid; + bool use_scm_assign; + bool has_reserved_mem; + size_t reserved_buf_size; + struct mutex scm_lock; /* protects scm_regions and num_scm_regions */ + struct q6apm_scm_region scm_regions[Q6APM_MAX_SCM_REGIONS]; + int num_scm_regions; }; +static int q6apm_dai_scm_assign(struct q6apm_dai_data *pdata, + phys_addr_t addr, size_t size) +{ + struct qcom_scm_vmperm dst[] = { + { .vmid = QCOM_SCM_VMID_HLOS, .perm = QCOM_SCM_PERM_RW }, + { .vmid = QCOM_SCM_VMID_MSS_MSA, .perm = QCOM_SCM_PERM_RW }, + { .vmid = QCOM_SCM_VMID_LPASS, .perm = QCOM_SCM_PERM_RW }, + }; + struct q6apm_scm_region *r; + u64 src = BIT(QCOM_SCM_VMID_HLOS); + int ret; + + mutex_lock(&pdata->scm_lock); + + if (pdata->num_scm_regions >= Q6APM_MAX_SCM_REGIONS) { + mutex_unlock(&pdata->scm_lock); + return -ENOSPC; + } + + ret = qcom_scm_assign_mem(addr, size, &src, dst, ARRAY_SIZE(dst)); + if (ret) { + mutex_unlock(&pdata->scm_lock); + return ret; + } + + r = &pdata->scm_regions[pdata->num_scm_regions++]; + r->addr = addr; + r->size = size; + r->src_perms = src; + r->assigned = true; + + mutex_unlock(&pdata->scm_lock); + + return 0; +} + +static void q6apm_dai_scm_unassign(struct q6apm_dai_data *pdata, + phys_addr_t addr) +{ + struct qcom_scm_vmperm hlos = { + .vmid = QCOM_SCM_VMID_HLOS, + .perm = QCOM_SCM_PERM_RW, + }; + int i; + + mutex_lock(&pdata->scm_lock); + + for (i = 0; i < pdata->num_scm_regions; i++) { + if (pdata->scm_regions[i].addr != addr || + !pdata->scm_regions[i].assigned) + continue; + + if (qcom_scm_assign_mem(addr, pdata->scm_regions[i].size, + &pdata->scm_regions[i].src_perms, + &hlos, 1)) { + dev_err(pdata->dev, "SCM unassign %pa failed\n", &addr); + mutex_unlock(&pdata->scm_lock); + return; + } + + pdata->scm_regions[i].assigned = false; + pdata->num_scm_regions--; + pdata->scm_regions[i] = pdata->scm_regions[pdata->num_scm_regions]; + mutex_unlock(&pdata->scm_lock); + return; + } + + mutex_unlock(&pdata->scm_lock); +} + +static void q6apm_dai_scm_cleanup(void *data) +{ + struct q6apm_dai_data *pdata = data; + int i; + + for (i = pdata->num_scm_regions - 1; i >= 0; i--) { + if (pdata->scm_regions[i].assigned) + q6apm_dai_scm_unassign(pdata, + pdata->scm_regions[i].addr); + } +} + +static void q6apm_dai_reserved_mem_release(void *data) +{ + of_reserved_mem_device_release(data); +} + static const struct snd_pcm_hardware q6apm_dai_hardware_capture = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | @@ -409,8 +518,11 @@ static int q6apm_dai_open(struct snd_soc_component *component, } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + size_t buf_max = pdata->has_reserved_mem ? pdata->reserved_buf_size : + BUFFER_BYTES_MAX; + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, - BUFFER_BYTES_MIN, BUFFER_BYTES_MAX); + BUFFER_BYTES_MIN, buf_max); if (ret < 0) { dev_err(dev, "constraint for buffer bytes min max ret = %d\n", ret); goto err; @@ -431,17 +543,20 @@ static int q6apm_dai_open(struct snd_soc_component *component, } runtime->private_data = prtd; - runtime->dma_bytes = BUFFER_BYTES_MAX; + runtime->dma_bytes = pdata->has_reserved_mem ? pdata->reserved_buf_size : + BUFFER_BYTES_MAX; if (pdata->sid < 0) prtd->phys = substream->dma_buffer.addr; else prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32); if (q6apm_is_graph_in_push_pull_mode(prtd->graph)) { + size_t buf_max = pdata->has_reserved_mem ? pdata->reserved_buf_size : + BUFFER_BYTES_MAX; void *pos_buffer; - prtd->pos_phys = prtd->phys + BUFFER_BYTES_MAX; - pos_buffer = (void *)(substream->dma_buffer.area + BUFFER_BYTES_MAX); + prtd->pos_phys = prtd->phys + buf_max; + pos_buffer = (void *)(substream->dma_buffer.area + buf_max); prtd->pos_buffer = (struct sh_mem_pull_push_mode_position_buffer *)(pos_buffer); } @@ -535,6 +650,7 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, { struct q6apm_dai_data *pdata; struct device *dev = component->dev; + size_t buf_max; phys_addr_t phys; int ret; @@ -544,20 +660,23 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, return -EINVAL; } + buf_max = pdata->has_reserved_mem ? pdata->reserved_buf_size : + BUFFER_BYTES_MAX; + if (pdata->sid < 0) phys = substream->dma_buffer.addr; else phys = substream->dma_buffer.addr | (pdata->sid << 32); - ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, BUFFER_BYTES_MAX); + ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, buf_max); if (ret < 0) dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret); if (is_push_pull) { if (pdata->sid < 0) - phys = substream->dma_buffer.addr + BUFFER_BYTES_MAX; + phys = substream->dma_buffer.addr + buf_max; else - phys = (substream->dma_buffer.addr + BUFFER_BYTES_MAX) | (pdata->sid << 32); + phys = (substream->dma_buffer.addr + buf_max) | (pdata->sid << 32); ret = q6apm_map_pos_buffer(dev, graph_id, phys, POS_BUFFER_BYTES); if (ret < 0) @@ -572,20 +691,22 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct q6apm_dai_data *pdata; struct snd_pcm *pcm = rtd->pcm; - /* - * Allocate one extra page as a workaround for a DSP bug where 32-bit - * address arithmetic can overflow when the buffer is placed near the - * end of the addressable range. - */ int size = BUFFER_BYTES_MAX + PAGE_SIZE; int graph_id, ret; bool is_push_pull; struct snd_pcm_substream *substream = NULL; + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) + return -EINVAL; + + if (pdata->has_reserved_mem) + size = pdata->reserved_buf_size + PAGE_SIZE; + graph_id = cpu_dai->driver->id; - /* Note: DSP backend dais are uni-directional ONLY(either playback or capture) */ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; else if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) @@ -603,9 +724,24 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc if (ret) return ret; + if (pdata->use_scm_assign && !pdata->has_reserved_mem) { + ret = q6apm_dai_scm_assign(pdata, + substream->dma_buffer.addr, + ALIGN(size, PAGE_SIZE)); + if (ret) { + dev_err(component->dev, + "SCM assign buffer failed: %d\n", ret); + return ret; + } + } + ret = q6apm_dai_memory_map(component, substream, graph_id, is_push_pull); - if (ret) + if (ret) { + if (pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign(pdata, + substream->dma_buffer.addr); return ret; + } } return 0; @@ -635,15 +771,26 @@ static void q6apm_dai_memory_unmap(struct snd_soc_component *component, static void q6apm_dai_pcm_free(struct snd_soc_component *component, struct snd_pcm *pcm) { + struct q6apm_dai_data *pdata; struct snd_pcm_substream *substream; + pdata = snd_soc_component_get_drvdata(component); + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; - if (substream) + if (substream) { q6apm_dai_memory_unmap(component, substream); + if (pdata && pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign(pdata, + substream->dma_buffer.addr); + } substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; - if (substream) + if (substream) { q6apm_dai_memory_unmap(component, substream); + if (pdata && pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign(pdata, + substream->dma_buffer.addr); + } } static int q6apm_dai_compr_open(struct snd_soc_component *component, @@ -683,6 +830,17 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component, if (ret) return ret; + if (pdata->use_scm_assign && !pdata->has_reserved_mem) { + ret = q6apm_dai_scm_assign(pdata, prtd->dma_buffer.addr, + ALIGN(size, PAGE_SIZE)); + if (ret) { + dev_err(dev, "SCM assign compr buffer failed: %d\n", + ret); + snd_dma_free_pages(&prtd->dma_buffer); + return ret; + } + } + if (pdata->sid < 0) prtd->phys = prtd->dma_buffer.addr; else @@ -700,11 +858,16 @@ static int q6apm_dai_compr_free(struct snd_soc_component *component, { struct snd_compr_runtime *runtime = stream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; + struct q6apm_dai_data *pdata; + + pdata = snd_soc_component_get_drvdata(component); q6apm_graph_stop(prtd->graph); q6apm_free_fragments(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK); q6apm_unmap_memory_fixed_region(component->dev, prtd->graph->id); q6apm_graph_close(prtd->graph); + if (pdata && pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign(pdata, prtd->dma_buffer.addr); snd_dma_free_pages(&prtd->dma_buffer); prtd->graph = NULL; kfree(prtd); @@ -1021,6 +1184,7 @@ static int q6apm_dai_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; + struct q6apm *apm = dev_get_drvdata(dev->parent); struct q6apm_dai_data *pdata; struct of_phandle_args args; int rc; @@ -1029,12 +1193,123 @@ static int q6apm_dai_probe(struct platform_device *pdev) if (!pdata) return -ENOMEM; + pdata->dev = dev; + mutex_init(&pdata->scm_lock); + rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); if (rc < 0) pdata->sid = -1; else pdata->sid = args.args[0] & SID_MASK_DEFAULT; + if (apm && apm->gdev && + apm->gdev->domain_id == GPR_DOMAIN_ID_MODEM) { + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + if (pdata->sid >= 0) { + dev_err(dev, + "iommus and mDSP SCM path are mutually exclusive\n"); + return -EINVAL; + } + + pdata->use_scm_assign = true; + + rc = devm_add_action_or_reset(dev, q6apm_dai_scm_cleanup, + pdata); + if (rc) + return rc; + } + + if (pdata->use_scm_assign) { + int mem_count; + + mem_count = of_count_phandle_with_args(node, "memory-region", + NULL); + if (mem_count >= 1) { + struct device_node *mem_node; + struct reserved_mem *rmem; + + mem_node = of_parse_phandle(node, "memory-region", 0); + rmem = of_reserved_mem_lookup(mem_node); + of_node_put(mem_node); + if (!rmem) { + dev_err(dev, + "memory-region[0]: lookup failed\n"); + return -ENODEV; + } + + rc = q6apm_dai_scm_assign(pdata, rmem->base, + ALIGN(rmem->size, PAGE_SIZE)); + if (rc) { + dev_err(dev, + "SCM assign memory-region[0] failed: %d\n", + rc); + return rc; + } + } + + if (mem_count >= 2) { + struct device_node *mem_node; + struct reserved_mem *rmem; + size_t per_stream; + + mem_node = of_parse_phandle(node, "memory-region", 1); + rmem = of_reserved_mem_lookup(mem_node); + of_node_put(mem_node); + if (!rmem) { + dev_err(dev, + "memory-region[1]: lookup failed\n"); + return -ENODEV; + } + + per_stream = rmem->size / Q6APM_POOL_MAX_STREAMS; + if (per_stream <= POS_BUFFER_BYTES + PAGE_SIZE) { + dev_err(dev, + "reserved-memory pool too small: %pa bytes\n", + &rmem->size); + return -EINVAL; + } + + /* + * Assign the whole pool to the consumer VMIDs once here, + * instead of per-stream in pcm_new()/compr_open(). Each + * qcom_scm_assign_mem() call consumes an entry in a small + * fixed-size TZ memory-protection table shared platform-wide; + * assigning per-stream-slice (up to Q6APM_POOL_MAX_STREAMS + * times) exhausts that table and hangs the SMC call. + */ + rc = q6apm_dai_scm_assign(pdata, rmem->base, + ALIGN(rmem->size, PAGE_SIZE)); + if (rc) { + dev_err(dev, + "SCM assign memory-region[1] failed: %d\n", + rc); + return rc; + } + + rc = of_reserved_mem_device_init_by_idx(dev, node, 1); + if (rc) { + dev_err(dev, + "reserved-memory pool init failed: %d\n", + rc); + return rc; + } + + rc = devm_add_action_or_reset(dev, + q6apm_dai_reserved_mem_release, + dev); + if (rc) + return rc; + + pdata->reserved_buf_size = + min_t(size_t, + per_stream - POS_BUFFER_BYTES - PAGE_SIZE, + BUFFER_BYTES_MAX); + pdata->has_reserved_mem = true; + } + } + dev_set_drvdata(dev, pdata); return devm_snd_soc_register_component(dev, &q6apm_fe_dai_component, NULL, 0); From e2eb4873b901290adc56b76f1d599005e620a6de Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Thu, 1 Oct 2026 10:46:57 +0800 Subject: [PATCH 549/924] ASoC: rt721-sdca: Adjust blindwrite flow to fix type detection issue Original blindwrite flow fail to support nokia type detection, but it's works normally in iphone type detection. We adjust the register settings to support both type detection. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20261001024657.739186-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca-sdw.c | 40 +++- sound/soc/codecs/rt721-sdca-sdw.h | 7 + sound/soc/codecs/rt721-sdca.c | 359 +++++++++++++++++++++++++++--- sound/soc/codecs/rt721-sdca.h | 27 +++ 4 files changed, 390 insertions(+), 43 deletions(-) diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c index 08c562c98cd7..fc34a0537d5f 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.c +++ b/sound/soc/codecs/rt721-sdca-sdw.c @@ -76,9 +76,16 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re { switch (reg) { case 0x0900004 ... 0x0900009: + case 0x0910000: + case 0x0910001: + case 0x0910002: + case 0x0910003: + case 0x0910202: case 0x0a00005: case 0x0c00005: case 0x0d00014: + case 0x0f00004: + case 0x0f00005: case 0x0310100: case 0x2000000 ... 0x2000003: case 0x2000013: @@ -87,18 +94,25 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re case 0x200003c: case 0x2000046: case 0x5810000: + case 0x5810033: case 0x5810036: case 0x5810037: case 0x5810038: case 0x5810039: + case 0x581003a: + case 0x581003b: case 0x5810100: - case 0x5b10018: - case 0x5b10019: + case 0x5b10000 ... 0x5b10026: + case 0x5b10100 ... 0x5b10103: + case 0x5f00010: + case 0x5f00043: case 0x5f00045: case 0x5f00048: case 0x6100000: + case 0x6100002: case 0x6100005: case 0x6100006: + case 0x6100007: case 0x610000d: case 0x6100010: case 0x6100011: @@ -152,6 +166,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x0a00005: case 0x0c00005: case 0x0d00014: + case 0x0f00004: case 0x2000000: case 0x200000d: case 0x2000019: @@ -168,9 +183,11 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x5810037: case 0x5810038: case 0x5810039: + case 0x581003a: + case 0x581003b: case 0x5810100: - case 0x5b10018: - case 0x5b10019: + case 0x5b10000 ... 0x5b10016: + case 0x5b10018 ... 0x5b10026: case 0x6100006: return true; default: @@ -318,6 +335,11 @@ static int rt721_sdca_interrupt_callback(struct sdw_slave *slave, int count = 0, retry = 3; unsigned int sdca_cascade, scp_sdca_stat1, scp_sdca_stat2 = 0; + if (rt721->imp_sensing_ongoing && rt721->wf_id == RT721_S) + dev_info(&slave->dev, + "%s: IRQ arrived DURING impedance sensing\n", + __func__); + if (cancel_delayed_work_sync(&rt721->jack_detect_work)) { dev_warn(&slave->dev, "%s the pending delayed_work was cancelled", __func__); /* avoid the HID owner doesn't change to device */ @@ -395,9 +417,15 @@ static int rt721_sdca_interrupt_callback(struct sdw_slave *slave, ret = sdw_read_no_pm(rt721->slave, SDW_SCP_SDCA_INT1); ret = sdw_read_no_pm(rt721->slave, SDW_SCP_SDCA_INT2); - if (status->sdca_cascade && !rt721->disable_irq) + if (status->sdca_cascade && !rt721->disable_irq) { + unsigned int delay_ms = 280; + + if (rt721->imp_sensing_ongoing && rt721->wf_id == RT721_S) + delay_ms = 800; + mod_delayed_work(system_power_efficient_wq, - &rt721->jack_detect_work, msecs_to_jiffies(280)); + &rt721->jack_detect_work, msecs_to_jiffies(delay_ms)); + } mutex_unlock(&rt721->disable_irq_lock); diff --git a/sound/soc/codecs/rt721-sdca-sdw.h b/sound/soc/codecs/rt721-sdca-sdw.h index 13d6227f3392..fe04a3726334 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.h +++ b/sound/soc/codecs/rt721-sdca-sdw.h @@ -80,18 +80,25 @@ static const struct reg_default rt721_sdca_reg_defaults[] = { }; static const struct reg_default rt721_sdca_mbq_defaults[] = { + { 0x0900006, 0xe000 }, { 0x0900007, 0xc004 }, + { 0x0910000, 0x00a1 }, + { 0x0910001, 0x1256 }, + { 0x0910002, 0x0000 }, + { 0x0910003, 0x0000 }, { 0x2000001, 0x0000 }, { 0x2000002, 0x0000 }, { 0x2000003, 0x0000 }, { 0x2000013, 0x8001 }, { 0x200003c, 0x0000 }, { 0x2000046, 0x3400 }, + { 0x5b10017, 0x1b07 }, { 0x5f00044, 0x6040 }, { 0x5f00045, 0x3333 }, { 0x5f00048, 0x0000 }, { 0x6100005, 0x0005 }, { 0x6100006, 0x0000 }, + { 0x6100007, 0x0007 }, { 0x610000d, 0x0051 }, { 0x6100010, 0x0180 }, { 0x6100011, 0x0000 }, diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index ef003c93f533..5fc0aa991914 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -27,11 +27,86 @@ #include "rt721-sdca.h" #include "rt-sdw-common.h" +static void rt721_uaj_power_on(struct rt721_sdca_priv *rt721) +{ + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL10, 0xfcb5, 0xfcb5); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON12, 0xc000, 0x4000); +} + +static void rt721_impedance_sensing(struct rt721_sdca_priv *rt721) +{ + unsigned int read_imp; + int i, loop_cnt = 25; + unsigned long cal_T; + bool spk = false; + + rt721->imp_sensing_ongoing = true; + + rt721_uaj_power_on(rt721); + + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_UAJ_CTL, 0x0048, 0x0000); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL4, 0x0003, 0x0003); + + cal_T = jiffies + msecs_to_jiffies(200); + + for (i = 0; i < loop_cnt; i++) { + regmap_read(rt721->mbq_regmap, RT721_IMP_SEN_CTRL13, &read_imp); + read_imp = (read_imp >> 12) & 0xf; + dev_dbg(&rt721->slave->dev, "%s, loop:%d, read_imp=0x%x\n", + __func__, i, read_imp); + + if (read_imp == 0x6 || read_imp == 0x7 || + read_imp == 0x8 || read_imp == 0x9) { + spk = true; + break; + } + + if (read_imp != 0 || i == loop_cnt - 1 || + time_after(jiffies, cal_T)) + break; + + usleep_range(20000, 21000); + } + + if (spk) { + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x8c00, 0x8c00); + usleep_range(50000, 51000); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x8000, 0x0000); + } else { + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x8c00, 0x8000); + usleep_range(50000, 51000); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x8000, 0x0000); + } + + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_UAJ_CTL, 0x0048, 0x0048); + rt721->int_cnt = 1; +} + +static void rt721_sdca_jack_reset_state(struct rt721_sdca_priv *rt721) +{ + rt721->jack_type = 0; + rt721->imp_sensing_done = false; + rt721->last_real_JD = 0; + rt721->int_cnt = 0; +} + static void rt721_sdca_jack_detect_handler(struct work_struct *work) { struct rt721_sdca_priv *rt721 = container_of(work, struct rt721_sdca_priv, jack_detect_work.work); + unsigned int sta_mode = 0; int btn_type = 0; + int retry, retry_loop = 40; + int real_JD; if (!rt721->hs_jack) return; @@ -39,12 +114,132 @@ static void rt721_sdca_jack_detect_handler(struct work_struct *work) if (!rt721->component->card || !rt721->component->card->instantiated) return; - /* SDW_SCP_SDCA_INT_SDCA_6 is used for jack detection */ - if (rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) { - rt721->jack_type = rt_sdca_headset_detect(rt721->regmap, - RT721_SDCA_ENT_GE49); - if (rt721->jack_type < 0) + if (rt721->wf_id == RT721_S) { + if (rt721->imp_sensing_ongoing) { + dev_dbg(&rt721->slave->dev, + "SKIP, imp_sensing_ongoing\n"); return; + } + + rt_sdca_index_read(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_STA1, &real_JD); + real_JD &= 0x1; + if (real_JD == 0x0) + rt721_sdca_jack_reset_state(rt721); + + if ((rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) && + !(rt721->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8) && + real_JD == 0x1 && rt721->int_cnt == 0 && + !rt721->imp_sensing_done && rt721->jack_type != 0) { + unsigned int cur_mode = 0; + int cur_type = -1; + + for (retry = 0; retry < retry_loop; retry++) { + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, + RT721_SDCA_ENT_GE49, + RT721_SDCA_CTL_DETECTED_MODE, 0), + &cur_mode); + if (cur_mode) + break; + usleep_range(20000, 21000); + } + + switch (cur_mode) { + case 0x03: + cur_type = SND_JACK_HEADPHONE; + break; + case 0x05: + cur_type = SND_JACK_HEADSET; + break; + default: + cur_type = -1; + break; + } + + dev_info(&rt721->slave->dev, + "%s: resume check, cur_mode=0x%x cur_type=%d retained jack_type=%d\n", + __func__, cur_mode, cur_type, rt721->jack_type); + + if (cur_mode != 0 && cur_type == rt721->jack_type) { + dev_info(&rt721->slave->dev, + "%s: resume with same jack, reuse jack_type=%d\n", + __func__, rt721->jack_type); + rt721->imp_sensing_done = true; + rt721->last_real_JD = real_JD; + snd_soc_jack_report(rt721->hs_jack, rt721->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + return; + } + + dev_info(&rt721->slave->dev, + "%s: resume jack changed/unsettled, redetect\n", __func__); + rt721->jack_type = 0; + } + + if ((rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) && + !(rt721->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8) && + real_JD == rt721->last_real_JD && real_JD == 0x1 && + rt721->int_cnt == 0 && rt721->imp_sensing_done && + rt721->jack_type != 0) { + dev_info(&rt721->slave->dev, + "%s: state settled (JD=%d), skip redetect\n", + __func__, real_JD); + rt721->last_real_JD = real_JD; + return; + } + rt721->last_real_JD = real_JD; + + if ((rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) && + !(rt721->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8) && + rt721->int_cnt == 0 && real_JD == 0x1 && + !rt721->imp_sensing_done) { + dev_info(&rt721->slave->dev, + "%s: BRANCH impedance_sensing (SDCA_0 set, fresh pass)\n", + __func__); + rt721->imp_sensing_done = true; + rt721_impedance_sensing(rt721); + rt721->imp_sensing_ongoing = false; + return; + } + + /* SDW_SCP_SDCA_INT_SDCA_0 is used for jack detection */ + if ((rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) && + (rt721->int_cnt == 1 || real_JD == 0x0)) { + if (real_JD == 0x1) { + for (retry = 0; retry < retry_loop; retry++) { + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, + RT721_SDCA_ENT_GE49, + RT721_SDCA_CTL_DETECTED_MODE, 0), + &sta_mode); + if (sta_mode) + break; + usleep_range(20000, 21000); + } + dev_dbg(&rt721->slave->dev, + "%s: detected_mode settled=0x%x, %d retries\n", + __func__, sta_mode, retry); + } + rt721->jack_type = rt_sdca_headset_detect(rt721->regmap, + RT721_SDCA_ENT_GE49); + rt721->int_cnt = 0; + if (rt721->jack_type < 0) + return; + } + dev_info(&rt721->slave->dev, + "%s: handler run stat1=0x%x stat2=0x%x (SDCA_8=%d)\n", + __func__, rt721->scp_sdca_stat1, rt721->scp_sdca_stat2, + !!(rt721->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8)); + } else { + if (rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) { + rt721->jack_type = rt_sdca_headset_detect(rt721->regmap, + RT721_SDCA_ENT_GE49); + if (rt721->jack_type < 0) + return; + } } /* SDW_SCP_SDCA_INT_SDCA_8 is used for button detection */ @@ -114,8 +309,12 @@ static void rt721_sdca_btn_check_handler(struct work_struct *work) /* Report ID for HID1 */ if (buf[0] == 0x11) btn_type = rt_sdca_btn_type(&buf[1]); - } else - rt721->jack_type = 0; + } else { + if (rt721->wf_id == RT721_S) + rt721_sdca_jack_reset_state(rt721); + else + rt721->jack_type = 0; + } dev_dbg(&rt721->slave->dev, "%s, btn_type=0x%x\n", __func__, btn_type); snd_soc_jack_report(rt721->hs_jack, rt721->jack_type | btn_type, @@ -217,11 +416,6 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) RT721_VREF1_HV_CTRL1, 0xe000); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, RT721_MISC_POWER_CTL31, 0x8007); - if (rt721->wf_id == RT721_S) { - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6420); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6421); - regmap_write(rt721->mbq_regmap, 0x5810000, 0xe421); - } rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_CH_FLOAT_CTL6, 0x5561); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_REG, @@ -253,6 +447,9 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) { + unsigned int calib_val, calib_val2, calib_tmp = 0, calib_tmp2 = 0; + unsigned int i, calib_loop = 10; + unsigned int wait_calib_loop = 10; unsigned int jack_func_status; struct device *dev = &rt721->slave->dev; @@ -268,6 +465,10 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) RT721_VREF1_HV_CTRL1, 0xe000); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, RT721_MISC_POWER_CTL31, 0x8007); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0xae05); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6048); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_GE_REL_CTRL1, 0x8011); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, @@ -279,37 +480,30 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_UMP_HID_CTRL5, 0x0c12); rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, - RT721_JD_1PIN_GAT_CTRL2, 0xc002); + RT721_JD_1PIN_GAT_CTRL2, 0xc004); rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, RT721_RC_CALIB_CTRL0, 0x0b00); rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, RT721_RC_CALIB_CTRL0, 0x0b40); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3333); - regmap_write(rt721->mbq_regmap, 0x5810035, 0x0036); - regmap_write(rt721->mbq_regmap, 0x5810030, 0xee00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL1, 0x0140); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x0021); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x8021); + RT721_UAJ_TOP_TCON14, 0x333b); rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, RT721_HP_AMP_2CH_CAL18, 0x5522); - regmap_write(rt721->mbq_regmap, 0x5b10007, 0x2000); - regmap_write(rt721->mbq_regmap, 0x5B10017, 0x1b0f); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, - RT721_CBJ_A0_GAT_CTRL1, 0x2205); rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL4, 0xa105); + RT721_HP_AMP_2CH_CAL1, 0x4540); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL4, 0xa107); + regmap_write(rt721->mbq_regmap, 0x5B10017, 0x180f); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL27, 0x4400); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3b33); + RT721_UAJ_TOP_TCON14, 0x3b3b); regmap_write(rt721->mbq_regmap, 0x310400, 0x3043); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3f33); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON13, 0x6048); + RT721_UAJ_TOP_TCON14, 0x3f3b); regmap_write(rt721->mbq_regmap, 0x310401, 0x3000); regmap_write(rt721->mbq_regmap, 0x310402, 0x1b00); - regmap_write(rt721->mbq_regmap, 0x310300, 0x000f); + regmap_write(rt721->mbq_regmap, 0x310300, 0x000c); regmap_write(rt721->mbq_regmap, 0x310301, 0x3000); regmap_write(rt721->mbq_regmap, 0x310302, 0x1b00); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, @@ -322,6 +516,12 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) RT721_MBIAS_LV_CTRL2, 0x6677); rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, RT721_VREF2_LV_CTRL1, 0x7600); + regmap_write(rt721->mbq_regmap, 0x0910000, 0x00a1); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6048); + rt_sdca_index_write(rt721->mbq_regmap, RT721_BOOST_CTRL, + RT721_BST_4CH_TOP_GATING_CTRL1, 0x002a); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_ENT_FLOAT_CTL2, 0x1234); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, @@ -330,16 +530,99 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) RT721_ENT_FLOAT_CTL1, 0x4040); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_ENT_FLOAT_CTL4, 0x1201); - rt_sdca_index_write(rt721->mbq_regmap, RT721_BOOST_CTRL, - RT721_BST_4CH_TOP_GATING_CTRL1, 0x002a); - regmap_write(rt721->regmap, 0x2f58, 0x07); - regmap_write(rt721->regmap, 0x2f51, 0x00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_MISC_CTL, 0x0004); - /* clear flag */ + regmap_write(rt721->regmap, 0x2f58, 0x01); + regmap_write(rt721->regmap, 0x2f59, 0x01); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6040); + regmap_write(rt721->mbq_regmap, 0x0910001, 0x1256); + regmap_write(rt721->mbq_regmap, 0x0910002, 0x0000); + regmap_write(rt721->mbq_regmap, 0x0910003, 0x0000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_TOP_CTRL3, 0x7788); + regmap_update_bits(rt721->mbq_regmap, RT721_ADC_BIAS_CTRL3, + 0x0004, 0x0004); + rt_sdca_index_write(rt721->mbq_regmap, RT721_MIC_BIAS_CTRL, + RT721_MIC_BIAS_TOP_CTRL2, 0xcc04); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CHARGE_PUMP_CTRL, + RT721_CHGPMP_GAT_CTRL1, 0x0000); + for (i = 0; i < wait_calib_loop; i++) { + regmap_read(rt721->mbq_regmap, 0x0581003a, &calib_val); + regmap_read(rt721->mbq_regmap, 0x0581003b, &calib_val2); + calib_val &= 0x1f; + calib_val2 &= 0xffff; + if (calib_val == 0 || calib_val2 == 0 || calib_val != calib_tmp || + calib_val2 != calib_tmp2) { + dev_dbg(&rt721->slave->dev, "%s: Keep calibration\n", __func__); + usleep_range(10000, 11000); + } else { + dev_dbg(&rt721->slave->dev, "%s: Calibration done\n", __func__); + break; + } + calib_tmp = calib_val; + calib_tmp2 = calib_val2; + } + + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL2, 0xd000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON_ANY, 0xc820); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL1, 0x0540); + regmap_write(rt721->mbq_regmap, 0x5810030, 0xee00); + regmap_write(rt721->mbq_regmap, 0x5810033, 0x6000); + regmap_write(rt721->mbq_regmap, 0x5810035, 0x0036); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x0020); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x0021); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x8021); + for (i = 0; i < calib_loop; i++) { + regmap_read(rt721->mbq_regmap, 0x0581003a, &calib_val); + regmap_read(rt721->mbq_regmap, 0x0581003b, &calib_val2); + calib_val &= 0x1f; + calib_val2 &= 0xffff; + if (calib_val == 0 || calib_val2 == 0 || calib_val != calib_tmp || + calib_val2 != calib_tmp2) { + dev_dbg(&rt721->slave->dev, "%s: Keep calibration\n", __func__); + usleep_range(10000, 11000); + } else { + dev_dbg(&rt721->slave->dev, "%s: Calibration done\n", __func__); + break; + } + calib_tmp = calib_val; + calib_tmp2 = calib_val2; + } + + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL1, 0x4540); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x2e05); + regmap_write(rt721->regmap, 0x40400188, 0x0); + regmap_write(rt721->regmap, 0x40400688, 0x0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(6), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(5), 0); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_MISC_CTL, BIT(1), 0); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_MISC_CTL, BIT(0), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(4), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(3), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(2), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(1), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(0), 0); + + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_UAJ_CTL, 0x1c00, 0x0000); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_CTRL1, 0xc000, 0xc000); + regmap_update_bits(rt721->mbq_regmap, RT721_IMP_SEN_CTRL11, + 0x001c, 0x0004); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_CTRL2, 0x0007, 0x0004); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x000f, 0x0003); regmap_write(rt721->regmap, SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), FUNCTION_NEEDS_INITIALIZATION); + msleep(500); } } @@ -1704,6 +1987,8 @@ int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) int val; rt721->disable_irq = false; + rt721->imp_sensing_done = false; + rt721->last_real_JD = -1; if (rt721->hw_init) return 0; diff --git a/sound/soc/codecs/rt721-sdca.h b/sound/soc/codecs/rt721-sdca.h index 16cf40cd88de..cc3bce0ba517 100644 --- a/sound/soc/codecs/rt721-sdca.h +++ b/sound/soc/codecs/rt721-sdca.h @@ -29,6 +29,10 @@ struct rt721_sdca_priv { /* For Headset jack & Headphone */ unsigned int scp_sdca_stat1; unsigned int scp_sdca_stat2; + unsigned int int_cnt; + bool imp_sensing_ongoing; + bool imp_sensing_done; + int last_real_JD; struct snd_soc_jack *hs_jack; struct delayed_work jack_detect_work; struct delayed_work jack_btn_check_work; @@ -54,8 +58,10 @@ struct rt721_sdca_dmic_kctrl_priv { /* NID */ #define RT721_ANA_POW_PART 0x01 #define RT721_DAC_CTRL 0x04 +#define RT721_MIC_BIAS_CTRL 0x08 #define RT721_JD_CTRL 0x09 #define RT721_CBJ_CTRL 0x0a +#define RT721_CHARGE_PUMP_CTRL 0x0b #define RT721_CAP_PORT_CTRL 0x0c #define RT721_CLASD_AMP_CTRL 0x0d #define RT721_BOOST_CTRL 0x0f @@ -81,17 +87,28 @@ struct rt721_sdca_dmic_kctrl_priv { #define RT721_DAC_2CH_CTRL3 0x02 #define RT721_DAC_2CH_CTRL4 0x03 +/* Index (NID:08h) */ +#define RT721_MIC_BIAS_TOP_CTRL2 0x02 + /* Index (NID:09h) */ +#define RT721_JD_1PIN_TOP_CTRL3 0x04 +#define RT721_JD_1PIN_GAT_CTRL1 0x06 #define RT721_JD_1PIN_GAT_CTRL2 0x07 +#define RT721_JD_1PIN_GAT_STA1 0x09 /* Index (NID:0ah) */ #define RT721_CBJ_A0_GAT_CTRL1 0x04 #define RT721_CBJ_A0_GAT_CTRL2 0x05 +/* Index (NID:0bh) */ +#define RT721_CHGPMP_GAT_CTRL1 0x01 + /* Index (NID:0Ch) */ #define RT721_HP_AMP_2CH_CAL1 0x05 +#define RT721_HP_AMP_2CH_CAL2 0x06 #define RT721_HP_AMP_2CH_CAL4 0x08 #define RT721_HP_AMP_2CH_CAL18 0x1b +#define RT721_HP_AMP_2CH_CAL27 0x24 /* Index (NID:0dh) */ #define RT721_CLASD_AMP_2CH_CAL 0x14 @@ -129,10 +146,13 @@ struct rt721_sdca_dmic_kctrl_priv { /* Index (NID:5fh) */ #define RT721_MISC_POWER_CTL0 0x00 +#define RT721_MISC_POWER_CTL10 0x10 #define RT721_MISC_POWER_CTL31 0x31 +#define RT721_UAJ_TOP_TCON12 0x43 #define RT721_UAJ_TOP_TCON13 0x44 #define RT721_UAJ_TOP_TCON14 0x45 #define RT721_UAJ_TOP_TCON17 0x48 +#define RT721_UAJ_TOP_TCON_ANY 0x70 /* Index (NID:61h) */ #define RT721_HDA_LEGACY_MUX_CTL0 0x00 @@ -181,6 +201,13 @@ struct rt721_sdca_dmic_kctrl_priv { #define RT721_BUF_ADDR_HID1 0x44030000 #define RT721_BUF_ADDR_HID2 0x44030020 +/* Impedance Sensing Control */ +#define RT721_IMP_SEN_CTRL11 0x5b10017 +#define RT721_IMP_SEN_CTRL13 0x5b1001b + +/* ADC Bias Control 3 */ +#define RT721_ADC_BIAS_CTRL3 0x0910202 + /* RT721 SDCA Control - function number */ #define FUNC_NUM_JACK_CODEC 0x01 #define FUNC_NUM_MIC_ARRAY 0x02 From ccfe6ab672c020fca56988e634088bfb7764e2a7 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 6 Oct 2026 15:44:04 +0200 Subject: [PATCH 550/924] ALSA: caiaq: Allow zero-length packet handling for EP1 The recent fix to avoid OOB access for malformed packets added a packet length check and returns immediately if it's too short. However, this can be lead to a regression because the completion handler doesn't resubmit the packet, while a zero-length packet itself is valid, per se. For addressing the potential regression above, change the handling for a zero-length packet to just resubmit without processing the incoming contents. Fixes: aba30af07d4f ("ALSA: usb-audio: caiaq: validate EP1 reply lengths") Link: https://patch.msgid.link/20261006134405.479603-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/caiaq/device.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index dea3a68ca896..a596f5f763b8 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -143,7 +143,7 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) return; } if (urb->actual_length < 1) - return; + goto resubmit; payload_len = urb->actual_length - 1; @@ -193,6 +193,7 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) break; } + resubmit: cdev->ep1_in_urb->actual_length = 0; ret = usb_submit_urb(cdev->ep1_in_urb, GFP_ATOMIC); if (ret < 0) From eb6237f64909d54540267581967293f2d423bb4c Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Tue, 6 Oct 2026 16:13:36 -0700 Subject: [PATCH 551/924] ALSA: hda: Discard pm_runtime_put_autosuspend() return value snd_hdac_power_down() and snd_hdac_power_down_pm() are defined to return an int, but their return values are never used by any caller. snd_hdac_power_down() also passes the return value of pm_runtime_put_autosuspend() to the caller, which is not very useful. Returning an error code from pm_runtime_put_autosuspend() merely means that it has not queued up a timer or work item to check whether or not the device can be suspended and there are many perfectly valid situations in which that can happen, like after writing "on" to the device's runtime PM "control" attribute in sysfs for one example. Modify snd_hdac_power_down() to discard the pm_runtime_put_autosuspend() return value and change the return types of snd_hdac_power_down() and snd_hdac_power_down_pm() to void. No intentional functional impact. This will facilitate a planned change of the pm_runtime_put_autosuspend() return type to void in the future, similar to commit 3afd8df02433 ("PM: runtime: Change pm_runtime_put() return type to void"). Signed-off-by: Brian Norris Link: https://patch.msgid.link/20261006161337.8.Idcada3ae757100bf3a38f7e71bac37f827be06c6@changeid Signed-off-by: Takashi Iwai --- include/sound/hdaudio.h | 8 ++++---- sound/hda/core/device.c | 13 ++++--------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index aa994d6e6d35..4cbbb1744740 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -179,9 +179,9 @@ static inline int snd_hdac_read_parm(struct hdac_device *codec, hda_nid_t nid, #ifdef CONFIG_PM int snd_hdac_power_up(struct hdac_device *codec); -int snd_hdac_power_down(struct hdac_device *codec); +void snd_hdac_power_down(struct hdac_device *codec); int snd_hdac_power_up_pm(struct hdac_device *codec); -int snd_hdac_power_down_pm(struct hdac_device *codec); +void snd_hdac_power_down_pm(struct hdac_device *codec); int snd_hdac_keep_power_up(struct hdac_device *codec); /* call this at entering into suspend/resume callbacks in codec driver */ @@ -207,9 +207,9 @@ static inline bool snd_hdac_is_power_on(struct hdac_device *codec) } #else static inline int snd_hdac_power_up(struct hdac_device *codec) { return 0; } -static inline int snd_hdac_power_down(struct hdac_device *codec) { return 0; } +static inline void snd_hdac_power_down(struct hdac_device *codec) {} static inline int snd_hdac_power_up_pm(struct hdac_device *codec) { return 0; } -static inline int snd_hdac_power_down_pm(struct hdac_device *codec) { return 0; } +static inline void snd_hdac_power_down_pm(struct hdac_device *codec) {} static inline int snd_hdac_keep_power_up(struct hdac_device *codec) { return 0; } static inline void snd_hdac_enter_pm(struct hdac_device *codec) {} static inline void snd_hdac_leave_pm(struct hdac_device *codec) {} diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c index a45f61e12c1a..6187ffaedbfa 100644 --- a/sound/hda/core/device.c +++ b/sound/hda/core/device.c @@ -572,14 +572,12 @@ EXPORT_SYMBOL_GPL(snd_hdac_power_up); /** * snd_hdac_power_down - power down the codec * @codec: the codec object - * - * Returns zero if successful, or a negative error code. */ -int snd_hdac_power_down(struct hdac_device *codec) +void snd_hdac_power_down(struct hdac_device *codec) { struct device *dev = &codec->dev; - return pm_runtime_put_autosuspend(dev); + pm_runtime_put_autosuspend(dev); } EXPORT_SYMBOL_GPL(snd_hdac_power_down); @@ -624,14 +622,11 @@ int snd_hdac_keep_power_up(struct hdac_device *codec) * * Like snd_hdac_power_up_pm(), this function is used in a recursive * code path like init code which may be called by PM suspend/resume again. - * - * Returns zero if successful, or a negative error code. */ -int snd_hdac_power_down_pm(struct hdac_device *codec) +void snd_hdac_power_down_pm(struct hdac_device *codec) { if (atomic_dec_if_positive(&codec->in_pm) < 0) - return snd_hdac_power_down(codec); - return 0; + snd_hdac_power_down(codec); } EXPORT_SYMBOL_GPL(snd_hdac_power_down_pm); #endif From 5b81127873aebf55cf55b6bd6462b1056f9b97e1 Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Sat, 3 Oct 2026 13:56:53 -0400 Subject: [PATCH 552/924] ASoC: dt-bindings: msm8916-digital-codec: Add SDM670 compatible The MSM8916 digital codec is also found on SDM670, typically connected to the sound card. Add the compatible for SDM670. Signed-off-by: Richard Acayan Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20261003175659.23536-6-mailingradian@gmail.com Signed-off-by: Mark Brown --- .../bindings/sound/qcom,msm8916-wcd-digital-codec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/sound/qcom,msm8916-wcd-digital-codec.yaml b/Documentation/devicetree/bindings/sound/qcom,msm8916-wcd-digital-codec.yaml index 33bc23b6176a..b603bb6eb9b7 100644 --- a/Documentation/devicetree/bindings/sound/qcom,msm8916-wcd-digital-codec.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,msm8916-wcd-digital-codec.yaml @@ -18,6 +18,7 @@ properties: - items: - enum: - qcom,sdm660-wcd-digital-codec + - qcom,sdm670-wcd-digital-codec - const: qcom,msm8916-wcd-digital-codec - const: qcom,msm8916-wcd-digital-codec From 7e8640dd88dcf174b1f223fe20e418d025450ca1 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 2 Oct 2026 09:38:59 +0200 Subject: [PATCH 553/924] dt-bindings: ASoC: nvidia,tegra30-ahub: Convert to json-schema Convert Tegra AHUB bindings from the free-form text format to json-schema. Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20261002-as3722-bindings-v3-2-21c179b72a95@nvidia.com Signed-off-by: Mark Brown --- .../bindings/sound/nvidia,tegra30-ahub.txt | 88 ------ .../bindings/sound/nvidia,tegra30-ahub.yaml | 289 ++++++++++++++++++ 2 files changed, 289 insertions(+), 88 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.txt create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.yaml diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.txt b/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.txt deleted file mode 100644 index 0e9a1895d7fb..000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.txt +++ /dev/null @@ -1,88 +0,0 @@ -NVIDIA Tegra30 AHUB (Audio Hub) - -Required properties: -- compatible : For Tegra30, must contain "nvidia,tegra30-ahub". For Tegra114, - must contain "nvidia,tegra114-ahub". For Tegra124, must contain - "nvidia,tegra124-ahub". Otherwise, must contain "nvidia,-ahub", - plus at least one of the above, where is tegra132. -- reg : Should contain the register physical address and length for each of - the AHUB's register blocks. - - Tegra30 requires 2 entries, for the APBIF and AHUB/AUDIO register blocks. - - Tegra114 requires an additional entry, for the APBIF2 register block. -- interrupts : Should contain AHUB interrupt -- clocks : Must contain an entry for each entry in clock-names. - See ../clocks/clock-bindings.txt for details. -- clock-names : Must include the following entries: - - d_audio - - apbif -- resets : Must contain an entry for each entry in reset-names. - See ../reset/reset.txt for details. -- reset-names : Must include the following entries: - Tegra30 and later: - - d_audio - - apbif - - i2s0 - - i2s1 - - i2s2 - - i2s3 - - i2s4 - - dam0 - - dam1 - - dam2 - - spdif - Tegra114 and later additionally require: - - amx - - adx - Tegra124 and later additionally require: - - amx1 - - adx1 - - afc0 - - afc1 - - afc2 - - afc3 - - afc4 - - afc5 -- ranges : The bus address mapping for the configlink register bus. - Can be empty since the mapping is 1:1. -- dmas : Must contain an entry for each entry in clock-names. - See ../dma/dma.txt for details. -- dma-names : Must include the following entries: - - rx0 .. rx - - tx0 .. tx - ... where n is: - Tegra30: 3 - Tegra114, Tegra124: 9 -- #address-cells : For the configlink bus. Should be <1>; -- #size-cells : For the configlink bus. Should be <1>. - -AHUB client modules need to specify the IDs of their CIFs (Client InterFaces). -For RX CIFs, the numbers indicate the register number within AHUB routing -register space (APBIF 0..3 RX, I2S 0..5 RX, DAM 0..2 RX 0..1, SPDIF RX 0..1). -For TX CIFs, the numbers indicate the bit position within the AHUB routing -registers (APBIF 0..3 TX, I2S 0..5 TX, DAM 0..2 TX, SPDIF TX 0..1). - -Example: - -ahub@70080000 { - compatible = "nvidia,tegra30-ahub"; - reg = <0x70080000 0x200 0x70080200 0x100>; - interrupts = < 0 103 0x04 >; - nvidia,dma-request-selector = <&apbdma 1>; - clocks = <&tegra_car 106>, <&tegra_car 107>; - clock-names = "d_audio", "apbif"; - resets = <&tegra_car 106>, <&tegra_car 107>, <&tegra_car 30>, - <&tegra_car 11>, <&tegra_car 18>, <&tegra_car 101>, - <&tegra_car 102>, <&tegra_car 108>, <&tegra_car 109>, - <&tegra_car 110>, <&tegra_car 10>; - reset-names = "d_audio", "apbif", "i2s0", "i2s1", "i2s2", - "i2s3", "i2s4", "dam0", "dam1", "dam2", - "spdif"; - dmas = <&apbdma 1>, <&apbdma 1>; - <&apbdma 2>, <&apbdma 2>; - <&apbdma 3>, <&apbdma 3>; - <&apbdma 4>, <&apbdma 4>; - dma-names = "rx0", "tx0", "rx1", "tx1", "rx2", "tx2", "rx3", "tx3"; - ranges; - #address-cells = <1>; - #size-cells = <1>; -}; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.yaml new file mode 100644 index 000000000000..26077c3ce198 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.yaml @@ -0,0 +1,289 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra30-ahub.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra30 AHUB (Audio Hub) + +maintainers: + - Thierry Reding + - Jon Hunter + +description: + AHUB client modules need to specify the IDs of their CIFs (Client + InterFaces). For RX CIFs, the numbers indicate the register number within + AHUB routing register space (APBIF 0..3 RX, I2S 0..5 RX, DAM 0..2 RX 0..1, + SPDIF RX 0..1). For TX CIFs, the numbers indicate the bit position within + the AHUB routing registers (APBIF 0..3 TX, I2S 0..5 TX, DAM 0..2 TX, SPDIF + TX 0..1). + +properties: + compatible: + oneOf: + - const: nvidia,tegra30-ahub + - const: nvidia,tegra114-ahub + - const: nvidia,tegra124-ahub + - items: + - const: nvidia,tegra132-ahub + - const: nvidia,tegra124-ahub + + reg: + minItems: 2 + maxItems: 3 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: digital audio clock + - description: module clock + + clock-names: + items: + - const: d_audio + - const: apbif + + resets: + minItems: 11 + maxItems: 21 + + reset-names: + minItems: 11 + maxItems: 21 + + ranges: true + + dmas: + minItems: 8 + maxItems: 20 + + dma-names: + minItems: 8 + maxItems: 20 + + "#address-cells": + enum: [1, 2] + + "#size-cells": + enum: [1, 2] + +patternProperties: + "^i2s@[0-9a-f]+$": + type: object + required: + - compatible + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + - reset-names + - ranges + - dmas + - dma-names + - "#address-cells" + - "#size-cells" + +allOf: + - if: + properties: + compatible: + const: nvidia,tegra30-ahub + then: + properties: + reg: + items: + - description: APBIF registers + - description: AHUB/AUDIO registers + + resets: + maxItems: 11 + + reset-names: + items: + - const: d_audio + - const: apbif + - const: i2s0 + - const: i2s1 + - const: i2s2 + - const: i2s3 + - const: i2s4 + - const: dam0 + - const: dam1 + - const: dam2 + - const: spdif + + dmas: + maxItems: 8 + + dma-names: + items: + - const: rx0 + - const: tx0 + - const: rx1 + - const: tx1 + - const: rx2 + - const: tx2 + - const: rx3 + - const: tx3 + + - if: + properties: + compatible: + const: nvidia,tegra114-ahub + then: + properties: + reg: + items: + - description: APBIF registers + - description: AHUB/AUDIO registers + - description: APBIF2 registers + + resets: + minItems: 13 + maxItems: 13 + + reset-names: + items: + - const: d_audio + - const: apbif + - const: i2s0 + - const: i2s1 + - const: i2s2 + - const: i2s3 + - const: i2s4 + - const: dam0 + - const: dam1 + - const: dam2 + - const: spdif + - const: amx + - const: adx + + dmas: + minItems: 20 + + dma-names: + items: + - const: rx0 + - const: tx0 + - const: rx1 + - const: tx1 + - const: rx2 + - const: tx2 + - const: rx3 + - const: tx3 + - const: rx4 + - const: tx4 + - const: rx5 + - const: tx5 + - const: rx6 + - const: tx6 + - const: rx7 + - const: tx7 + - const: rx8 + - const: tx8 + - const: rx9 + - const: tx9 + + - if: + properties: + compatible: + contains: + const: nvidia,tegra124-ahub + then: + properties: + reg: + items: + - description: APBIF registers + - description: AHUB/AUDIO registers + - description: APBIF2 registers + + resets: + minItems: 21 + + reset-names: + items: + - const: d_audio + - const: apbif + - const: i2s0 + - const: i2s1 + - const: i2s2 + - const: i2s3 + - const: i2s4 + - const: dam0 + - const: dam1 + - const: dam2 + - const: spdif + - const: amx + - const: amx1 + - const: adx + - const: adx1 + - const: afc0 + - const: afc1 + - const: afc2 + - const: afc3 + - const: afc4 + - const: afc5 + + dmas: + minItems: 20 + + dma-names: + items: + - const: rx0 + - const: tx0 + - const: rx1 + - const: tx1 + - const: rx2 + - const: tx2 + - const: rx3 + - const: tx3 + - const: rx4 + - const: tx4 + - const: rx5 + - const: tx5 + - const: rx6 + - const: tx6 + - const: rx7 + - const: tx7 + - const: rx8 + - const: tx8 + - const: rx9 + - const: tx9 + +additionalProperties: false + +examples: + - | + #include + #include + + ahub@70080000 { + compatible = "nvidia,tegra30-ahub"; + reg = <0x70080000 0x200>, + <0x70080200 0x100>; + interrupts = ; + clocks = <&tegra_car TEGRA30_CLK_D_AUDIO>, + <&tegra_car TEGRA30_CLK_APBIF>; + clock-names = "d_audio", "apbif"; + resets = <&tegra_car 106>, <&tegra_car 107>, <&tegra_car 30>, + <&tegra_car 11>, <&tegra_car 18>, <&tegra_car 101>, + <&tegra_car 102>, <&tegra_car 108>, <&tegra_car 109>, + <&tegra_car 110>, <&tegra_car 10>; + reset-names = "d_audio", "apbif", "i2s0", "i2s1", "i2s2", + "i2s3", "i2s4", "dam0", "dam1", "dam2", + "spdif"; + dmas = <&apbdma 1>, <&apbdma 1>, + <&apbdma 2>, <&apbdma 2>, + <&apbdma 3>, <&apbdma 3>, + <&apbdma 4>, <&apbdma 4>; + dma-names = "rx0", "tx0", "rx1", "tx1", "rx2", "tx2", "rx3", "tx3"; + ranges; + #address-cells = <1>; + #size-cells = <1>; + }; From cd4c07a11ef060192f9a2957a5c650e5a2a65f07 Mon Sep 17 00:00:00 2001 From: Ricardo Rivera-Matos Date: Thu, 1 Oct 2026 10:14:27 -0500 Subject: [PATCH 554/924] ASoC: dt-bindings: cs35l45: Adds LDPM settings Introduce devicetree properties for configuring level dependent power management (LDPM) functionality. Signed-off-by: Ricardo Rivera-Matos Link: https://patch.msgid.link/20261001-cs35l45-ldpm-v1-1-545787a5afe5@opensource.cirrus.com Signed-off-by: Mark Brown --- .../bindings/sound/cirrus,cs35l45.yaml | 86 +++++++++++++++++++ include/dt-bindings/sound/cs35l45.h | 27 ++++++ 2 files changed, 113 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml index 1ba809888a88..e674811b22b2 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml @@ -71,6 +71,58 @@ properties: maximum: 7 default: 0 + cirrus,ldpm-group1-boost-select: + type: boolean + description: + When in a valid group 1 level dependent power management (LDPM) state + the integrated boost converter will enter the configured low power mode. + + cirrus,boost-low-power-mode: + description: + Configures the integrated boost converter reaction to a low power mode + request. + 0 = No reaction + 1 = Force boost DCM low power mode + 2 = Force boost static bypass + $ref: /schemas/types.yaml#/definitions/uint32 + default: 2 + enum: + - 0 + - 1 + - 2 + + cirrus,ldpm-group1-amp-select: + type: boolean + description: + When in a valid group 1 level dependent power management (LDPM) state + the amplifier will enter the configured low power mode. + + cirrus,amplifier-low-power-mode: + description: + Configures the amplifier output stage reaction to a low power mode + request. + 0 = Stop output switching (noise gate mode) + 1 = Noise floor reduction mode + $ref: /schemas/types.yaml#/definitions/uint32 + default: 1 + enum: + - 0 + - 1 + + cirrus,ldpm-group2-vmon-select: + type: boolean + description: + When in a valid group 2 level dependent power management (LDPM) state + the voltage monitor (VMON) circuit is put into a low power operational + state. + + cirrus,ldpm-group2-imon-select: + type: boolean + description: + When in a valid group 2 level dependent power management (LDPM) state + the current monitor (IMON) circuit is put into a low power operational + state. + patternProperties: "^cirrus,gpio-ctrl[1-3]$": description: @@ -139,6 +191,30 @@ patternProperties: minimum: 0 maximum: 7 default: 0 + + "^cirrus,ldpm-group[1-2]-pcm-threshold-db$": + description: + Amplifier level dependent power management (LDPM) threshold. Input + referred threshold of the PCM audio signal in which the level is low + enough for the LDPM group to engage after entry delay. A value of dB + -2147483648 is assumed to mean true zero data with an LSB dither. + $ref: /schemas/types.yaml#/definitions/int32-array + minItems: 1 + maxItems: 1 + items: + enum: [-2147483648, -102, -96, -90, -84, -78, -72, -66] + + "^cirrus,ldpm-group[1-2]-delay-ms$": + description: + Amplifier level dependent power management (LDPM) state entry delay. + Time that the incoming audio signal must be below the PCM threshold + before the LDPM group engages. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 1 + items: + enum: [5, 10, 25, 50, 100, 250, 500, 1000] + required: - compatible - reg @@ -165,6 +241,16 @@ examples: CS35L45_ASP_TX_HIZ_DISABLED)>; cirrus,sync-lsw-txid = <0x1>; cirrus,sync-sw-txid = <0x1>; + cirrus,ldpm-group1-pcm-threshold-db = <(-66)>; + cirrus,ldpm-group1-delay-ms = <500>; + cirrus,ldpm-group1-boost-select; + cirrus,boost-low-power-mode = ; + cirrus,ldpm-group1-amp-select; + cirrus,amplifier-low-power-mode = ; + cirrus,ldpm-group2-pcm-threshold-db = <(-102)>; + cirrus,ldpm-group2-delay-ms = <1000>; + cirrus,ldpm-group2-vmon-select; + cirrus,ldpm-group2-imon-select; cirrus,gpio-ctrl1 { gpio-ctrl = <0x2>; }; diff --git a/include/dt-bindings/sound/cs35l45.h b/include/dt-bindings/sound/cs35l45.h index 25386af18445..5cb77d48b0b8 100644 --- a/include/dt-bindings/sound/cs35l45.h +++ b/include/dt-bindings/sound/cs35l45.h @@ -17,6 +17,33 @@ #define CS35L45_ASP_TX_HIZ_UNUSED 0x1 #define CS35L45_ASP_TX_HIZ_DISABLED 0x2 +/* + * cirrus,boost-low-power-mode + * + * BST_LPMODE_NO_REACTION: No reaction + * BST_LPMODE_DCM_MODE: Force boost DCM low power mode + * BST_LPMODE_STATIC_BYPASS: Force boost static bypass + */ +#define CS35L45_BST_LPMODE_NO_REACTION 0x0 +#define CS35L45_BST_LPMODE_DCM_MODE 0x1 +#define CS35L45_BST_LPMODE_STATIC_BYPASS 0x2 + +/* + * cirrus,amplifier-low-power-mode + * + * AMP_LPMODE_NG_MODE: Noise gate mode + * AMP_LPMODE_NFR_MODE: Noise floor reduction mode + */ + #define CS35L45_AMP_LPMODE_NG_MODE 0x0 + #define CS35L45_AMP_LPMODE_NFR_MODE 0x1 + +/* + * cirrus,ldpm-groupX-pcm-threshold-db + * + * LDPM_PCM_THLD_TRUE_ZERO: + */ +#define CS35L45_LDPM_PCM_THLD_TRUE_ZERO -2147483648 + /* * Optional GPIOX Sub-nodes: * The cs35l45 node can have up to three "cirrus,gpio-ctrlX" ('X' = [1,2,3]) From 0ad64fbbb7ed98f2fef4f3994c2e86a9c0fde4bd Mon Sep 17 00:00:00 2001 From: Ricardo Rivera-Matos Date: Thu, 1 Oct 2026 10:14:28 -0500 Subject: [PATCH 555/924] ASoC: cs35l45: Adds support for LDPM feature Adds support for level dependent power management feature. Signed-off-by: Ricardo Rivera-Matos Link: https://patch.msgid.link/20261001-cs35l45-ldpm-v1-2-545787a5afe5@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l45-tables.c | 58 ++++++++++++++++++++++ sound/soc/codecs/cs35l45.c | 80 +++++++++++++++++++++++++++++++ sound/soc/codecs/cs35l45.h | 30 ++++++++++++ 3 files changed, 168 insertions(+) diff --git a/sound/soc/codecs/cs35l45-tables.c b/sound/soc/codecs/cs35l45-tables.c index 57982b8ae645..554c8121a74f 100644 --- a/sound/soc/codecs/cs35l45-tables.c +++ b/sound/soc/codecs/cs35l45-tables.c @@ -53,6 +53,7 @@ static const struct reg_default cs35l45_defaults[] = { { CS35L45_GLOBAL_SAMPLE_RATE, 0x00000003 }, { CS35L45_SYNC_TX_RX_ENABLES, 0x00000200 }, { CS35L45_SYNC_SW_TX_ID, 0x00000000 }, + { CS35L45_BOOST_LPMODE_CFG, 0x00000002 }, { CS35L45_ASP_ENABLES1, 0x00000000 }, { CS35L45_ASP_CONTROL1, 0x00000028 }, { CS35L45_ASP_CONTROL2, 0x18180200 }, @@ -76,6 +77,7 @@ static const struct reg_default cs35l45_defaults[] = { { CS35L45_DSP1RX6_INPUT, 0x00000028 }, { CS35L45_DSP1RX7_INPUT, 0x0000003A }, { CS35L45_DSP1RX8_INPUT, 0x00000028 }, + { CS35L45_LDPM_CONFIG, 0x00003636 }, { CS35L45_AMP_PCM_CONTROL, 0x00100000 }, { CS35L45_AMP_GAIN, 0x00002300 }, { CS35L45_IRQ1_CFG, 0x00000000 }, @@ -138,6 +140,7 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_GLOBAL_SAMPLE_RATE: case CS35L45_SYNC_TX_RX_ENABLES: case CS35L45_SYNC_SW_TX_ID: + case CS35L45_BOOST_LPMODE_CFG: case CS35L45_ASP_ENABLES1: case CS35L45_ASP_CONTROL1: case CS35L45_ASP_CONTROL2: @@ -162,6 +165,7 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_DSP1RX7_INPUT: case CS35L45_DSP1RX8_INPUT: case CS35L45_HVLV_CONFIG: + case CS35L45_LDPM_CONFIG: case CS35L45_AMP_PCM_CONTROL: case CS35L45_AMP_GAIN: case CS35L45_AMP_PCM_HPF_TST: @@ -334,3 +338,57 @@ int cs35l45_get_clk_freq_id(unsigned int freq) return -EINVAL; } EXPORT_SYMBOL_NS_GPL(cs35l45_get_clk_freq_id, "SND_SOC_CS35L45"); + +static const struct { + u8 cfg_id; + s32 pcm_thld_db; +} cs35l45_ldpm_pcm_thld[] = { + { 0x0, -66 }, + { 0x1, -72 }, + { 0x2, -78 }, + { 0x3, -84 }, + { 0x4, -90 }, + { 0x5, -96 }, + { 0x6, -102 }, + { 0x7, -2147483648 }, +}; + +int cs35l45_get_ldpm_pcm_thld(s32 pcm_thld_db) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l45_ldpm_pcm_thld); i++) { + if (cs35l45_ldpm_pcm_thld[i].pcm_thld_db == pcm_thld_db) + return cs35l45_ldpm_pcm_thld[i].cfg_id; + } + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(cs35l45_get_ldpm_pcm_thld, "SND_SOC_CS35L45"); + +static const struct { + u8 cfg_id; + u32 delay_ms; +} cs35l45_ldpm_delay[] = { + { 0x0, 5 }, + { 0x1, 10 }, + { 0x2, 25 }, + { 0x3, 50 }, + { 0x4, 100 }, + { 0x5, 250 }, + { 0x6, 500 }, + { 0x7, 1000 }, +}; + +int cs35l45_get_ldpm_delay(u32 delay_ms) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l45_ldpm_delay); i++) { + if (cs35l45_ldpm_delay[i].delay_ms == delay_ms) + return cs35l45_ldpm_delay[i].cfg_id; + } + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(cs35l45_get_ldpm_delay, "SND_SOC_CS35L45"); diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index 9eef7c8b1837..064ee612f05e 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -1148,6 +1148,82 @@ static void cs35l45_apply_sync_property_config(struct cs35l45_private *cs35l45) CS35L45_SYNC_SW_TXID_MASK, val); } +static int cs35l45_apply_ldpm_config(struct cs35l45_private *cs35l45) +{ + char prop_name[CS35L45_LDPM_PROP_NAME_MAX]; + int i, ret; + u32 val; + + for (i = LDPM_GROUP1; i < NUM_LDPM_GROUP; i++) { + sprintf(prop_name, "cirrus,ldpm-group%d-pcm-threshold-db", i); + if (device_property_read_u32(cs35l45->dev, prop_name, &val) == 0) { + ret = cs35l45_get_ldpm_pcm_thld((s32)val); + if (ret == -EINVAL) + return -EINVAL; + + switch (i) { + case LDPM_GROUP1: + regmap_update_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, + CS35L45_LDPM_GP1_PCM_THLD_MASK, + FIELD_PREP(CS35L45_LDPM_GP1_PCM_THLD_MASK, + ret)); + break; + case LDPM_GROUP2: + regmap_update_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, + CS35L45_LDPM_GP2_PCM_THLD_MASK, + FIELD_PREP(CS35L45_LDPM_GP2_PCM_THLD_MASK, + ret)); + break; + default: + return -EINVAL; + } + } + + sprintf(prop_name, "cirrus,ldpm-group%d-delay-ms", i); + if (device_property_read_u32(cs35l45->dev, prop_name, &val) == 0) { + ret = cs35l45_get_ldpm_delay(val); + if (ret == -EINVAL) + return -EINVAL; + + switch (i) { + case LDPM_GROUP1: + regmap_update_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, + CS35L45_LDPM_GP1_DELAY_MASK, + FIELD_PREP(CS35L45_LDPM_GP1_DELAY_MASK, ret)); + break; + case LDPM_GROUP2: + regmap_update_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, + CS35L45_LDPM_GP2_DELAY_MASK, + FIELD_PREP(CS35L45_LDPM_GP2_DELAY_MASK, ret)); + break; + default: + return -EINVAL; + } + } + } + + if (device_property_read_bool(cs35l45->dev, "cirrus,ldpm-group1-boost-select")) + regmap_set_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, CS35L45_LDPM_GP1_BOOST_SEL); + + if (device_property_read_u32(cs35l45->dev, "cirrus,boost-low-power-mode", &val) == 0) + regmap_write(cs35l45->regmap, CS35L45_BOOST_LPMODE_CFG, val); + + if (device_property_read_bool(cs35l45->dev, "cirrus,ldpm-group1-amp-select")) + regmap_set_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, CS35L45_LDPM_GP1_AMP_SEL); + + if ((device_property_read_u32(cs35l45->dev, "cirrus,amplifier-low-power-mode", &val) == 0) + && (val == 0)) + regmap_clear_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES, CS35L45_NFR_EN_MASK); + + if (device_property_read_bool(cs35l45->dev, "cirrus,ldpm-group2-vmon-select")) + regmap_set_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, CS35L45_LDPM_GP2_VMON_SEL); + + if (device_property_read_bool(cs35l45->dev, "cirrus,ldpm-group2-imon-select")) + regmap_set_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, CS35L45_LDPM_GP2_IMON_SEL); + + return 0; +} + static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) { struct device_node *node = cs35l45->dev->of_node; @@ -1223,6 +1299,10 @@ static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) val << CS35L45_ASP_DOUT_HIZ_CTRL_SHIFT); } + ret = cs35l45_apply_ldpm_config(cs35l45); + if (ret < 0) + return ret; + return 0; } diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h index f8a4713ae92f..5014c765adcb 100644 --- a/sound/soc/codecs/cs35l45.h +++ b/sound/soc/codecs/cs35l45.h @@ -38,6 +38,7 @@ #define CS35L45_SYNC_TX_RX_ENABLES 0x00003400 #define CS35L45_SYNC_SW_TX_ID 0x00003408 #define CS35L45_BOOST_CCM_CFG 0x00003808 +#define CS35L45_BOOST_LPMODE_CFG 0x00003810 #define CS35L45_BOOST_DCM_CFG 0x0000380C #define CS35L45_BOOST_OV_CFG 0x0000382C #define CS35L45_ASP_ENABLES1 0x00004800 @@ -174,6 +175,8 @@ #define CS35L45_BST_EN_MASK GENMASK(5, 4) #define CS35L45_RCV_EN_SHIFT 2 #define CS35L45_RCV_EN_MASK BIT(2) +#define CS35L45_NFR_EN_SHIFT 1 +#define CS35L45_NFR_EN_MASK BIT(1) #define CS35L45_AMP_EN_SHIFT 0 #define CS35L45_AMP_EN_MASK BIT(0) @@ -240,6 +243,9 @@ #define CS35L45_SYNC_SW_TXID_SHIFT 0 #define CS35L45_SYNC_SW_TXID_MASK GENMASK(2, 0) +/* BOOST_LPMODE_CFG */ +#define CS35L45_BST_LPMODE_SEL GENMASK(1, 0) + /* ASP_ENABLES_1 */ #define CS35L45_ASP_RX2_EN_SHIFT 17 #define CS35L45_ASP_RX1_EN_SHIFT 16 @@ -303,6 +309,20 @@ #define CS35L45_HVLV_MODE_SHIFT 0 #define CS35L45_HVLV_MODE_MASK GENMASK(1, 0) +/* LDPM_CONFIG */ +#define CS35L45_LDPM_GP1_BOOST_SEL BIT(15) +#define CS35L45_LDPM_GP1_AMP_SEL BIT(14) +#define CS35L45_LDPM_GP1_DELAY_SHIFT 11 +#define CS35L45_LDPM_GP1_DELAY_MASK GENMASK(13, 11) +#define CS35L45_LDPM_GP1_PCM_THLD_SHIFT 8 +#define CS35L45_LDPM_GP1_PCM_THLD_MASK GENMASK(10, 8) +#define CS35L45_LDPM_GP2_IMON_SEL BIT(7) +#define CS35L45_LDPM_GP2_VMON_SEL BIT(6) +#define CS35L45_LDPM_GP2_DELAY_SHIFT 3 +#define CS35L45_LDPM_GP2_DELAY_MASK GENMASK(5, 3) +#define CS35L45_LDPM_GP2_PCM_THLD_SHIFT 0 +#define CS35L45_LDPM_GP2_PCM_THLD_MASK GENMASK(2, 0) + /* AMP_PCM_CONTROL */ #define CS35L45_AMP_VOL_PCM_SHIFT 0 #define CS35L45_AMP_VOL_PCM_WIDTH 11 @@ -448,6 +468,12 @@ enum amp_mode { AMP_MODE_RCV = 1, }; +enum ldpm_groups { + LDPM_GROUP1 = 1, + LDPM_GROUP2 = 2, + NUM_LDPM_GROUP, +}; + #define CS35L45_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_3LE| \ SNDRV_PCM_FMTBIT_S24_LE) @@ -457,6 +483,8 @@ enum amp_mode { SNDRV_PCM_RATE_88200 | \ SNDRV_PCM_RATE_96000) +#define CS35L45_LDPM_PROP_NAME_MAX 37 + /* * IRQs */ @@ -531,6 +559,8 @@ extern const struct regmap_config cs35l45_i2c_regmap; extern const struct regmap_config cs35l45_spi_regmap; int cs35l45_apply_patch(struct cs35l45_private *cs35l45); int cs35l45_get_clk_freq_id(unsigned int freq); +int cs35l45_get_ldpm_pcm_thld(int pcm_thld_db); +int cs35l45_get_ldpm_delay(u32 delay_ms); int cs35l45_probe(struct cs35l45_private *cs35l45); void cs35l45_remove(struct cs35l45_private *cs35l45); From 39205bb7ed4fe74f0b5df58d9b6529ac2690303f Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 18:27:38 +0700 Subject: [PATCH 556/924] ASoC: rockchip: rockchip_sai: Handle runtime PM resume failures in set_fmt rockchip_sai_set_fmt() calls pm_runtime_get_sync() before accessing hardware registers, but ignores its return value. If the runtime resume fails, the function continues to perform register accesses while the device state is undefined. Replace pm_runtime_get_sync() with pm_runtime_resume_and_get() and return early on failure to avoid unpowered register accesses. Fixes: cc78d1eaabad ("ASoC: rockchip: add Serial Audio Interface (SAI) driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903112739.373621-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_sai.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c index 9dfe46ffe0ff..dab96df8bf96 100644 --- a/sound/soc/rockchip/rockchip_sai.c +++ b/sound/soc/rockchip/rockchip_sai.c @@ -487,7 +487,9 @@ static int rockchip_sai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) unsigned long flags; int ret = 0; - pm_runtime_get_sync(dai->dev); + ret = pm_runtime_resume_and_get(dai->dev); + if (ret) + return ret; mask = SAI_CKR_MSS_MASK; switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { From e9eb1854d16136006c6fe94c927b54b2b264b429 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 18:27:39 +0700 Subject: [PATCH 557/924] ASoC: rockchip: rockchip_i2s: Handle runtime PM resume failures in set_fmt rockchip_i2s_set_fmt() calls pm_runtime_get_sync() before accessing hardware registers, but ignores its return value. If the runtime resume fails, the function continues to perform register accesses while the device state is undefined. Replace pm_runtime_get_sync() with pm_runtime_resume_and_get() and return early on failure to avoid unpowered register accesses. Fixes: 53ca9b9777b9 ("ASoC: rockchip: i2s: Fix regmap_ops hang") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903112739.373621-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_i2s.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c index ef3f6586af77..0a67632e68ed 100644 --- a/sound/soc/rockchip/rockchip_i2s.c +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -244,7 +244,9 @@ static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int mask = 0, val = 0; int ret = 0; - pm_runtime_get_sync(cpu_dai->dev); + ret = pm_runtime_resume_and_get(cpu_dai->dev); + if (ret) + return ret; mask = I2S_CKR_MSS_MASK; switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { case SND_SOC_DAIFMT_BP_FP: From 59b7978580c70aa0934e9b1968f77e9f876cd658 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:28 +0300 Subject: [PATCH 558/924] ALSA: compress: pin card module while stream is open Take a module reference in snd_compr_open() and release it in snd_compr_free(). This pins the card driver module for the lifetime of an open compress stream and prevents card removal while the stream file is still in use. Adjust the open() cleanup paths to drop the added module reference only when it was acquired, and keep release ordering safe by dropping the module reference before freeing stream data. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Reviewed-by: Takashi Iwai Link: https://patch.msgid.link/20261007084955.1256-2-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/core/compress_offload.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 7c397b1c9231..ba6cdfe64246 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -75,11 +75,11 @@ static inline void snd_compr_task_free_all(struct snd_compr_stream *stream) { } static int snd_compr_open(struct inode *inode, struct file *f) { struct snd_compr *compr; - struct snd_compr_file *data; - struct snd_compr_runtime *runtime; + struct snd_compr_file *data = NULL; + struct snd_compr_runtime *runtime = NULL; enum snd_compr_direction dirn; int maj = imajor(inode); - int ret; + int ret = 0; if ((f->f_flags & O_ACCMODE) == O_WRONLY) dirn = SND_COMPRESS_PLAYBACK; @@ -101,16 +101,21 @@ static int snd_compr_open(struct inode *inode, struct file *f) return -ENODEV; } + if (!try_module_get(compr->card->module)) { + snd_card_unref(compr->card); + return -EFAULT; + } + if (dirn != compr->direction) { pr_err("this device doesn't support this direction\n"); - snd_card_unref(compr->card); - return -EINVAL; + ret = -EINVAL; + goto __error; } data = kzalloc_obj(*data); if (!data) { - snd_card_unref(compr->card); - return -ENOMEM; + ret = -ENOMEM; + goto __error; } INIT_DELAYED_WORK(&data->stream.error_work, error_delayed_work); @@ -121,9 +126,8 @@ static int snd_compr_open(struct inode *inode, struct file *f) data->stream.device = compr; runtime = kzalloc_obj(*runtime); if (!runtime) { - kfree(data); - snd_card_unref(compr->card); - return -ENOMEM; + ret = -ENOMEM; + goto __error; } runtime->state = SNDRV_PCM_STATE_OPEN; init_waitqueue_head(&runtime->sleep); @@ -134,9 +138,12 @@ static int snd_compr_open(struct inode *inode, struct file *f) f->private_data = (void *)data; scoped_guard(mutex, &compr->lock) ret = compr->ops->open(&data->stream); + +__error: if (ret) { kfree(runtime); kfree(data); + module_put(compr->card->module); } snd_card_unref(compr->card); return ret; @@ -164,6 +171,7 @@ static int snd_compr_free(struct inode *inode, struct file *f) data->stream.ops->free(&data->stream); if (!data->stream.runtime->dma_buffer_p) kfree(data->stream.runtime->buffer); + module_put(data->stream.device->card->module); kfree(data->stream.runtime); kfree(data); return 0; From fd7f98db9c28574b102303e317f4dca712281003 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:29 +0300 Subject: [PATCH 559/924] ALSA: compress: register the open file with the card The compress-offload device is the only ALSA character device family which does not add its open files to the card's file list. Without it the core cannot replace the file operations on disconnect, so syscalls issued after the card is gone still reach the driver, and a card freed with snd_card_free_when_closed() does not wait for the open compress streams. Call snd_card_file_add() at open and snd_card_file_remove() at release, following the ordering used by the PCM, control, rawmidi and hwdep devices. Suggested-by: Takashi Iwai Signed-off-by: Peter Ujfalusi Reviewed-by: Takashi Iwai Link: https://patch.msgid.link/20261007084955.1256-3-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/core/compress_offload.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index ba6cdfe64246..aaed865f8175 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -101,7 +101,14 @@ static int snd_compr_open(struct inode *inode, struct file *f) return -ENODEV; } + ret = snd_card_file_add(compr->card, f); + if (ret < 0) { + snd_card_unref(compr->card); + return ret; + } + if (!try_module_get(compr->card->module)) { + snd_card_file_remove(compr->card, f); snd_card_unref(compr->card); return -EFAULT; } @@ -144,6 +151,7 @@ static int snd_compr_open(struct inode *inode, struct file *f) kfree(runtime); kfree(data); module_put(compr->card->module); + snd_card_file_remove(compr->card, f); } snd_card_unref(compr->card); return ret; @@ -153,6 +161,7 @@ static int snd_compr_free(struct inode *inode, struct file *f) { struct snd_compr_file *data = f->private_data; struct snd_compr_runtime *runtime = data->stream.runtime; + struct snd_compr *compr = data->stream.device; cancel_delayed_work_sync(&data->stream.error_work); @@ -174,6 +183,7 @@ static int snd_compr_free(struct inode *inode, struct file *f) module_put(data->stream.device->card->module); kfree(data->stream.runtime); kfree(data); + snd_card_file_remove(compr->card, f); return 0; } From b04c9229ee31be8f3cd87d15648b040bee2e64ae Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:30 +0300 Subject: [PATCH 560/924] ALSA: compress: stop active streams on disconnect Track open compressed streams per device so disconnect can stop active streams and wake waiters before snd_unregister_device(). This aligns compressed stream teardown with PCM disconnect behavior and prevents active userspace streams from running into unregister races. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Reviewed-by: Takashi Iwai Link: https://patch.msgid.link/20261007084955.1256-4-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- include/sound/compress_driver.h | 2 ++ sound/core/compress_offload.c | 48 ++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/include/sound/compress_driver.h b/include/sound/compress_driver.h index 9e3d801e45ec..84f51edc3f9d 100644 --- a/include/sound/compress_driver.h +++ b/include/sound/compress_driver.h @@ -188,6 +188,7 @@ struct snd_compr_ops { * @card: sound card pointer * @direction: Playback or capture direction * @lock: device lock + * @open_list: list of open compress files * @device: device id * @use_pause_in_draining: allow pause in draining, true when set */ @@ -199,6 +200,7 @@ struct snd_compr { struct snd_card *card; unsigned int direction; struct mutex lock; + struct list_head open_list; int device; bool use_pause_in_draining; #ifdef CONFIG_SND_VERBOSE_PROCFS diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index aaed865f8175..89435e4394ef 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -42,6 +42,7 @@ #endif struct snd_compr_file { + struct list_head list; unsigned long caps; struct snd_compr_stream stream; }; @@ -124,6 +125,7 @@ static int snd_compr_open(struct inode *inode, struct file *f) ret = -ENOMEM; goto __error; } + INIT_LIST_HEAD(&data->list); INIT_DELAYED_WORK(&data->stream.error_work, error_delayed_work); @@ -143,8 +145,11 @@ static int snd_compr_open(struct inode *inode, struct file *f) #endif data->stream.runtime = runtime; f->private_data = (void *)data; - scoped_guard(mutex, &compr->lock) + scoped_guard(mutex, &compr->lock) { ret = compr->ops->open(&data->stream); + if (!ret) + list_add_tail(&data->list, &compr->open_list); + } __error: if (ret) { @@ -165,14 +170,19 @@ static int snd_compr_free(struct inode *inode, struct file *f) cancel_delayed_work_sync(&data->stream.error_work); - switch (runtime->state) { - case SNDRV_PCM_STATE_RUNNING: - case SNDRV_PCM_STATE_DRAINING: - case SNDRV_PCM_STATE_PAUSED: - data->stream.ops->trigger(&data->stream, SNDRV_PCM_TRIGGER_STOP); - break; - default: - break; + scoped_guard(mutex, &compr->lock) { + if (!list_empty(&data->list)) + list_del_init(&data->list); + + switch (runtime->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_PAUSED: + data->stream.ops->trigger(&data->stream, SNDRV_PCM_TRIGGER_STOP); + break; + default: + break; + } } snd_compr_task_free_all(&data->stream); @@ -1450,8 +1460,27 @@ static int snd_compress_dev_register(struct snd_device *device) static int snd_compress_dev_disconnect(struct snd_device *device) { struct snd_compr *compr; + struct snd_compr_file *data; compr = device->device_data; + scoped_guard(mutex, &compr->lock) { + list_for_each_entry(data, &compr->open_list, list) { + switch (data->stream.runtime->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_PAUSED: + data->stream.ops->trigger(&data->stream, + SNDRV_PCM_TRIGGER_STOP); + break; + default: + break; + } + + data->stream.runtime->state = SNDRV_PCM_STATE_DISCONNECTED; + wake_up(&data->stream.runtime->sleep); + } + } + snd_unregister_device(compr->dev); return 0; } @@ -1559,6 +1588,7 @@ int snd_compress_new(struct snd_card *card, int device, compr->device = device; compr->direction = dirn; mutex_init(&compr->lock); + INIT_LIST_HEAD(&compr->open_list); snd_compress_set_id(compr, id); From 84aede82dd4e628d863d5eb597f3b164f1c1f2d9 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:31 +0300 Subject: [PATCH 561/924] ALSA: compress: Set the draining state before the drain trigger The state of the stream is set to SNDRV_PCM_STATE_DRAINING only after the drain trigger has been sent to the driver. Drivers can not block in the trigger callback and the drain is completed asynchronously: snd_compr_drain_notify() can be called either from the trigger callback itself or from a different context as soon as the trigger returns. If that happens before the core updates the state then the notification is lost: the state is overwritten with DRAINING right after it and the wait for the drain to complete will not be woken up. Set the state before the trigger is issued and restore it if the trigger fails. The partial_drain flag is cleared on the error path as well as it would otherwise be left set for the next drain. Signed-off-by: Peter Ujfalusi Link: https://patch.msgid.link/20261007084955.1256-5-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/core/compress_offload.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 89435e4394ef..5c0db178d635 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -947,8 +947,10 @@ static int snd_compress_wait_for_drain(struct snd_compr_stream *stream) * It is expected that driver will notify the drain completion and then * stream will be moved to SETUP state, even if draining resulted in an * error. We can trigger next track after this. + * + * The state has been set to SNDRV_PCM_STATE_DRAINING by the caller, + * before the drain trigger was issued. */ - stream->runtime->state = SNDRV_PCM_STATE_DRAINING; mutex_unlock(&stream->device->lock); /* we wait for drain to complete here, drain can return when @@ -973,6 +975,7 @@ static int snd_compress_wait_for_drain(struct snd_compr_stream *stream) static int snd_compr_drain(struct snd_compr_stream *stream) { + snd_pcm_state_t state; int retval; switch (stream->runtime->state) { @@ -987,9 +990,20 @@ static int snd_compr_drain(struct snd_compr_stream *stream) break; } + /* + * The state must be set to draining before the trigger is issued as the + * drain can be completed by the driver either from the trigger callback + * itself or from a different context as soon as the trigger is called. + * A snd_compr_drain_notify() arriving before the state is updated would + * be lost and the wait for the drain to finish would never be woken up. + */ + state = stream->runtime->state; + stream->runtime->state = SNDRV_PCM_STATE_DRAINING; + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_DRAIN); if (retval) { pr_debug("SND_COMPR_TRIGGER_DRAIN failed %d\n", retval); + stream->runtime->state = state; wake_up(&stream->runtime->sleep); return retval; } @@ -1025,6 +1039,7 @@ static int snd_compr_next_track(struct snd_compr_stream *stream) static int snd_compr_partial_drain(struct snd_compr_stream *stream) { + snd_pcm_state_t state; int retval; switch (stream->runtime->state) { @@ -1048,9 +1063,16 @@ static int snd_compr_partial_drain(struct snd_compr_stream *stream) return -EPERM; stream->partial_drain = true; + + /* See the comment in snd_compr_drain() on the state handling */ + state = stream->runtime->state; + stream->runtime->state = SNDRV_PCM_STATE_DRAINING; + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_PARTIAL_DRAIN); if (retval) { pr_debug("Partial drain returned failure\n"); + stream->partial_drain = false; + stream->runtime->state = state; wake_up(&stream->runtime->sleep); return retval; } From c6cf521a0db34ae4b84dab4a66ae08da6c201cf0 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:32 +0300 Subject: [PATCH 562/924] ASoC: soc-compress: Provide a runtime for the compressed FE substream When a compressed stream is used on the FE side of a DPCM link, the BE is still running as 'normal' PCM. DPCM lends the runtime of the FE to every BE it opens and dpcm_be_reparent() re-points it at another FE when the lending one disconnects. The internal PCM created for a compressed FE by snd_soc_new_compress() is never opened via the PCM API, so its substream has no runtime and the BEs of a compressed FE were left with a NULL one. BE DAI and codec drivers can look at substream->runtime, they add their constraints to it in startup(), for example cs42l43, cs42l42, hdac_hdmi. The other users of compressed DPCM do not hit this as their BEs are DSP internal ports, driven by code which takes everything from the hw_params it is passed and never looks at substream->runtime. The BEs here end at generic CODEC drivers which are shared with the non DPCM case and cannot be expected to know about the FE type. Allocate a runtime for the compressed FE substream while the stream is open, initialize its constraints as snd_pcm_open() does and fill it in from the BE parameters once the mandatory machine level be_hw_params_fixup() has run, so that the compressed FE lends a valid and populated runtime just like a PCM FE does. This also allows a BE to be shared between a compressed and a PCM FE, which is needed to play a notification over PCM to an endpoint which a compressed stream is already using. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-6-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/soc-compress.c | 109 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index b8402802ae78..e5a70c9b3d31 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -130,6 +131,104 @@ static int soc_compr_open(struct snd_compr_stream *cstream) return ret; } +/* + * The internal PCM of a compressed FE is never opened via the PCM API, so its + * substream has no runtime attached to it. + * + * DPCM lends the runtime of the FE to every BE it opens and re-points it at + * another FE when the lending one goes away, so the compressed FE must provide + * one as well. Without it the BEs are left with a NULL runtime, which oopses + * in BE DAI and CODEC drivers looking at substream->runtime, and a BE can not + * be shared between a compressed and a PCM FE at all. + * + * The runtime is owned by the FE for as long as the compressed stream is open. + */ +static int soc_compr_alloc_fe_runtime(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_pcm_substream *fe_substream = snd_soc_dpcm_get_substream(fe, stream); + struct snd_pcm_hw_constraints *constrs; + int i; + + if (!fe_substream || fe_substream->runtime) + return 0; + + fe_substream->runtime = kzalloc_obj(*fe_substream->runtime); + if (!fe_substream->runtime) + return -ENOMEM; + + /* + * snd_pcm_open() initializes the constraints of a PCM runtime to + * 'anything goes'. A zeroed one means an empty mask and a [0, 0] + * interval instead, which the constraint helpers refine against and + * reject, so initialize them the same way the PCM core does. + * + * The hw rules snd_pcm_hw_constraints_init() installs on top are only + * evaluated by snd_pcm_hw_refine(), which never runs for this + * substream, so they are not needed here. + */ + constrs = &fe_substream->runtime->hw_constraints; + for (i = SNDRV_PCM_HW_PARAM_FIRST_MASK; i <= SNDRV_PCM_HW_PARAM_LAST_MASK; i++) + snd_mask_any(constrs_mask(constrs, i)); + + for (i = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; i <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; i++) + snd_interval_any(constrs_interval(constrs, i)); + + return 0; +} + +static void soc_compr_free_fe_runtime(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_pcm_substream *fe_substream = snd_soc_dpcm_get_substream(fe, stream); + + if (!fe_substream || !fe_substream->runtime) + return; + + /* BE startup callbacks may have added hw constraint rules */ + kfree(fe_substream->runtime->hw_constraints.rules); + kfree(fe_substream->runtime); + fe_substream->runtime = NULL; +} + +/* + * The BE parameters of a compressed FE are set up by the machine level + * be_hw_params_fixup(), which is mandatory for a compressed BE, see + * soc_compr_set_params_fe(). Once they are fixed up, use them to fill in the + * runtime the BEs have been lent, so that a BE DAI or CODEC driver sees the + * format it is being configured for. + */ +static void soc_compr_set_fe_runtime(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_pcm_substream *fe_substream = snd_soc_dpcm_get_substream(fe, stream); + struct snd_pcm_runtime *runtime; + struct snd_soc_dpcm *dpcm; + + snd_soc_dpcm_mutex_assert_held(fe); + + if (!fe_substream || !fe_substream->runtime) + return; + + runtime = fe_substream->runtime; + + for_each_dpcm_be(fe, stream, dpcm) { + struct snd_pcm_hw_params *params = &dpcm->be->dpcm[stream].hw_params; + int bits = snd_pcm_format_physical_width(params_format(params)); + + /* skip a BE which has not been fixed up */ + if (bits <= 0) + continue; + + runtime->access = params_access(params); + runtime->format = params_format(params); + runtime->subformat = params_subformat(params); + runtime->channels = params_channels(params); + runtime->rate = params_rate(params); + + runtime->sample_bits = bits; + runtime->frame_bits = bits * runtime->channels; + break; + } +} + static int soc_compr_open_fe(struct snd_compr_stream *cstream) { struct snd_soc_pcm_runtime *fe = cstream->private_data; @@ -141,6 +240,10 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) snd_soc_card_mutex_lock(fe->card); + ret = soc_compr_alloc_fe_runtime(fe, stream); + if (ret < 0) + goto be_err; + ret = dpcm_path_get(fe, stream, &list); if (ret < 0) goto be_err; @@ -195,6 +298,7 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) dpcm_path_put(&list); snd_soc_dpcm_mutex_unlock(fe); be_err: + soc_compr_free_fe_runtime(fe, stream); fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; snd_soc_card_mutex_unlock(fe->card); return ret; @@ -237,6 +341,9 @@ static int soc_compr_free_fe(struct snd_compr_stream *cstream) snd_soc_dai_compr_shutdown(cpu_dai, cstream, 0); + /* all BEs are shut down and disconnected, the runtime is unused now */ + soc_compr_free_fe_runtime(fe, stream); + snd_soc_card_mutex_unlock(fe->card); return 0; } @@ -387,6 +494,8 @@ static int soc_compr_set_params_fe(struct snd_compr_stream *cstream, snd_soc_dpcm_mutex_lock(fe); ret = dpcm_be_dai_hw_params(fe, stream); + if (!ret) + soc_compr_set_fe_runtime(fe, stream); snd_soc_dpcm_mutex_unlock(fe); if (ret < 0) goto out; From 39f013967e15ae756127e7c64a25e7ab566b224b Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:33 +0300 Subject: [PATCH 563/924] ASoC: soc-compress: Implement trigger FE-BE sequencing as with normal PCMs The FE-BE trigger sequence should be dynamic, similarly how soc-pcm.c dpcm_fe_dai_do_trigger() does it. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-7-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/soc-compress.c | 85 ++++++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index e5a70c9b3d31..e9f6ab8c1678 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -380,30 +380,89 @@ static int soc_compr_trigger(struct snd_compr_stream *cstream, int cmd) return ret; } -static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd) +static int soc_compr_trigger_fe_be(struct snd_compr_stream *cstream, int cmd, + bool fe_first) { struct snd_soc_pcm_runtime *fe = cstream->private_data; struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(fe, 0); - int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */ int ret; - if (cmd == SND_COMPR_TRIGGER_PARTIAL_DRAIN || - cmd == SND_COMPR_TRIGGER_DRAIN) + if (fe_first) { + dev_dbg(fe->dev, "ASoC: pre trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = snd_soc_dai_compr_trigger(cpu_dai, cstream, cmd); + if (ret < 0) + goto out; + + ret = snd_soc_component_compr_trigger(cstream, cmd); + if (ret < 0) + goto out; + + ret = dpcm_be_dai_trigger(fe, cstream->direction, cmd); + } else { + dev_dbg(fe->dev, "ASoC: post trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = dpcm_be_dai_trigger(fe, cstream->direction, cmd); + if (ret < 0) + goto out; + + ret = snd_soc_dai_compr_trigger(cpu_dai, cstream, cmd); + if (ret < 0) + goto out; + + ret = snd_soc_component_compr_trigger(cstream, cmd); + } + +out: + return snd_soc_ret(fe->dev, ret, "trigger FE cmd: %d failed\n", cmd); +} + +static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd) +{ + struct snd_soc_pcm_runtime *fe = cstream->private_data; + int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */ + bool fe_first; + int ret; + + /* FE only triggers */ + switch (cmd) { + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + case SND_COMPR_TRIGGER_NEXT_TRACK: + case SND_COMPR_TRIGGER_DRAIN: return snd_soc_component_compr_trigger(cstream, cmd); + default: + break; + } + + if (fe->dai_link->trigger[stream] == SND_SOC_DPCM_TRIGGER_POST) + fe_first = false; + else + fe_first = true; snd_soc_card_mutex_lock(fe->card); - ret = snd_soc_dai_compr_trigger(cpu_dai, cstream, cmd); - if (ret < 0) - goto out; - - ret = snd_soc_component_compr_trigger(cstream, cmd); - if (ret < 0) - goto out; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; - ret = dpcm_be_dai_trigger(fe, stream, cmd); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = soc_compr_trigger_fe_be(cstream, cmd, fe_first); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = soc_compr_trigger_fe_be(cstream, cmd, !fe_first); + break; + default: + ret = -EINVAL; + break; + } + + if (ret < 0) + goto out; switch (cmd) { case SNDRV_PCM_TRIGGER_START: From fc6b7cfdbcf01f5355ae0f870af78bbc72a73c82 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:34 +0300 Subject: [PATCH 564/924] ASoC: soc-compress: Stop running dpcm on free If the last trigger that the compr device received is a DRAIN then the DPCM is left in running state (no stop trigger is sent). Before we execute the free we need to send a STOP trigger to make sure that both BE and FE is in expected state and prepared for closing. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-8-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/soc-compress.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index e9f6ab8c1678..3e1c11c788ef 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -311,6 +311,16 @@ static int soc_compr_free_fe(struct snd_compr_stream *cstream) struct snd_soc_dpcm *dpcm; int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */ + /* + * The core will not send a STOP trigger on free if the device is in + * DRAIN state, but we need to stop BE and FE before we can proceed to + * free the stream. + * Run the STOP trigger if the DPCM state is START (DRAIN is not + * changing the DPCM state). + */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_START) + cstream->ops->trigger(cstream, SNDRV_PCM_TRIGGER_STOP); + snd_soc_card_mutex_lock(fe->card); snd_soc_dpcm_mutex_lock(fe); From 3af66bfe7648b8500903a73685d7de0fcc52f2b0 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 7 Oct 2026 11:49:35 +0300 Subject: [PATCH 565/924] ASoC: SOF: compress: Move the IPC agnostic helpers to sof-audio.c The fragment elapsed handling and the page table creation in compress.c are not tied to the IPC version, they will be used by both the IPC3 and the upcoming IPC4 compressed implementation. Move them to sof-audio.c and export create_page_table() as snd_sof_compr_create_page_table(). No functional change. Signed-off-by: Ranjani Sridharan Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-9-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/compress.c | 82 +-------------------------------------- sound/soc/sof/sof-audio.c | 81 ++++++++++++++++++++++++++++++++++++++ sound/soc/sof/sof-audio.h | 3 ++ 3 files changed, 85 insertions(+), 81 deletions(-) diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c index 93f2376585db..76d03faaafc4 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/compress.c @@ -12,86 +12,6 @@ #include "sof-utils.h" #include "ops.h" -static void sof_set_transferred_bytes(struct sof_compr_stream *sstream, - u64 host_pos, u64 buffer_size) -{ - u64 prev_pos; - unsigned int copied; - - div64_u64_rem(sstream->copied_total, buffer_size, &prev_pos); - - if (host_pos < prev_pos) - copied = (buffer_size - prev_pos) + host_pos; - else - copied = host_pos - prev_pos; - - sstream->copied_total += copied; -} - -static void snd_sof_compr_fragment_elapsed_work(struct work_struct *work) -{ - struct snd_sof_pcm_stream *sps = - container_of(work, struct snd_sof_pcm_stream, - period_elapsed_work); - - snd_compr_fragment_elapsed(sps->cstream); -} - -void snd_sof_compr_init_elapsed_work(struct work_struct *work) -{ - INIT_WORK(work, snd_sof_compr_fragment_elapsed_work); -} - -/* - * sof compr fragment elapse, this could be called in irq thread context - */ -void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream) -{ - struct snd_soc_pcm_runtime *rtd; - struct snd_compr_runtime *crtd; - struct snd_soc_component *component; - struct sof_compr_stream *sstream; - struct snd_sof_pcm *spcm; - - if (!cstream) - return; - - rtd = cstream->private_data; - crtd = cstream->runtime; - sstream = crtd->private_data; - component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); - - spcm = snd_sof_find_spcm_dai(component, rtd); - if (!spcm) { - dev_err(component->dev, - "fragment elapsed called for unknown stream!\n"); - return; - } - - sof_set_transferred_bytes(sstream, spcm->stream[cstream->direction].posn.host_posn, - crtd->buffer_size); - - /* use the same workqueue-based solution as for PCM, cf. snd_sof_pcm_elapsed */ - schedule_work(&spcm->stream[cstream->direction].period_elapsed_work); -} - -static int create_page_table(struct snd_soc_component *component, - struct snd_compr_stream *cstream, - unsigned char *dma_area, size_t size) -{ - struct snd_dma_buffer *dmab = cstream->runtime->dma_buffer_p; - struct snd_soc_pcm_runtime *rtd = cstream->private_data; - int dir = cstream->direction; - struct snd_sof_pcm *spcm; - - spcm = snd_sof_find_spcm_dai(component, rtd); - if (!spcm) - return -EINVAL; - - return snd_sof_create_page_table(component->dev, dmab, - spcm->stream[dir].page_table.area, size); -} - static int sof_compr_open(struct snd_soc_component *component, struct snd_compr_stream *cstream) { @@ -213,7 +133,7 @@ static int sof_compr_set_params(struct snd_soc_component *component, if (ret < 0) goto out; - ret = create_page_table(component, cstream, crtd->dma_area, crtd->dma_bytes); + ret = snd_sof_compr_create_page_table(component, cstream, crtd->dma_area, crtd->dma_bytes); if (ret < 0) goto out; diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 72ff03b0b80d..d244e90a734b 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -11,6 +11,7 @@ #include #include #include "sof-audio.h" +#include "sof-utils.h" #include "ops.h" /* @@ -1050,3 +1051,83 @@ int sof_dai_get_tdm_slots(struct snd_soc_pcm_runtime *rtd) return sof_dai_get_param(rtd, SOF_DAI_PARAM_INTEL_SSP_TDM_SLOTS); } EXPORT_SYMBOL(sof_dai_get_tdm_slots); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) +static void sof_set_transferred_bytes(struct sof_compr_stream *sstream, + u64 host_pos, u64 buffer_size) +{ + u64 prev_pos; + unsigned int copied; + + div64_u64_rem(sstream->copied_total, buffer_size, &prev_pos); + + if (host_pos < prev_pos) + copied = (buffer_size - prev_pos) + host_pos; + else + copied = host_pos - prev_pos; + + sstream->copied_total += copied; +} + +static void snd_sof_compr_fragment_elapsed_work(struct work_struct *work) +{ + struct snd_sof_pcm_stream *sps = container_of(work, struct snd_sof_pcm_stream, + period_elapsed_work); + + snd_compr_fragment_elapsed(sps->cstream); +} + +void snd_sof_compr_init_elapsed_work(struct work_struct *work) +{ + INIT_WORK(work, snd_sof_compr_fragment_elapsed_work); +} + +/* + * sof compr fragment elapse, this could be called in irq thread context + */ +void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_compr_runtime *crtd; + struct snd_soc_component *component; + struct sof_compr_stream *sstream; + struct snd_sof_pcm *spcm; + + if (!cstream) + return; + + rtd = cstream->private_data; + crtd = cstream->runtime; + sstream = crtd->private_data; + component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) { + dev_err(component->dev, "fragment elapsed called for unknown stream!\n"); + return; + } + + sof_set_transferred_bytes(sstream, spcm->stream[cstream->direction].posn.host_posn, + crtd->buffer_size); + + /* use the same workqueue-based solution as for PCM, cf. snd_sof_pcm_elapsed */ + schedule_work(&spcm->stream[cstream->direction].period_elapsed_work); +} + +int snd_sof_compr_create_page_table(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + unsigned char *dma_area, size_t size) +{ + struct snd_dma_buffer *dmab = cstream->runtime->dma_buffer_p; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + int dir = cstream->direction; + struct snd_sof_pcm *spcm; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + return snd_sof_create_page_table(component->dev, dmab, + spcm->stream[dir].page_table.area, size); +} +#endif diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index ae95efc9be1c..c09099f30378 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -660,6 +660,9 @@ void snd_sof_pcm_init_elapsed_work(struct work_struct *work); #if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream); void snd_sof_compr_init_elapsed_work(struct work_struct *work); +int snd_sof_compr_create_page_table(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + unsigned char *dma_area, size_t size); #else static inline void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream) { } static inline void snd_sof_compr_init_elapsed_work(struct work_struct *work) { } From aa2adb90252c7bd56de7473d5c20e3159446c344 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 7 Oct 2026 11:49:36 +0300 Subject: [PATCH 566/924] ASoC: SOF: compress: Rename compress ops with ipc3 prefix In preparation for adding support for compressed offload support for IPC4, rename the current compress implementation with the IPC3 prefix and rename the compress.c file to ipc3-compress.c Introduce a new field in struct sof_ipc_pcm_ops to save the IPC-specific compressed ops pointer, which is assigned to the component driver ops during SOF device probe. As this now depends on the IPC ops, move snd_sof_new_platform_drv() after the IPC ops are initialized. Signed-off-by: Ranjani Sridharan Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-10-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/Makefile | 2 +- sound/soc/sof/core.c | 10 ++- sound/soc/sof/{compress.c => ipc3-compress.c} | 63 ++++++++++--------- sound/soc/sof/ipc3-pcm.c | 3 + sound/soc/sof/ipc3-priv.h | 3 + sound/soc/sof/pcm.c | 5 +- sound/soc/sof/sof-audio.h | 2 + 7 files changed, 52 insertions(+), 36 deletions(-) rename sound/soc/sof/{compress.c => ipc3-compress.c} (82%) diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index b0b22e6ebc03..3d52f8c4d173 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -19,7 +19,7 @@ ifneq ($(CONFIG_SND_SOC_SOF_CLIENT),) snd-sof-y += sof-client.o endif -snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += compress.o +snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += ipc3-compress.o snd-sof-pci-y := sof-pci-dev.o snd-sof-acpi-y := sof-acpi-dev.o diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 2d394389c945..41747ef0e432 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -463,11 +463,12 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); - /* set up platform component driver */ - snd_sof_new_platform_drv(sdev); - if (sdev->dspless_mode_selected) { sof_set_fw_state(sdev, SOF_DSPLESS_MODE); + + /* set up platform component driver */ + snd_sof_new_platform_drv(sdev); + goto skip_dsp_init; } @@ -492,6 +493,9 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto ipc_err; } + /* set up platform component driver after initializing the IPC ops */ + snd_sof_new_platform_drv(sdev); + /* load the firmware */ ret = snd_sof_load_firmware(sdev); if (ret < 0) { diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/ipc3-compress.c similarity index 82% rename from sound/soc/sof/compress.c rename to sound/soc/sof/ipc3-compress.c index 76d03faaafc4..115d454bcaf5 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/ipc3-compress.c @@ -12,8 +12,8 @@ #include "sof-utils.h" #include "ops.h" -static int sof_compr_open(struct snd_soc_component *component, - struct snd_compr_stream *cstream) +static int sof_ipc3_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *cstream) { struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct snd_compr_runtime *crtd = cstream->runtime; @@ -48,8 +48,8 @@ static int sof_compr_open(struct snd_soc_component *component, return 0; } -static int sof_compr_free(struct snd_soc_component *component, - struct snd_compr_stream *cstream) +static int sof_ipc3_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *cstream) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct sof_compr_stream *sstream = cstream->runtime->private_data; @@ -79,8 +79,9 @@ static int sof_compr_free(struct snd_soc_component *component, return ret; } -static int sof_compr_set_params(struct snd_soc_component *component, - struct snd_compr_stream *cstream, struct snd_compr_params *params) +static int sof_ipc3_compr_set_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_params *params) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_soc_pcm_runtime *rtd = cstream->private_data; @@ -185,8 +186,9 @@ static int sof_compr_set_params(struct snd_soc_component *component, return ret; } -static int sof_compr_get_params(struct snd_soc_component *component, - struct snd_compr_stream *cstream, struct snd_codec *params) +static int sof_ipc3_compr_get_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_codec *params) { struct sof_compr_stream *sstream = cstream->runtime->private_data; @@ -195,8 +197,8 @@ static int sof_compr_get_params(struct snd_soc_component *component, return 0; } -static int sof_compr_trigger(struct snd_soc_component *component, - struct snd_compr_stream *cstream, int cmd) +static int sof_ipc3_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *cstream, int cmd) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_soc_pcm_runtime *rtd = cstream->private_data; @@ -232,8 +234,8 @@ static int sof_compr_trigger(struct snd_soc_component *component, return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream)); } -static int sof_compr_copy_playback(struct snd_compr_runtime *rtd, - char __user *buf, size_t count) +static int sof_ipc3_compr_copy_playback(struct snd_compr_runtime *rtd, + char __user *buf, size_t count) { void *ptr; unsigned int offset, n; @@ -253,8 +255,8 @@ static int sof_compr_copy_playback(struct snd_compr_runtime *rtd, return count - ret; } -static int sof_compr_copy_capture(struct snd_compr_runtime *rtd, - char __user *buf, size_t count) +static int sof_ipc3_compr_copy_capture(struct snd_compr_runtime *rtd, + char __user *buf, size_t count) { void *ptr; unsigned int offset, n; @@ -274,9 +276,9 @@ static int sof_compr_copy_capture(struct snd_compr_runtime *rtd, return count - ret; } -static int sof_compr_copy(struct snd_soc_component *component, - struct snd_compr_stream *cstream, - char __user *buf, size_t count) +static int sof_ipc3_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) { struct snd_compr_runtime *rtd = cstream->runtime; @@ -284,14 +286,14 @@ static int sof_compr_copy(struct snd_soc_component *component, count = rtd->buffer_size; if (cstream->direction == SND_COMPRESS_PLAYBACK) - return sof_compr_copy_playback(rtd, buf, count); + return sof_ipc3_compr_copy_playback(rtd, buf, count); else - return sof_compr_copy_capture(rtd, buf, count); + return sof_ipc3_compr_copy_capture(rtd, buf, count); } -static int sof_compr_pointer(struct snd_soc_component *component, - struct snd_compr_stream *cstream, - struct snd_compr_tstamp64 *tstamp) +static int sof_ipc3_compr_pointer(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp) { struct snd_sof_pcm *spcm; struct snd_soc_pcm_runtime *rtd = cstream->private_data; @@ -312,13 +314,12 @@ static int sof_compr_pointer(struct snd_soc_component *component, return 0; } -struct snd_compress_ops sof_compressed_ops = { - .open = sof_compr_open, - .free = sof_compr_free, - .set_params = sof_compr_set_params, - .get_params = sof_compr_get_params, - .trigger = sof_compr_trigger, - .pointer = sof_compr_pointer, - .copy = sof_compr_copy, +const struct snd_compress_ops sof_ipc3_compressed_ops = { + .open = sof_ipc3_compr_open, + .free = sof_ipc3_compr_free, + .set_params = sof_ipc3_compr_set_params, + .get_params = sof_ipc3_compr_get_params, + .trigger = sof_ipc3_compr_trigger, + .pointer = sof_ipc3_compr_pointer, + .copy = sof_ipc3_compr_copy, }; -EXPORT_SYMBOL(sof_compressed_ops); diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c index 143bf0fe8dd9..1d0c80466f6a 100644 --- a/sound/soc/sof/ipc3-pcm.c +++ b/sound/soc/sof/ipc3-pcm.c @@ -442,4 +442,7 @@ const struct sof_ipc_pcm_ops ipc3_pcm_ops = { .dai_link_fixup = sof_ipc3_pcm_dai_link_fixup, .reset_hw_params_during_stop = true, .d0i3_supported_in_s0ix = true, +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) + .compress_ops = &sof_ipc3_compressed_ops, +#endif }; diff --git a/sound/soc/sof/ipc3-priv.h b/sound/soc/sof/ipc3-priv.h index 866c5f67b91a..f95957453ab8 100644 --- a/sound/soc/sof/ipc3-priv.h +++ b/sound/soc/sof/ipc3-priv.h @@ -17,6 +17,9 @@ extern const struct sof_ipc_tplg_ops ipc3_tplg_ops; extern const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops; extern const struct sof_ipc_fw_loader_ops ipc3_loader_ops; extern const struct sof_ipc_fw_tracing_ops ipc3_dtrace_ops; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) +extern const struct snd_compress_ops sof_ipc3_compressed_ops; +#endif /* helpers for fw_ready and ext_manifest parsing */ int sof_ipc3_get_ext_windows(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index f748d072109a..941baa48b275 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -847,7 +847,10 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->delay = sof_pcm_delay; #if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) - pd->compress_ops = &sof_compressed_ops; + const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); + + if (pcm_ops) + pd->compress_ops = pcm_ops->compress_ops; #endif pd->pcm_new = sof_pcm_new; diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index c09099f30378..bd5bbaf9ae44 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -119,6 +119,7 @@ struct snd_sof_dai_config_data { * therefore the host must do the same and should stop the DMA during * hw_free. * @d0i3_supported_in_s0ix: Allow DSP D0I3 during S0iX + * @compress_ops: Pointer to ops for compressed streams */ struct sof_ipc_pcm_ops { int (*hw_params)(struct snd_soc_component *component, struct snd_pcm_substream *substream, @@ -139,6 +140,7 @@ struct sof_ipc_pcm_ops { bool ipc_first_on_start; bool platform_stop_during_hw_free; bool d0i3_supported_in_s0ix; + const struct snd_compress_ops *compress_ops; }; /** From be887709bd9980ec2e3a5361a59e60f2e5038028 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:37 +0300 Subject: [PATCH 567/924] ASoC: SOF: sof-audio: Fix the pipeline_list population Two issues with the way sof_set_up_widgets_in_path() adds a pipeline to the list of pipelines to be triggered: The count is incremented before the array entry is written, so the last entry of the list is NULL for a short window. A concurrent user of the list, like sof_ipc4_trigger_pipelines(), can see the incremented count with the entry still unset. A widget which is not part of a pipeline has no spipe, but it is still added to the list, resulting in a NULL entry within the count. Store the entry before incrementing the count and skip the widgets without a pipeline. Signed-off-by: Peter Ujfalusi Link: https://patch.msgid.link/20261007084955.1256-11-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/sof-audio.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index d244e90a734b..850eb2bc2b44 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -629,6 +629,10 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d if (!pipeline_list->pipelines) goto sink_setup; + /* a widget which is not part of a pipeline has nothing to trigger */ + if (!swidget->spipe) + goto sink_setup; + /* * Add the widget's pipe_widget to the list of pipelines to be triggered if not * already in the list. This will result in the pipelines getting added in the @@ -641,8 +645,8 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d } if (i == pipeline_list->count) { - pipeline_list->count++; pipeline_list->pipelines[i] = swidget->spipe; + pipeline_list->count++; } } From 9d16a6633f4606d66406454a4e2ef505ed8cbf45 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:38 +0300 Subject: [PATCH 568/924] ASoC: SOF: ipc4-pcm: Serialize the PCM free with the pipeline triggers sof_ipc4_pcm_free() frees the array of the pipelines to be triggered without any serialization against sof_ipc4_trigger_pipelines(), which walks the very same array. The trigger took the pipeline_state_mutex only after it fetched the first pipeline from the list, so a PCM free running in parallel - on unbind or topology removal - can free the array from under it. Take the pipeline_state_mutex in sof_ipc4_pcm_free() and move the guard in sof_ipc4_trigger_pipelines() before the first access to the pipeline list. Clear the count of the freed list as well, so that a trigger which was waiting for the mutex sees an empty list and returns. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-12-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-pcm.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 5929ecf6642e..d2a6c569845c 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -434,6 +434,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, spcm_dbg(spcm, substream->stream, "cmd: %d, state: %d\n", cmd, state); pipeline_list = &spcm->stream[substream->stream].pipeline_list; + guard(mutex)(&ipc4_data->pipeline_state_mutex); /* nothing to trigger if the list is empty */ if (!pipeline_list->pipelines || !pipeline_list->count) @@ -487,8 +488,6 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, return -ENOMEM; } - guard(mutex)(&ipc4_data->pipeline_state_mutex); - /* * IPC4 requires pipelines to be triggered in order starting at the sink and * walking all the way to the source. So traverse the pipeline_list in the order @@ -903,13 +902,17 @@ static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, static void sof_ipc4_pcm_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm) { struct snd_sof_pcm_stream_pipeline_list *pipeline_list; + struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct sof_ipc4_pcm_stream_priv *stream_priv; int stream; + guard(mutex)(&ipc4_data->pipeline_state_mutex); + for_each_pcm_streams(stream) { pipeline_list = &spcm->stream[stream].pipeline_list; kfree(pipeline_list->pipelines); pipeline_list->pipelines = NULL; + pipeline_list->count = 0; stream_priv = spcm->stream[stream].private; kfree(stream_priv->time_info); From 973851a710c12b14890b914c4f27fc58cb6a1b86 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:39 +0300 Subject: [PATCH 569/924] ASoC: SOF: sof-audio: do not dereference swidget->spipe unconditionally on free sof_widget_free_unlocked() dereferences swidget->spipe without checking it for two things: swidget->spipe->complete for a scheduler widget, and swidget->spipe->pipe_widget for the recursive free of the pipeline's scheduler widget. Both can be reached with spipe or pipe_widget not set, which oopses in the free path - where there is nothing left to bail out to. Check both before use and cache swidget->spipe in the local spipe variable that is already there. A widget with no pipeline has nothing to put or complete, and no scheduler widget to free, so skipping is the correct behaviour. No functional change for a widget that was successfully set up: sof_widget_setup_unlocked() already rejects a dynamic pipeline widget with no spipe or no spipe->pipe_widget with -EINVAL. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-13-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/sof-audio.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 850eb2bc2b44..9f9f18b3c935 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -103,7 +103,7 @@ static int sof_widget_free_unlocked(struct snd_sof_dev *sdev, * decrement ref count for cores associated with all modules in the pipeline and clear * the complete flag */ - if (swidget->id == snd_soc_dapm_scheduler) { + if (swidget->id == snd_soc_dapm_scheduler && spipe) { int i; for_each_set_bit(i, &spipe->core_mask, sdev->num_cores) { @@ -115,16 +115,16 @@ static int sof_widget_free_unlocked(struct snd_sof_dev *sdev, err = ret; } } - swidget->spipe->complete = 0; + spipe->complete = 0; } /* * free the scheduler widget (same as pipe_widget) associated with the current swidget. * skip for static pipelines */ - if (swidget->spipe && swidget->dynamic_pipeline_widget && + if (spipe && spipe->pipe_widget && swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) { - ret = sof_widget_free_unlocked(sdev, swidget->spipe->pipe_widget); + ret = sof_widget_free_unlocked(sdev, spipe->pipe_widget); if (ret < 0 && !err) err = ret; } From e1135ddec78841c100d3825582e3af7f1e1dc0a1 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 7 Oct 2026 11:49:40 +0300 Subject: [PATCH 570/924] ASoC: SOF: sof-audio: Expose a couple of functions These are common functions that will also be needed for the IPC4 compressed support. Signed-off-by: Ranjani Sridharan Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-14-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/pcm.c | 6 +++--- sound/soc/sof/sof-audio.h | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 941baa48b275..8f59c200b8ec 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -66,7 +66,7 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) } EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); -static int +int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd, struct snd_sof_pcm *spcm, struct snd_pcm_hw_params *params, struct snd_sof_platform_stream_params *platform_params, int dir) @@ -100,8 +100,8 @@ sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_run return 0; } -static struct snd_sof_widget *snd_sof_find_swidget_by_comp_id(struct snd_sof_dev *sdev, - int comp_id) +struct snd_sof_widget *snd_sof_find_swidget_by_comp_id(struct snd_sof_dev *sdev, + int comp_id) { struct snd_sof_widget *swidget; diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index bd5bbaf9ae44..1b3e66037584 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -636,7 +636,12 @@ struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp, int *direction); void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream); void snd_sof_pcm_init_elapsed_work(struct work_struct *work); - +int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd, + struct snd_sof_pcm *spcm, struct snd_pcm_hw_params *params, + struct snd_sof_platform_stream_params *platform_params, + int dir); +struct snd_sof_widget *snd_sof_find_swidget_by_comp_id(struct snd_sof_dev *sdev, + int comp_id); /* * snd_sof_pcm specific wrappers for dev_dbg() and dev_err() to provide * consistent and useful prints. From 9de51be869dc1339f42c9d91d55c2788bf7f3122 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 7 Oct 2026 11:49:41 +0300 Subject: [PATCH 571/924] ASoC: SOF: pcm: Modify the signature of a couple of PCM IPC ops In order to reuse the pipeline triggering logic for compressed support with IPC4, modify the signature of the trigger and hw_free PCM IPC ops so that they can be reused. Signed-off-by: Ranjani Sridharan Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-15-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc3-pcm.c | 26 +++++++--------------- sound/soc/sof/ipc4-pcm.c | 45 ++++++++++++++++++++------------------- sound/soc/sof/pcm.c | 7 +++--- sound/soc/sof/sof-audio.h | 7 +++--- 4 files changed, 39 insertions(+), 46 deletions(-) diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c index 1d0c80466f6a..42f93e596598 100644 --- a/sound/soc/sof/ipc3-pcm.c +++ b/sound/soc/sof/ipc3-pcm.c @@ -14,23 +14,18 @@ #include "sof-audio.h" static int sof_ipc3_pcm_hw_free(struct snd_soc_component *component, - struct snd_pcm_substream *substream) + struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int dir) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct sof_ipc_stream stream; - struct snd_sof_pcm *spcm; - spcm = snd_sof_find_spcm_dai(component, rtd); - if (!spcm) - return -EINVAL; - - if (!spcm->prepared[substream->stream]) + if (!spcm->prepared[dir]) return 0; stream.hdr.size = sizeof(stream); stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; - stream.comp_id = spcm->stream[substream->stream].comp_id; + stream.comp_id = spcm->stream[dir].comp_id; /* send IPC to the DSP */ return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream)); @@ -141,20 +136,15 @@ static int sof_ipc3_pcm_hw_params(struct snd_soc_component *component, } static int sof_ipc3_pcm_trigger(struct snd_soc_component *component, - struct snd_pcm_substream *substream, int cmd) + struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int cmd, int dir) { - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct sof_ipc_stream stream; - struct snd_sof_pcm *spcm; - - spcm = snd_sof_find_spcm_dai(component, rtd); - if (!spcm) - return -EINVAL; stream.hdr.size = sizeof(stream); stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG; - stream.comp_id = spcm->stream[substream->stream].comp_id; + stream.comp_id = spcm->stream[dir].comp_id; switch (cmd) { case SNDRV_PCM_TRIGGER_PAUSE_PUSH: @@ -172,7 +162,7 @@ static int sof_ipc3_pcm_trigger(struct snd_soc_component *component, stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP; break; default: - spcm_err(spcm, substream->stream, "Unhandled trigger cmd %d\n", cmd); + spcm_err(spcm, dir, "Unhandled trigger cmd %d\n", cmd); return -EINVAL; } diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index d2a6c569845c..d3add122ec00 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -412,28 +412,23 @@ static int sof_ipc4_chain_dma_trigger(struct snd_sof_dev *sdev, } static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, - struct snd_pcm_substream *substream, int state, int cmd) + struct snd_pcm_substream *substream, int state, int cmd, + struct snd_sof_pcm *spcm, int dir) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_sof_pcm_stream_pipeline_list *pipeline_list; struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct ipc4_pipeline_set_state_data *trigger_list; struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline; struct snd_sof_pipeline *spipe; - struct snd_sof_pcm *spcm; u8 *pipe_priority; int ret; int i; - spcm = snd_sof_find_spcm_dai(component, rtd); - if (!spcm) - return -EINVAL; + spcm_dbg(spcm, dir, "cmd: %d, state: %d\n", cmd, state); - spcm_dbg(spcm, substream->stream, "cmd: %d, state: %d\n", cmd, state); - - pipeline_list = &spcm->stream[substream->stream].pipeline_list; + pipeline_list = &spcm->stream[dir].pipeline_list; guard(mutex)(&ipc4_data->pipeline_state_mutex); /* nothing to trigger if the list is empty */ @@ -451,9 +446,9 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, if (pipeline->use_chain_dma) { struct sof_ipc4_timestamp_info *time_info; - time_info = sof_ipc4_sps_to_time_info(&spcm->stream[substream->stream]); + time_info = sof_ipc4_sps_to_time_info(&spcm->stream[dir]); - ret = sof_ipc4_chain_dma_trigger(sdev, spcm, substream->stream, + ret = sof_ipc4_chain_dma_trigger(sdev, spcm, dir, pipeline_list, state, cmd); if (ret || !time_info) return ret; @@ -462,12 +457,16 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, /* * Record the DAI position for delay reporting * To handle multiple pause/resume/xrun we need to add - * the positions to simulate how the firmware behaves + * the positions to simulate how the firmware behaves. + * Chained DMA does not support compress streams. We should + * never get here with compress. */ - u64 pos = snd_sof_pcm_get_dai_frame_counter(sdev, component, - substream); + if (substream) { + u64 pos = snd_sof_pcm_get_dai_frame_counter(sdev, component, + substream); - time_info->stream_end_offset += pos; + time_info->stream_end_offset += pos; + } } else if (state == SOF_IPC4_PIPE_RESET) { /* Reset the end offset as the stream is stopped */ time_info->stream_end_offset = 0; @@ -526,7 +525,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, */ ret = sof_ipc4_set_multi_pipeline_state(sdev, SOF_IPC4_PIPE_PAUSED, trigger_list); if (ret < 0) { - spcm_err(spcm, substream->stream, "failed to pause all pipelines\n"); + spcm_err(spcm, dir, "failed to pause all pipelines\n"); /* * workaround: if the firmware is crashed or the IPC timed out * while setting the pipeline state we must ignore the error @@ -557,7 +556,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, * Invalidate the stream_start_offset to make sure that it is * going to be updated if the stream resumes */ - time_info = sof_ipc4_sps_to_time_info(&spcm->stream[substream->stream]); + time_info = sof_ipc4_sps_to_time_info(&spcm->stream[dir]); if (time_info) time_info->stream_start_offset = SOF_IPC4_INVALID_STREAM_POSITION; @@ -567,7 +566,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, /* else set the RUNNING/RESET state in the DSP */ ret = sof_ipc4_set_multi_pipeline_state(sdev, state, trigger_list); if (ret < 0) { - spcm_err(spcm, substream->stream, + spcm_err(spcm, dir, "failed to set final state %d for all pipelines\n", state); /* @@ -598,7 +597,8 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, } static int sof_ipc4_pcm_trigger(struct snd_soc_component *component, - struct snd_pcm_substream *substream, int cmd) + struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int cmd, int dir) { int state; @@ -620,14 +620,15 @@ static int sof_ipc4_pcm_trigger(struct snd_soc_component *component, } /* set the pipeline state */ - return sof_ipc4_trigger_pipelines(component, substream, state, cmd); + return sof_ipc4_trigger_pipelines(component, substream, state, cmd, spcm, dir); } static int sof_ipc4_pcm_hw_free(struct snd_soc_component *component, - struct snd_pcm_substream *substream) + struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int dir) { /* command is not relevant with RESET, so just pass 0 */ - return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET, 0); + return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET, 0, spcm, dir); } static int ipc4_ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 8f59c200b8ec..42738f12fa33 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -152,7 +152,7 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, * between. At least ALSA OSS emulation depends on this. */ if (spcm->prepared[substream->stream] && pcm_ops && pcm_ops->hw_free) { - ret = pcm_ops->hw_free(component, substream); + ret = pcm_ops->hw_free(component, substream, spcm, substream->stream); if (ret < 0) return ret; @@ -223,7 +223,8 @@ static int sof_pcm_stream_free(struct snd_sof_dev *sdev, /* free PCM in the DSP */ if (pcm_ops && pcm_ops->hw_free) { - ret = pcm_ops->hw_free(sdev->component, substream); + ret = pcm_ops->hw_free(sdev->component, substream, spcm, + substream->stream); if (ret < 0) { spcm_err(spcm, substream->stream, "pcm_ops->hw_free failed %d\n", ret); @@ -458,7 +459,7 @@ static int sof_pcm_trigger(struct snd_soc_component *component, snd_sof_pcm_platform_trigger(sdev, substream, cmd); if (pcm_ops && pcm_ops->trigger) - ret = pcm_ops->trigger(component, substream, cmd); + ret = pcm_ops->trigger(component, substream, spcm, cmd, substream->stream); switch (cmd) { case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 1b3e66037584..0610f2ab844d 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -125,9 +125,10 @@ struct sof_ipc_pcm_ops { int (*hw_params)(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_sof_platform_stream_params *platform_params); - int (*hw_free)(struct snd_soc_component *component, struct snd_pcm_substream *substream); - int (*trigger)(struct snd_soc_component *component, struct snd_pcm_substream *substream, - int cmd); + int (*hw_free)(struct snd_soc_component *component, struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int dir); + int (*trigger)(struct snd_soc_component *component, struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int cmd, int dir); int (*dai_link_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); int (*pcm_setup)(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm); void (*pcm_free)(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm); From 2271de29a09706b0622459ec7c4e0f5afbbf34bf Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 7 Oct 2026 11:49:42 +0300 Subject: [PATCH 572/924] ASoC: SOF: intel: hda-stream: Clear the current position when releasing stream After the host DMA ID is released, reset the curr_pos to 0 for a clean start for subsequent stream starts. This is not needed for PCM streams but for compressed streams. Signed-off-by: Ranjani Sridharan Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-16-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda-stream.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c index c95230487a1b..5097a497188d 100644 --- a/sound/soc/sof/intel/hda-stream.c +++ b/sound/soc/sof/intel/hda-stream.c @@ -317,6 +317,7 @@ static int _hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stre if (s->direction == direction && s->stream_tag == stream_tag) { s->opened = false; found = true; + s->curr_pos = 0; if (pair) link_stream = hext_stream; } else if (!(hda_stream->flags & SOF_HDA_STREAM_DMI_L1_COMPATIBLE)) { From b7822e6cf3446df1ffba7bae6359a780019ca624 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:43 +0300 Subject: [PATCH 573/924] ASoC: SOF: ipc4: Add definition of module data in init_ext object type The object array of the MODULE_INIT message can carry different types of objects, identified by SOF_IPC4_MOD_INIT_EXT_OBJ_ID in the object header. Add the SOF_IPC4_MOD_INIT_DATA_ID_MODULE_DATA type to pass module specific initialization data to a module. Unlike the existing DP_DATA object, its content is opaque to the driver and its size is variable, defined by the module itself. It is going to be used to configure the decoder and encoder process modules. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-17-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- include/sound/sof/ipc4/header.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h index cea3d9453b84..e5161f0a37e8 100644 --- a/include/sound/sof/ipc4/header.h +++ b/include/sound/sof/ipc4/header.h @@ -706,7 +706,8 @@ struct sof_ipc4_module_init_ext_object { enum sof_ipc4_mod_init_ext_obj_id { SOF_IPC4_MOD_INIT_DATA_ID_INVALID = 0, SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA, - SOF_IPC4_MOD_INIT_DATA_ID_MAX = SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA, + SOF_IPC4_MOD_INIT_DATA_ID_MODULE_DATA, + SOF_IPC4_MOD_INIT_DATA_ID_MAX = SOF_IPC4_MOD_INIT_DATA_ID_MODULE_DATA, }; /* DP module memory configuration data object for object array */ From 73df6ceb831404438a904a85056a3aa8d4fd7676 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:44 +0300 Subject: [PATCH 574/924] ASoC: SOF: ipc4-topology: Support init_ext_module_data for process modules Add support for handling init_ext_module_data for process modules, which is going to be used by decoder and encoder type of process modules. The support is generic and it can be extended to other type of process modules or other module types than process with a small update of sof_ipc4_add_init_ext_module_data() function. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-18-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-topology.c | 99 +++++++++++++++++++++++------------ sound/soc/sof/ipc4-topology.h | 4 ++ sound/soc/sof/sof-audio.h | 1 + 3 files changed, 71 insertions(+), 33 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 24a1175dd690..42cf444e847e 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -3263,22 +3263,56 @@ static void sof_ipc4_add_init_ext_dp_memory_data(struct snd_sof_dev *sdev, u32 *payload, u32 *ext_pos, struct sof_ipc4_module_init_ext_object **hdr) { - /* Add memory_data if comp_domain indicates DP */ - if (swidget->comp_domain == SOF_COMP_DOMAIN_DP) { - struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; + struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; - *hdr = (struct sof_ipc4_module_init_ext_object *)&payload[*ext_pos]; - (*hdr)->header = - SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | - SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), - sizeof(u32))); - *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); - dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[*ext_pos]; - dp_mem_data->domain_id = swidget->domain_id; - dp_mem_data->stack_bytes = swidget->stack_bytes; - dp_mem_data->heap_bytes = swidget->heap_bytes; - *ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); + *hdr = (struct sof_ipc4_module_init_ext_object *)&payload[*ext_pos]; + (*hdr)->header = + SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | + SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), + sizeof(u32))); + *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); + dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[*ext_pos]; + dp_mem_data->domain_id = swidget->domain_id; + dp_mem_data->stack_bytes = swidget->stack_bytes; + dp_mem_data->heap_bytes = swidget->heap_bytes; + *ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); +} + +static int +sof_ipc4_add_init_ext_module_data(struct snd_sof_dev *sdev, + struct sof_ipc4_process *process, + u32 *payload, u32 *ext_pos, + struct sof_ipc4_module_init_ext_object **hdr) +{ + u32 data_size = process->init_ext_module_size; + void *data = process->init_ext_module_data; + size_t needed; + + /* + * Unlike the other objects, the module data is of variable size, + * provided by the module which is being set up. + * Make sure that the object fits into the payload buffer before any of + * it is written. + */ + needed = ((size_t)*ext_pos + DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)) + + DIV_ROUND_UP(data_size, sizeof(u32))) * sizeof(u32); + if (needed > sdev->ipc->max_payload_size) { + dev_err(sdev->dev, + "Max ipc payload size %zu exceeded by module data: %zu\n", + sdev->ipc->max_payload_size, needed); + return -EINVAL; } + + *hdr = (struct sof_ipc4_module_init_ext_object *)&payload[*ext_pos]; + (*hdr)->header = SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_MODULE_DATA) | + SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(data_size, sizeof(u32))); + *ext_pos += DIV_ROUND_UP(sizeof(*(*hdr)), sizeof(u32)); + + memcpy(&payload[*ext_pos], data, data_size); + + *ext_pos += DIV_ROUND_UP(data_size, sizeof(u32)); + + return 0; } static int sof_ipc4_widget_mod_init_msg_payload(struct snd_sof_dev *sdev, @@ -3287,17 +3321,17 @@ static int sof_ipc4_widget_mod_init_msg_payload(struct snd_sof_dev *sdev, void *ipc_data, u32 ipc_size, void **new_data) { - struct sof_ipc4_module_init_ext_init *ext_init; + struct sof_ipc4_process *process = swidget->private; struct sof_ipc4_module_init_ext_object *hdr = NULL; + struct sof_ipc4_module_init_ext_init *ext_init; + bool in_dp_domain = swidget->comp_domain == SOF_COMP_DOMAIN_DP; + bool has_ext_data = WIDGET_IS_PROCESS(swidget->id) && process->init_ext_module_size; int new_size; u32 *payload; u32 ext_pos; + int ret; - /* - * Only DP widgets currently add init-ext objects here. Avoid allocating - * a max-sized payload buffer for widgets that will immediately return 0. - */ - if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) + if (!in_dp_domain && !has_ext_data) return 0; payload = kzalloc(sdev->ipc->max_payload_size, GFP_KERNEL); @@ -3306,25 +3340,24 @@ static int sof_ipc4_widget_mod_init_msg_payload(struct snd_sof_dev *sdev, /* Add ext_init first and set objects array flag to 1 */ ext_init = (struct sof_ipc4_module_init_ext_init *)payload; + ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; ext_pos = DIV_ROUND_UP(sizeof(*ext_init), sizeof(u32)); /* Add object array objects after ext_init */ + if (in_dp_domain) + sof_ipc4_add_init_ext_dp_memory_data(sdev, swidget, payload, + &ext_pos, &hdr); - sof_ipc4_add_init_ext_dp_memory_data(sdev, swidget, payload, &ext_pos, &hdr); - - /* Add following object array items here */ - - if (!hdr) { - /* - * NOTE: Remove this early bail out, when struct - * sof_ipc4_module_init_ext_init alone has some - * function. - */ - kfree(payload); - return 0; + if (has_ext_data) { + ret = sof_ipc4_add_init_ext_module_data(sdev, process, payload, + &ext_pos, &hdr); + if (ret) { + kfree(payload); + return ret; + } } - ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; + /* Set last bit for the last object in the array */ hdr->header |= SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK; /* Calculate final size and check that it fits to max payload size */ diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index c9fe9f2e4083..5e78f3de092e 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -524,6 +524,8 @@ struct sof_ipc4_base_module_cfg_ext { * @msg: IPC4 message struct containing header and data info * @base_config_ext_size: Size of the base config extension data in bytes * @init_config: Module init config type (SOF_IPC4_MODULE_INIT_CONFIG_TYPE_*) + * @init_ext_module_data: module_data for init_ext object + * @init_ext_module_size: size of init_ext_module_data */ struct sof_ipc4_process { struct sof_ipc4_base_module_cfg base_config; @@ -535,6 +537,8 @@ struct sof_ipc4_process { struct sof_ipc4_msg msg; u32 base_config_ext_size; u32 init_config; + void *init_ext_module_data; + size_t init_ext_module_size; }; bool sof_ipc4_copier_is_single_bitdepth(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 0610f2ab844d..151cfa9e6b7c 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -43,6 +43,7 @@ #define WIDGET_IS_AIF(id) ((id) == snd_soc_dapm_aif_in || (id) == snd_soc_dapm_aif_out) #define WIDGET_IS_AIF_OR_DAI(id) (WIDGET_IS_DAI(id) || WIDGET_IS_AIF(id)) #define WIDGET_IS_COPIER(id) (WIDGET_IS_AIF_OR_DAI(id) || (id) == snd_soc_dapm_buffer) +#define WIDGET_IS_PROCESS(id) ((id) == snd_soc_dapm_effect) #define SOF_DAI_PARAM_INTEL_SSP_MCLK 0 #define SOF_DAI_PARAM_INTEL_SSP_BCLK 1 From 03f3ae9e892881161f9e6437efc210d725cf7f4c Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:45 +0300 Subject: [PATCH 575/924] ASoC: SOF: ipc4-pcm: Make the timestamp info usable outside of ipc4-pcm.c The support for compressed stream will also need to have access to the same information which is used for delay reporting for DAI data progression tracking. Make the necessary struct and functions to be available and premare them to be called without a valid substream. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-19-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-pcm.c | 40 +++++++++------------------------------ sound/soc/sof/ipc4-priv.h | 31 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index d3add122ec00..c565e0e12949 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -15,29 +15,6 @@ #include "ipc4-topology.h" #include "ipc4-fw-reg.h" -/** - * struct sof_ipc4_timestamp_info - IPC4 timestamp info - * @host_copier: the host copier of the pcm stream - * @dai_copier: the dai copier of the pcm stream - * @stream_start_offset: reported by fw in memory window (converted to - * frames at host_copier sampling rate) - * @stream_end_offset: reported by fw in memory window (converted to - * frames at host_copier sampling rate) - * @llp_offset: llp offset in memory window - * @delay: Calculated and stored in pointer callback. The stored value is - * returned in the delay callback. Expressed in frames at host copier - * sampling rate. - */ -struct sof_ipc4_timestamp_info { - struct sof_ipc4_copier *host_copier; - struct sof_ipc4_copier *dai_copier; - u64 stream_start_offset; - u64 stream_end_offset; - u32 llp_offset; - - snd_pcm_sframes_t delay; -}; - /** * struct sof_ipc4_pcm_stream_priv - IPC4 specific private data * @time_info: pointer to time info struct if it is supported, otherwise NULL @@ -61,7 +38,7 @@ struct sof_ipc4_pcm_stream_priv { #define DELAY_MAX (DELAY_BOUNDARY >> 1) -static inline struct sof_ipc4_timestamp_info * +struct sof_ipc4_timestamp_info * sof_ipc4_sps_to_time_info(struct snd_sof_pcm_stream *sps) { struct sof_ipc4_pcm_stream_priv *stream_priv = sps->private; @@ -979,7 +956,7 @@ static int sof_ipc4_pcm_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm return 0; } -static void sof_ipc4_build_time_info(struct snd_sof_dev *sdev, struct snd_sof_pcm_stream *sps) +void sof_ipc4_build_time_info(struct snd_sof_dev *sdev, struct snd_sof_pcm_stream *sps) { struct sof_ipc4_copier *host_copier = NULL; struct sof_ipc4_copier *dai_copier = NULL; @@ -1077,7 +1054,7 @@ static int sof_ipc4_pcm_hw_params(struct snd_soc_component *component, return 0; } -static u64 sof_ipc4_frames_dai_to_host(struct sof_ipc4_timestamp_info *time_info, u64 value) +u64 sof_ipc4_frames_dai_to_host(struct sof_ipc4_timestamp_info *time_info, u64 value) { u64 dai_rate, host_rate; @@ -1106,10 +1083,10 @@ static u64 sof_ipc4_frames_dai_to_host(struct sof_ipc4_timestamp_info *time_info return value; } -static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - struct snd_sof_pcm_stream *sps, - struct sof_ipc4_timestamp_info *time_info) +int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_sof_pcm_stream *sps, + struct sof_ipc4_timestamp_info *time_info) { struct sof_ipc4_copier *host_copier = time_info->host_copier; struct sof_ipc4_copier *dai_copier = time_info->dai_copier; @@ -1123,7 +1100,8 @@ static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev, if (host_copier->data.gtw_cfg.node_id == SOF_IPC4_INVALID_NODE_ID) { return -EINVAL; - } else if (host_copier->data.gtw_cfg.node_id == SOF_IPC4_CHAIN_DMA_NODE_ID) { + } else if (substream && + host_copier->data.gtw_cfg.node_id == SOF_IPC4_CHAIN_DMA_NODE_ID) { /* * While the firmware does not support time_info reporting for * streams using ChainDMA, it is granted that ChainDMA can only diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index a8cdf9bc750b..7c0861d63bef 100644 --- a/sound/soc/sof/ipc4-priv.h +++ b/sound/soc/sof/ipc4-priv.h @@ -98,6 +98,29 @@ struct sof_ipc4_fw_data { struct mutex pipeline_state_mutex; /* protect pipeline triggers, ref counts and states */ }; +/** + * struct sof_ipc4_timestamp_info - IPC4 timestamp info + * @host_copier: the host copier of the pcm stream + * @dai_copier: the dai copier of the pcm stream + * @stream_start_offset: reported by fw in memory window (converted to + * frames at host_copier sampling rate) + * @stream_end_offset: reported by fw in memory window (converted to + * frames at host_copier sampling rate) + * @llp_offset: llp offset in memory window + * @delay: Calculated and stored in pointer callback. The stored value is + * returned in the delay callback. Expressed in frames at host copier + * sampling rate. + */ +struct sof_ipc4_timestamp_info { + struct sof_ipc4_copier *host_copier; + struct sof_ipc4_copier *dai_copier; + u64 stream_start_offset; + u64 stream_end_offset; + u32 llp_offset; + + snd_pcm_sframes_t delay; +}; + extern const struct sof_ipc_fw_loader_ops ipc4_loader_ops; extern const struct sof_ipc_tplg_ops ipc4_tplg_ops; extern const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops; @@ -129,4 +152,12 @@ void sof_ipc4_mic_privacy_state_change(struct snd_sof_dev *sdev, bool state); enum sof_ipc4_pipeline_state; const char *sof_ipc4_pipeline_state_str(enum sof_ipc4_pipeline_state state); +struct sof_ipc4_timestamp_info *sof_ipc4_sps_to_time_info(struct snd_sof_pcm_stream *sps); +void sof_ipc4_build_time_info(struct snd_sof_dev *sdev, struct snd_sof_pcm_stream *sps); +int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_sof_pcm_stream *sps, + struct sof_ipc4_timestamp_info *time_info); +u64 sof_ipc4_frames_dai_to_host(struct sof_ipc4_timestamp_info *time_info, u64 value); + #endif From 9fdc525713f0c754b3c735067686f9ab6a118bd9 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:46 +0300 Subject: [PATCH 576/924] ASoC: SOF: ipc4/ipc4-loader: Add SOF_INFO and CODEC_INFO to fw_config_params SOF_INFO (id == 35) tuple holds tuple structured information about SOF features. The first entry in SOF_INFO is the SOF_CODEC_INFO (id == 0) which contains information about the supported codecs for decode/encode in the booted firmware. If present in the fw_config payload, make a copy of it and store it sof_ipc4_fw_data->codec_info to be used by the compressed code. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-20-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- include/sound/sof/ipc4/header.h | 18 ++++++++++------ sound/soc/sof/ipc4-loader.c | 37 +++++++++++++++++++++++++++++++++ sound/soc/sof/ipc4-priv.h | 4 ++++ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h index e5161f0a37e8..dd1e0d1cf0d8 100644 --- a/include/sound/sof/ipc4/header.h +++ b/include/sound/sof/ipc4/header.h @@ -436,12 +436,10 @@ enum sof_ipc4_fw_config_params { SOF_IPC4_FW_CFG_RESERVED, SOF_IPC4_FW_CFG_POWER_GATING_POLICY, SOF_IPC4_FW_CFG_ASSERT_MODE, - SOF_IPC4_FW_RESERVED1, - SOF_IPC4_FW_RESERVED2, - SOF_IPC4_FW_RESERVED3, - SOF_IPC4_FW_RESERVED4, - SOF_IPC4_FW_RESERVED5, - SOF_IPC4_FW_CONTEXT_SAVE + /* Reserved: 24 - 28 */ + SOF_IPC4_FW_CONTEXT_SAVE = 29, + /* Reserved: 30 - 34 */ + SOF_IPC4_FW_CFG_SOF_INFO = 35, }; struct sof_ipc4_fw_version { @@ -451,6 +449,14 @@ struct sof_ipc4_fw_version { uint16_t build; } __packed; +/* + * tuple based array for SOF specific information under SOF_IPC4_FW_CFG_SOF_INFO + * tuple of fw_config + */ +enum ipc4_fw_sof_info_params { + SOF_IPC4_SOF_CODEC_INFO, +}; + /* Payload data for SOF_IPC4_MOD_SET_DX */ struct sof_ipc4_dx_state_info { /* core(s) to apply the change */ diff --git a/sound/soc/sof/ipc4-loader.c b/sound/soc/sof/ipc4-loader.c index 07a78cb3c25c..2dff394f4128 100644 --- a/sound/soc/sof/ipc4-loader.c +++ b/sound/soc/sof/ipc4-loader.c @@ -408,6 +408,38 @@ static int sof_ipc4_validate_firmware(struct snd_sof_dev *sdev) return 0; } +static int sof_ipc4_query_sof_info(struct snd_sof_dev *sdev, + void *sof_info_data, u32 sof_info_size) +{ + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct sof_ipc4_tuple *tuple; + size_t offset = 0; + + /* + * No size validation is needed as the sof_info data is coming from + * firmware as part of the firmware config, it is guarantied to be + * correct. + */ + while (offset < sof_info_size) { + tuple = (struct sof_ipc4_tuple *)((u8 *)sof_info_data + offset); + + switch (tuple->type) { + case SOF_IPC4_SOF_CODEC_INFO: + ipc4_data->codec_info = devm_kmemdup(sdev->dev, tuple->value, + tuple->size, GFP_KERNEL); + if (!ipc4_data->codec_info) + return -ENOMEM; + break; + default: + break; + } + + offset += sizeof(*tuple) + tuple->size; + } + + return 0; +} + int sof_ipc4_query_fw_configuration(struct snd_sof_dev *sdev) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; @@ -483,6 +515,11 @@ int sof_ipc4_query_fw_configuration(struct snd_sof_dev *sdev) */ ipc4_data->libraries_restored = ipc4_data->fw_context_save; break; + case SOF_IPC4_FW_CFG_SOF_INFO: + ret = sof_ipc4_query_sof_info(sdev, tuple->value, tuple->size); + if (ret) + goto out; + break; default: break; } diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index 7c0861d63bef..07df6799c7a8 100644 --- a/sound/soc/sof/ipc4-priv.h +++ b/sound/soc/sof/ipc4-priv.h @@ -75,6 +75,8 @@ struct sof_ipc4_fw_library { * @fw_context_save: Firmware supports full context save and restore * @libraries_restored: The libraries have been retained during firmware boot * + * @codec_info: Information about the available codecs in booted firmware. The + * data is to be used by the code for compressed support. * @load_library: Callback function for platform dependent library loading * @pipeline_state_mutex: Mutex to protect pipeline triggers, ref counts, states and deletion */ @@ -91,6 +93,8 @@ struct sof_ipc4_fw_data { bool fw_context_save; bool libraries_restored; + void *codec_info; + int (*load_library)(struct snd_sof_dev *sdev, struct sof_ipc4_fw_library *fw_lib, bool reload); void (*intel_configure_mic_privacy)(struct snd_sof_dev *sdev, From 275203b2450de73c4e5a1ea6b060b63942a9fc20 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:47 +0300 Subject: [PATCH 577/924] ASoC: SOF: ipc4-pcm: Handle COMPR DRAIN triggers as EOS pipeline state The DRAIN trigger is received by a compr device when user space wrote all the data to the buffer and it is waiting for the decoding to be completed. Set the pipeline state to EOS in firmware so it can expect the stream to be stopping anytime soon. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-21-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-pcm.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index c565e0e12949..f65c2a804a36 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -153,7 +153,8 @@ sof_ipc4_add_pipeline_to_trigger_list(struct snd_sof_dev *sdev, int state, struct snd_sof_widget *pipe_widget = spipe->pipe_widget; struct sof_ipc4_pipeline *pipeline = pipe_widget->private; - if (pipeline->skip_during_fe_trigger && state != SOF_IPC4_PIPE_RESET) + if (pipeline->skip_during_fe_trigger && state != SOF_IPC4_PIPE_RESET && + state != SOF_IPC4_PIPE_EOS) return; switch (state) { @@ -173,7 +174,12 @@ sof_ipc4_add_pipeline_to_trigger_list(struct snd_sof_dev *sdev, int state, true); break; case SOF_IPC4_PIPE_PAUSED: - /* Pause the pipeline only when its started_count is 1 more than paused_count */ + case SOF_IPC4_PIPE_EOS: + /* + * Pause the pipeline only when its started_count is 1 more than + * paused_count. + * Same rule applies to EOS state. + */ if (spipe->paused_count == (spipe->started_count - 1)) sof_ipc4_add_pipeline_by_priority(trigger_list, pipe_widget, pipe_priority, true); @@ -492,8 +498,9 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, goto free; } - /* no need to pause before reset or before pause release */ - if (state == SOF_IPC4_PIPE_RESET || cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) + /* no need to pause before reset, EOS or before pause release */ + if (state == SOF_IPC4_PIPE_RESET || state == SOF_IPC4_PIPE_EOS || + cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) goto skip_pause_transition; /* @@ -591,6 +598,10 @@ static int sof_ipc4_pcm_trigger(struct snd_soc_component *component, case SNDRV_PCM_TRIGGER_STOP: state = SOF_IPC4_PIPE_PAUSED; break; + case SND_COMPR_TRIGGER_DRAIN: + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + state = SOF_IPC4_PIPE_EOS; + break; default: dev_err(component->dev, "%s: unhandled trigger cmd %d\n", __func__, cmd); return -EINVAL; From e58b93a5ff49c223b8bb939f487dc70f6a13caf7 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:48 +0300 Subject: [PATCH 578/924] ASoC: SOF: ipc4-topology: Set FAST_MODE for host copier in compr mode FAST_MODE allows the host DMA to work in opportunistic, free running mode, which matches with the bitstream nature of compressed devices. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-22-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-topology.c | 3 +++ sound/soc/sof/ipc4-topology.h | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 42cf444e847e..566913025cc7 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -744,6 +744,9 @@ static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) sps->dsp_max_burst_size_in_ms = 1; } + if (spcm->pcm.compress) + ipc4_copier->data.copier_feature_mask |= BIT(SOF_IPC4_COPIER_FAST_MODE); + skip_gtw_cfg: ipc4_copier->gtw_attr = kzalloc_obj(*ipc4_copier->gtw_attr); if (!ipc4_copier->gtw_attr) { diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index 5e78f3de092e..c2949ff979a5 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -222,6 +222,11 @@ struct sof_copier_gateway_cfg { uint32_t config_data[]; }; +/* bit definition in copier_feature_mask */ +enum sof_ipc4_copier_feature { + SOF_IPC4_COPIER_FAST_MODE = 0, /* free running mode of host copier */ +}; + /** * struct sof_ipc4_copier_data - IPC data for copier * @base_config: Base configuration including input audio format From c6b1d75d064603a1ad61ac18f616060096cb2644 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 7 Oct 2026 11:49:49 +0300 Subject: [PATCH 579/924] ASoC: SOF: ops: Add new platform-specific ops for compress Add new ops in the struct snd_sof_ops for platform-specific ops for compresssed streams. Also, define and set them for the HDA platforms. Signed-off-by: Ranjani Sridharan Co-developed-by: Peter Ujfalusi Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-23-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda-common-ops.c | 8 ++ sound/soc/sof/intel/hda-pcm.c | 142 +++++++++++++++++++++++++++ sound/soc/sof/intel/hda-stream.c | 69 +++++++++---- sound/soc/sof/intel/hda.h | 15 +++ sound/soc/sof/ops.h | 74 ++++++++++++++ sound/soc/sof/sof-priv.h | 12 +++ 6 files changed, 301 insertions(+), 19 deletions(-) diff --git a/sound/soc/sof/intel/hda-common-ops.c b/sound/soc/sof/intel/hda-common-ops.c index 746b426b1329..aa91f2b6fd65 100644 --- a/sound/soc/sof/intel/hda-common-ops.c +++ b/sound/soc/sof/intel/hda-common-ops.c @@ -57,6 +57,14 @@ const struct snd_sof_dsp_ops sof_hda_common_ops = { .pcm_pointer = hda_dsp_pcm_pointer, .pcm_ack = hda_dsp_pcm_ack, + .compr_open = hda_dsp_compr_open, + .compr_hw_params = hda_dsp_compr_hw_params, + .compr_hw_free = hda_dsp_stream_compr_hw_free, + .compr_close = hda_dsp_compr_close, + .compr_trigger = hda_dsp_compr_trigger, + .compr_pointer = hda_dsp_compr_pointer, + .compr_get_dai_frame_counter = hda_dsp_compr_get_stream_llp, + .get_dai_frame_counter = hda_dsp_get_stream_llp, .get_host_byte_counter = hda_dsp_get_stream_ldp, diff --git a/sound/soc/sof/intel/hda-pcm.c b/sound/soc/sof/intel/hda-pcm.c index 16a364072821..83efc9bdf418 100644 --- a/sound/soc/sof/intel/hda-pcm.c +++ b/sound/soc/sof/intel/hda-pcm.c @@ -151,6 +151,71 @@ int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, } EXPORT_SYMBOL_NS(hda_dsp_pcm_hw_params, "SND_SOC_SOF_INTEL_HDA_COMMON"); +int hda_dsp_compr_hw_params(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_sof_platform_stream_params *platform_params) +{ + struct hdac_stream *hstream = cstream->runtime->private_data; + struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream); + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct snd_dma_buffer *dmab; + u32 bits, rate; + int bps; + int ret; + + hstream->cstream = cstream; + dmab = cstream->runtime->dma_buffer_p; + + /* Use correct format based on the used codec */ + switch (params->codec.id) { + case SND_AUDIOCODEC_PCM: + bps = snd_pcm_format_physical_width((snd_pcm_format_t)params->codec.format); + break; + case SND_AUDIOCODEC_VORBIS: + bps = snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S16_LE); + break; + case SND_AUDIOCODEC_FLAC: + { + struct snd_dec_flac *dec_flac = ¶ms->codec.options.flac_d; + + if (dec_flac->sample_size == 16) + bps = snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S16_LE); + else + bps = snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32_LE); + break; + } + default: + bps = snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32_LE); + } + + if (bps < 0) + return bps; + bits = hda_dsp_get_bits(sdev, bps); + rate = hda_dsp_get_mult_div(sdev, params->codec.sample_rate); + + hstream->format_val = rate | bits | (params->codec.ch_out - 1); + hstream->bufsize = cstream->runtime->buffer_size; + hstream->period_bytes = cstream->runtime->fragment_size; + hstream->no_period_wakeup = false; + + /* params is not used so pass NULL */ + dmab = cstream->runtime->dma_buffer_p; + ret = hda_dsp_stream_hw_params(sdev, hext_stream, dmab, NULL); + if (ret < 0) { + dev_err(sdev->dev, "%s: hdac prepare failed: %d\n", __func__, ret); + return ret; + } + + if (hda) + platform_params->no_ipc_position = hda->no_ipc_position; + + platform_params->stream_tag = hstream->stream_tag; + + return 0; +} +EXPORT_SYMBOL_NS(hda_dsp_compr_hw_params, "SND_SOC_SOF_INTEL_HDA_COMMON"); + /* update SPIB register with appl position */ int hda_dsp_pcm_ack(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { @@ -184,6 +249,16 @@ int hda_dsp_pcm_trigger(struct snd_sof_dev *sdev, } EXPORT_SYMBOL_NS(hda_dsp_pcm_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON"); +int hda_dsp_compr_trigger(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, int cmd) +{ + struct hdac_stream *hstream = cstream->runtime->private_data; + struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream); + + return hda_dsp_stream_trigger(sdev, hext_stream, cmd); +} +EXPORT_SYMBOL_NS(hda_dsp_compr_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON"); + snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { @@ -216,6 +291,20 @@ snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, } EXPORT_SYMBOL_NS(hda_dsp_pcm_pointer, "SND_SOC_SOF_INTEL_HDA_COMMON"); +int hda_dsp_compr_pointer(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp) +{ + struct hdac_stream *hstream = cstream->runtime->private_data; + + /* hstream->curr_pos is updated when we receive the ioc */ + tstamp->copied_total = hstream->curr_pos; + + tstamp->byte_offset = hda_dsp_stream_get_position(hstream, cstream->direction, true); + + return 0; +} +EXPORT_SYMBOL_NS(hda_dsp_compr_pointer, "SND_SOC_SOF_INTEL_HDA_COMMON"); + int hda_dsp_pcm_open(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { @@ -342,6 +431,41 @@ int hda_dsp_pcm_open(struct snd_sof_dev *sdev, } EXPORT_SYMBOL_NS(hda_dsp_pcm_open, "SND_SOC_SOF_INTEL_HDA_COMMON"); +int hda_dsp_compr_open(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *scomp = sdev->component; + struct hdac_ext_stream *dsp_stream; + struct snd_sof_pcm *spcm; + int direction = cstream->direction; + + spcm = snd_sof_find_spcm_dai(scomp, rtd); + if (!spcm) { + dev_err(sdev->dev, "%s: can't find PCM with DAI ID %d\n", + __func__, rtd->dai_link->id); + return -EINVAL; + } + + dsp_stream = hda_dsp_stream_get(sdev, direction, 0); + if (!dsp_stream) { + dev_err(sdev->dev, "%s: no stream available\n", __func__); + return -ENODEV; + } + + /* binding compr stream to hda stream */ + cstream->runtime->private_data = &dsp_stream->hstream; + + /* + * Reset the llp cache values (they are used for LLP compensation in + * case the counter is not reset) + */ + dsp_stream->pplcllpl = 0; + dsp_stream->pplcllpu = 0; + + return 0; +} +EXPORT_SYMBOL_NS(hda_dsp_compr_open, "SND_SOC_SOF_INTEL_HDA_COMMON"); + int hda_dsp_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { @@ -361,3 +485,21 @@ int hda_dsp_pcm_close(struct snd_sof_dev *sdev, return 0; } EXPORT_SYMBOL_NS(hda_dsp_pcm_close, "SND_SOC_SOF_INTEL_HDA_COMMON"); + +int hda_dsp_compr_close(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream) +{ + struct hdac_stream *hstream = cstream->runtime->private_data; + int direction = cstream->direction; + int ret; + + ret = hda_dsp_stream_put(sdev, direction, hstream->stream_tag); + if (ret) + return -ENODEV; + + /* unbinding compress stream to hda stream */ + hstream->cstream = NULL; + cstream->runtime->private_data = NULL; + + return 0; +} +EXPORT_SYMBOL_NS(hda_dsp_compr_close, "SND_SOC_SOF_INTEL_HDA_COMMON"); diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c index 5097a497188d..ac2a62c4e41f 100644 --- a/sound/soc/sof/intel/hda-stream.c +++ b/sound/soc/sof/intel/hda-stream.c @@ -755,13 +755,12 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, return ret; } -int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream) +static int _hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, + struct hdac_stream *hstream) { - struct hdac_stream *hstream = substream->runtime->private_data; struct hdac_ext_stream *hext_stream = container_of(hstream, - struct hdac_ext_stream, - hstream); + struct hdac_ext_stream, + hstream); int ret; ret = hda_dsp_stream_reset(sdev, hstream); @@ -786,8 +785,21 @@ int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, return 0; } + +int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + return _hda_dsp_stream_hw_free(sdev, substream->runtime->private_data); +} EXPORT_SYMBOL_NS(hda_dsp_stream_hw_free, "SND_SOC_SOF_INTEL_HDA_COMMON"); +int hda_dsp_stream_compr_hw_free(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream) +{ + return _hda_dsp_stream_hw_free(sdev, cstream->runtime->private_data); +} +EXPORT_SYMBOL_NS(hda_dsp_stream_compr_hw_free, "SND_SOC_SOF_INTEL_HDA_COMMON"); + bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev) { struct hdac_bus *bus = sof_to_bus(sdev); @@ -1162,19 +1174,9 @@ EXPORT_SYMBOL_NS(hda_dsp_stream_get_position, "SND_SOC_SOF_INTEL_HDA_COMMON"); #define merge_u64(u32_u, u32_l) (((u64)(u32_u) << 32) | (u32_l)) -/** - * hda_dsp_get_stream_llp - Retrieve the LLP (Linear Link Position) of the stream - * @sdev: SOF device - * @component: ASoC component - * @substream: PCM substream - * - * Returns the raw Linear Link Position value - */ -u64 hda_dsp_get_stream_llp(struct snd_sof_dev *sdev, - struct snd_soc_component *component, - struct snd_pcm_substream *substream) +static u64 hda_dsp_get_llp(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime *rtd, int dir) { - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_pcm_runtime *be_rtd = NULL; struct hdac_ext_stream *hext_stream; struct snd_soc_dai *cpu_dai; @@ -1185,7 +1187,7 @@ u64 hda_dsp_get_stream_llp(struct snd_sof_dev *sdev, * The LLP needs to be read from the Link DMA used for this FE as it is * allowed to use any combination of Link and Host channels */ - for_each_dpcm_be(rtd, substream->stream, dpcm) { + for_each_dpcm_be(rtd, dir, dpcm) { if (dpcm->fe != rtd) continue; @@ -1199,7 +1201,7 @@ u64 hda_dsp_get_stream_llp(struct snd_sof_dev *sdev, if (!cpu_dai) return 0; - hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); + hext_stream = snd_soc_dai_dma_data_get(cpu_dai, dir); if (!hext_stream) return 0; @@ -1223,8 +1225,37 @@ u64 hda_dsp_get_stream_llp(struct snd_sof_dev *sdev, return merge_u64(llp_u, llp_l); } + +/** + * hda_dsp_get_stream_llp - Retrieve the LLP (Linear Link Position) of the stream + * @sdev: SOF device + * @component: ASoC component + * @substream: PCM substream + * + * Returns the raw Linear Link Position value + */ +u64 hda_dsp_get_stream_llp(struct snd_sof_dev *sdev, + struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + return hda_dsp_get_llp(sdev, snd_soc_substream_to_rtd(substream), + substream->stream); +} EXPORT_SYMBOL_NS(hda_dsp_get_stream_llp, "SND_SOC_SOF_INTEL_HDA_COMMON"); +/** + * hda_dsp_compr_get_stream_llp - Retrieve the LLP (Linear Link Position) of the stream + * @sdev: SOF device + * @cstream: Compress stream + * + * Returns the raw Linear Link Position value + */ +u64 hda_dsp_compr_get_stream_llp(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream) +{ + return hda_dsp_get_llp(sdev, cstream->private_data, cstream->direction); +} +EXPORT_SYMBOL_NS(hda_dsp_compr_get_stream_llp, "SND_SOC_SOF_INTEL_HDA_COMMON"); + /** * hda_dsp_get_stream_ldp - Retrieve the LDP (Linear DMA Position) of the stream * @sdev: SOF device diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 1609589929a1..701578ec35cf 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -683,6 +683,21 @@ snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); int hda_dsp_pcm_ack(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); +int hda_dsp_compr_open(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream); +int hda_dsp_compr_close(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream); +int hda_dsp_compr_hw_params(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_sof_platform_stream_params *platform_params); +int hda_dsp_stream_compr_hw_free(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream); +int hda_dsp_compr_trigger(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, int cmd); +int hda_dsp_compr_pointer(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp); +u64 hda_dsp_compr_get_stream_llp(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream); + /* * DSP Stream Operations. */ diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 72af1f4ff620..4afc92490fbe 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "sof-priv.h" @@ -448,6 +449,79 @@ snd_sof_pcm_platform_hw_params(struct snd_sof_dev *sdev, return 0; } +static inline int +snd_sof_compr_platform_open(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_open) + return sof_ops(sdev)->compr_open(sdev, cstream); + + return 0; +} + +/* disconnect pcm substream to a host stream */ +static inline int +snd_sof_compr_platform_close(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_close) + return sof_ops(sdev)->compr_close(sdev, cstream); + + return 0; +} + +/* host stream hw params */ +static inline int +snd_sof_compr_platform_hw_params(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_sof_platform_stream_params *platform_params) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_hw_params) + return sof_ops(sdev)->compr_hw_params(sdev, cstream, params, platform_params); + + return 0; +} + +static inline int +snd_sof_compr_platform_hw_free(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_hw_free) + return sof_ops(sdev)->compr_hw_free(sdev, cstream); + + return 0; +} + +static inline int +snd_sof_compr_platform_trigger(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, int cmd) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_trigger) + return sof_ops(sdev)->compr_trigger(sdev, cstream, cmd); + + return 0; +} + +static inline int +snd_sof_compr_platform_pointer(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_pointer) + return sof_ops(sdev)->compr_pointer(sdev, cstream, tstamp); + + return 0; +} + +static inline u64 +snd_sof_compr_get_dai_frame_counter(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_get_dai_frame_counter) + return sof_ops(sdev)->compr_get_dai_frame_counter(sdev, cstream); + + return 0; +} + /* host stream hw free */ static inline int snd_sof_pcm_platform_hw_free(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 38753b088fc1..560be58611c0 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -256,6 +256,18 @@ struct snd_sof_dsp_ops { /* pcm ack */ int (*pcm_ack)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); /* optional */ + int (*compr_open)(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream); + int (*compr_close)(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream); + int (*compr_hw_params)(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_sof_platform_stream_params *platform_params); + int (*compr_hw_free)(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream); + int (*compr_trigger)(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream, + int cmd); + int (*compr_pointer)(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp); + u64 (*compr_get_dai_frame_counter)(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream); /* * optional callback to retrieve the number of frames left/arrived from/to * the DSP on the DAI side (link/codec/DMIC/etc). From 8c3db31941f035b0347392cdb789e3edb3ab9d27 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:50 +0300 Subject: [PATCH 580/924] ASoC: SOF: Check that the audio buffer fits into the page table The page table describing the audio buffer for the firmware is a single PAGE_SIZE allocation, but snd_sof_create_page_table() writes the compressed PFNs into it without checking that they fit. The PFNs are stored in 2.5 bytes each and the last one is written with a 32 bit access, so with 4K pages the table can hold 1638 PFNs, which is a buffer of 6.4MB. A larger buffer corrupts memory past the end of the page table. The PCM path is not affected as the maximum buffer size is specified by the topology, but the compressed streams can ask for larger buffers. Pass the page table buffer to snd_sof_create_page_table() instead of only its address and verify that the buffer can be described by it. Signed-off-by: Peter Ujfalusi Link: https://patch.msgid.link/20261007084955.1256-24-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc3-dtrace.c | 2 +- sound/soc/sof/pcm.c | 2 +- sound/soc/sof/sof-audio.c | 2 +- sound/soc/sof/sof-utils.c | 12 ++++++++++-- sound/soc/sof/sof-utils.h | 11 ++++++++++- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/sound/soc/sof/ipc3-dtrace.c b/sound/soc/sof/ipc3-dtrace.c index 22053357731a..e8c998c09dc3 100644 --- a/sound/soc/sof/ipc3-dtrace.c +++ b/sound/soc/sof/ipc3-dtrace.c @@ -534,7 +534,7 @@ static int ipc3_dtrace_init(struct snd_sof_dev *sdev) /* create compressed page table for audio firmware */ ret = snd_sof_create_page_table(sdev->dev, &priv->dmatb, - priv->dmatp.area, priv->dmatb.bytes); + &priv->dmatp, priv->dmatb.bytes); if (ret < 0) goto table_err; diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 42738f12fa33..b4305dc43d61 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -194,7 +194,7 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream); ret = snd_sof_create_page_table(component->dev, dmab, - spcm->stream[substream->stream].page_table.area, + &spcm->stream[substream->stream].page_table, runtime->dma_bytes); if (ret < 0) return ret; diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 9f9f18b3c935..8cbeca471542 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -1132,6 +1132,6 @@ int snd_sof_compr_create_page_table(struct snd_soc_component *component, return -EINVAL; return snd_sof_create_page_table(component->dev, dmab, - spcm->stream[dir].page_table.area, size); + &spcm->stream[dir].page_table, size); } #endif diff --git a/sound/soc/sof/sof-utils.c b/sound/soc/sof/sof-utils.c index f70089317b8c..59dc10196958 100644 --- a/sound/soc/sof/sof-utils.c +++ b/sound/soc/sof/sof-utils.c @@ -24,12 +24,20 @@ int snd_sof_create_page_table(struct device *dev, struct snd_dma_buffer *dmab, - unsigned char *page_table, size_t size) + struct snd_dma_buffer *page_table, size_t size) { int i, pages; pages = snd_sgbuf_aligned_pages(size); + if (pages < 1 || page_table->bytes < sizeof(u32) || + pages > SOF_PAGE_TABLE_MAX_PFNS(page_table->bytes)) { + dev_err(dev, + "Can not store %d pages in a %zu bytes page table\n", + pages, page_table->bytes); + return -EINVAL; + } + dev_dbg(dev, "generating page table for %p size 0x%zx pages %d\n", dmab->area, size, pages); @@ -45,7 +53,7 @@ int snd_sof_create_page_table(struct device *dev, u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; u8 *pg_table; - pg_table = (u8 *)(page_table + idx); + pg_table = (u8 *)(page_table->area + idx); /* * pagetable compression: diff --git a/sound/soc/sof/sof-utils.h b/sound/soc/sof/sof-utils.h index 9ac6de9a6d6a..58e5822a63f0 100644 --- a/sound/soc/sof/sof-utils.h +++ b/sound/soc/sof/sof-utils.h @@ -12,8 +12,17 @@ struct snd_dma_buffer; struct device; +/* + * Number of PFNs which can be stored in a page table of @bytes size. + * The PFNs are compressed to 2.5 bytes each but they are written with 32 bit + * accesses, therefore the last PFN can reach up to 3 bytes past the space it + * needs for itself. + */ +#define SOF_PAGE_TABLE_MAX_PFNS(bytes) \ + (((((bytes) - sizeof(u32)) << 1) + 1) / 5 + 1) + int snd_sof_create_page_table(struct device *dev, struct snd_dma_buffer *dmab, - unsigned char *page_table, size_t size); + struct snd_dma_buffer *page_table, size_t size); #endif From 782a378dacdb0a4b33dbb8d4268608a70d9c564e Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:51 +0300 Subject: [PATCH 581/924] ASoC: SOF: Add support for IPC4 compressed Set and define the compressed ops for IPC4. The initial implementation supports basic features: PAUSE PUSH/RELEASE, DRAIN and progress reporting. Tested with PCM, MP3, AAC and VORBIS codec. Co-developed-by: Ranjani Sridharan Signed-off-by: Ranjani Sridharan Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-25-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/Makefile | 4 +- sound/soc/sof/ipc4-compress.c | 823 ++++++++++++++++++++++++++++++++++ sound/soc/sof/ipc4-pcm.c | 3 + sound/soc/sof/ipc4-priv.h | 10 + sound/soc/sof/sof-audio.h | 2 + 5 files changed, 840 insertions(+), 2 deletions(-) create mode 100644 sound/soc/sof/ipc4-compress.c diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 3d52f8c4d173..d5d31da5e937 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -8,10 +8,12 @@ snd-sof-y := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ ifneq ($(CONFIG_SND_SOC_SOF_IPC3),) snd-sof-y += ipc3.o ipc3-loader.o ipc3-topology.o ipc3-control.o ipc3-pcm.o\ ipc3-dtrace.o +snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += ipc3-compress.o endif ifneq ($(CONFIG_SND_SOC_SOF_IPC4),) snd-sof-y += ipc4.o ipc4-loader.o ipc4-topology.o ipc4-control.o ipc4-pcm.o\ ipc4-mtrace.o ipc4-telemetry.o +snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += ipc4-compress.o endif # SOF client support @@ -19,8 +21,6 @@ ifneq ($(CONFIG_SND_SOC_SOF_CLIENT),) snd-sof-y += sof-client.o endif -snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += ipc3-compress.o - snd-sof-pci-y := sof-pci-dev.o snd-sof-acpi-y := sof-acpi-dev.o snd-sof-of-y := sof-of-dev.o diff --git a/sound/soc/sof/ipc4-compress.c b/sound/soc/sof/ipc4-compress.c new file mode 100644 index 000000000000..97a4ec751905 --- /dev/null +++ b/sound/soc/sof/ipc4-compress.c @@ -0,0 +1,823 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright 2026 Intel Corporation. All rights reserved. +// +#include +#include +#include +#include +#include "sof-audio.h" +#include "sof-priv.h" +#include "sof-utils.h" +#include "ops.h" +#include "ipc4-priv.h" +#include "ipc4-topology.h" +#include "ipc4-fw-reg.h" + +/* Maximum processing size of the decoder/encoder is 2048 bytes */ +#define SOF_IPC4_COMPR_MAX_PROCESSING_SIZE (SZ_2K) + +#define SOF_IPC4_COMPR_MIN_FRAGMENTS 3 +#define SOF_IPC4_COMPR_MAX_FRAGMENT_SIZE (SZ_128K) +#define SOF_IPC4_COMPR_MAX_FRAGMENTS 64 +#define SOF_IPC4_COMPR_MIN_BUFFER_SIZE(min_size) ((min_size) * \ + SOF_IPC4_COMPR_MIN_FRAGMENTS) + +struct sof_ipc4_compr_init_data { + struct snd_codec codec; + u32 dir; +} __packed __aligned(4); + +static struct sof_ipc4_process * +sof_ipc4_compr_get_module(struct snd_sof_pcm *spcm, int dir) +{ + int id = dir ? snd_soc_dapm_encoder : snd_soc_dapm_decoder; + struct snd_sof_pcm_stream *sps = &spcm->stream[dir]; + struct snd_soc_dapm_widget *widget; + int i; + + /* Find the (first) compr module in path */ + for_each_dapm_widgets(sps->list, i, widget) { + struct snd_sof_widget *swidget = widget->dobj.private; + + if (!swidget) + continue; + + if (swidget->widget->id == id) + return swidget->private; + } + + return NULL; +} + +static u32 sof_ipc4_compr_calc_min_fragment_size(struct snd_sof_pcm_stream *sps) +{ + u32 host_buffer_estimate; + + /* Estimated host DMA buffer size based on stereo S32_LE, 48KHz */ + host_buffer_estimate = snd_pcm_format_size(SNDRV_PCM_FORMAT_S32_LE, 2 * 48); + host_buffer_estimate *= sps->dsp_max_burst_size_in_ms; + /* + * The minimum fragment size must not be smaller than the processing size + * or in case of deep buffer on host side, the host DMA buffer size. + */ + return max(SOF_IPC4_COMPR_MAX_PROCESSING_SIZE, host_buffer_estimate); +} + +/* The buffer must be described by the page table allocated for the stream */ +static u64 sof_ipc4_compr_calc_max_buffer_size(struct snd_sof_pcm_stream *sps) +{ + return (u64)SOF_PAGE_TABLE_MAX_PFNS(sps->page_table.bytes) * PAGE_SIZE; +} + +static int sof_ipc4_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *cstream) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_sof_pcm *spcm; + int dir, ret; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + dir = cstream->direction; + + if (spcm->stream[dir].cstream) + return -EBUSY; + + spcm_dbg(spcm, dir, "Entry: open\n"); + + ret = snd_sof_compr_platform_open(sdev, cstream); + if (ret < 0) { + spcm_err(spcm, dir, "platform compress open failed %d\n", ret); + return ret; + } + + spcm->stream[dir].cstream = cstream; + spcm->stream[dir].posn.host_posn = 0; + spcm->stream[dir].posn.dai_posn = 0; + spcm->prepared[dir] = false; + spcm->pending_stop[dir] = false; + spcm->compr_started[dir] = false; + + return 0; +} + +static int sof_ipc4_compr_stream_free(struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm, + struct snd_compr_stream *cstream) +{ + const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); + int dir = cstream->direction; + int ret = 0; + int err = 0; + + if (spcm->prepared[dir]) { + if (spcm->pending_stop[dir]) + pcm_ops->trigger(sdev->component, NULL, spcm, + SNDRV_PCM_TRIGGER_STOP, dir); + + snd_sof_compr_platform_trigger(sdev, cstream, + SNDRV_PCM_TRIGGER_STOP); + + err = pcm_ops->hw_free(sdev->component, NULL, spcm, dir); + if (err < 0) + spcm_err(spcm, dir, "pcm_ops->hw_free failed %d\n", err); + } + + spcm->prepared[dir] = false; + spcm->pending_stop[dir] = false; + spcm->compr_started[dir] = false; + spcm->stream[dir].cstream = NULL; + + /* reset the DMA */ + ret = snd_sof_compr_platform_hw_free(sdev, cstream); + if (ret < 0) { + spcm_err(spcm, dir, "platform hw free failed %d\n", ret); + if (!err) + err = ret; + } + + /* free widget list */ + ret = sof_widget_list_free(sdev, spcm, dir); + if (ret < 0 && err == 0) { + spcm_err(spcm, dir, "sof_widget_list_free failed %d\n", ret); + err = ret; + } + + return err; +} + +static int sof_ipc4_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *cstream) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_sof_pcm *spcm; + int ret, err; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + spcm_dbg(spcm, cstream->direction, "Entry: free\n"); + + ret = sof_ipc4_compr_stream_free(sdev, spcm, cstream); + + /* unprepare and free the list of DAPM widgets */ + sof_widget_list_unprepare(sdev, spcm, cstream->direction); + + cancel_work_sync(&spcm->stream[cstream->direction].period_elapsed_work); + + snd_compr_free_pages(cstream); + + err = snd_sof_compr_platform_close(sdev, cstream); + if (err < 0) { + spcm_err(spcm, cstream->direction, + "platform compress close failed %d\n", err); + if (!ret) + ret = err; + } + + return ret; +} + +#define SOF_IPC4_CODEC_INFO_GET_ID(value) ((value) & 0xff) +#define SOF_IPC4_CODEC_INFO_GET_DIR(value) (((value) >> 8) & 0xf) + +struct sof_ipc4_codec_info_data { + u32 count; + u32 items[]; +} __packed __aligned(4); + +static int sof_ipc4_compr_get_caps(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct sof_ipc4_codec_info_data *codec_info = ipc4_data->codec_info; + int dir = cstream->direction; + u64 max_buffer_size; + u64 max_fragments; + struct snd_sof_pcm *spcm; + int i; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + /* No compress support available in booted firmware */ + if (!codec_info || !codec_info->count) { + spcm_err(spcm, dir, + "Compress is not supported (no codecs available)\n"); + return -EINVAL; + } + + for (i = 0; i < codec_info->count; i++) { + int _dir = SOF_IPC4_CODEC_INFO_GET_DIR(codec_info->items[i]); + + if (_dir == dir) { + int id = SOF_IPC4_CODEC_INFO_GET_ID(codec_info->items[i]); + + if (caps->num_codecs < ARRAY_SIZE(caps->codecs)) { + spcm_dbg(spcm, dir, "codec#%d: %d\n", + caps->num_codecs, id); + caps->codecs[caps->num_codecs++] = id; + } else { + spcm_dbg(spcm, dir, "codec#%d: %d ignored\n", + caps->num_codecs, id); + } + } + } + + caps->direction = dir; + caps->min_fragment_size = + sof_ipc4_compr_calc_min_fragment_size(&spcm->stream[dir]); + caps->max_fragment_size = SOF_IPC4_COMPR_MAX_FRAGMENT_SIZE; + if (caps->max_fragment_size < caps->min_fragment_size) + caps->max_fragment_size = caps->min_fragment_size; + + caps->min_fragments = SOF_IPC4_COMPR_MIN_FRAGMENTS; + caps->max_fragments = SOF_IPC4_COMPR_MAX_FRAGMENTS; + /* + * A buffer of max_fragments number of max_fragment_size fragments must + * be describable by the page table allocated for the stream + */ + max_buffer_size = sof_ipc4_compr_calc_max_buffer_size(&spcm->stream[dir]); + max_fragments = div_u64(max_buffer_size, caps->max_fragment_size); + if (caps->max_fragments > max_fragments) + caps->max_fragments = max_fragments; + + spcm_dbg(spcm, dir, + "num_codecs: %u, fragment_size: %u-%u, fragments: %u-%u\n", + caps->num_codecs, + caps->min_fragment_size, caps->max_fragment_size, + caps->min_fragments, caps->max_fragments); + return 0; +} + +static int sof_ipc4_compr_alloc_pages(struct device *dev, + struct snd_sof_pcm_stream *sps, + struct snd_soc_component *component, + struct snd_compr_stream *cstream) +{ + u32 min_fragment_size = sof_ipc4_compr_calc_min_fragment_size(sps); + u64 max_buffer_size = sof_ipc4_compr_calc_max_buffer_size(sps); + struct snd_compr_runtime *crtd = cstream->runtime; + u64 fragments = crtd->buffer_size; + int ret; + + if (crtd->buffer_size < SOF_IPC4_COMPR_MIN_BUFFER_SIZE(min_fragment_size)) { + dev_err(dev, "%s: Buffer size is too small: %llu (minimum is %u)\n", + __func__, crtd->buffer_size, + SOF_IPC4_COMPR_MIN_BUFFER_SIZE(min_fragment_size)); + return -EINVAL; + } + + if (crtd->buffer_size > max_buffer_size) { + dev_err(dev, "%s: Buffer size is too big: %llu (maximum is %llu)\n", + __func__, crtd->buffer_size, max_buffer_size); + return -EINVAL; + } + + if (crtd->fragment_size < min_fragment_size) { + dev_err(dev, "%s: Fragment size is too small: %u (minimum is %u)\n", + __func__, crtd->fragment_size, min_fragment_size); + return -EINVAL; + } + + do_div(fragments, crtd->fragment_size); + if (fragments < SOF_IPC4_COMPR_MIN_FRAGMENTS || + fragments > SOF_IPC4_COMPR_MAX_FRAGMENTS) { + dev_err(dev, + "%s: Incorrect number of fragments: %llu (valid: %d-%d)\n", + __func__, fragments, SOF_IPC4_COMPR_MIN_FRAGMENTS, + SOF_IPC4_COMPR_MAX_FRAGMENTS); + return -EINVAL; + } + + cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; + cstream->dma_buffer.dev.dev = dev; + + ret = snd_compr_malloc_pages(cstream, crtd->buffer_size); + if (ret < 0) + return ret; + + ret = snd_sof_compr_create_page_table(component, cstream, crtd->dma_area, + crtd->dma_bytes); + if (ret < 0) + snd_compr_free_pages(cstream); + + return ret; +} + +static bool +sof_ipc4_compr_codec_supported(struct snd_sof_dev *sdev, int codec_id, int dir) +{ + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct sof_ipc4_codec_info_data *codec_info = ipc4_data->codec_info; + int i; + + /* No compress support available in booted firmware */ + if (!codec_info || !codec_info->count) + return false; + + for (i = 0; i < codec_info->count; i++) { + int _dir = SOF_IPC4_CODEC_INFO_GET_DIR(codec_info->items[i]); + int _id = SOF_IPC4_CODEC_INFO_GET_ID(codec_info->items[i]); + + if (_dir == dir && codec_id == _id) + return true; + } + + return false; +} + +static int sof_ipc4_compr_set_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); + struct sof_ipc4_compr_init_data *compr_data __free(kfree) = NULL; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_sof_platform_stream_params *platform_params; + struct sof_ipc4_timestamp_info *time_info; + struct snd_compr_params *compr_params; + struct snd_sof_widget *host_swidget; + struct sof_ipc4_process *process; + struct snd_pcm_hw_params p = {0}; + struct snd_interval *interval; + struct snd_sof_pcm *spcm; + struct snd_mask *fmt; + int dir = cstream->direction; + int ret; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + host_swidget = snd_sof_find_swidget_by_comp_id(sdev, spcm->stream[dir].comp_id); + if (!host_swidget) { + spcm_err(spcm, dir, "failed to find host widget with comp_id %d\n", + spcm->stream[dir].comp_id); + return -ENODEV; + } + + if (!sof_ipc4_compr_codec_supported(sdev, params->codec.id, dir)) { + spcm_err(spcm, dir, "Unsupported codec id: %u\n", params->codec.id); + return -EINVAL; + } + + spcm_dbg(spcm, dir, + "codec_id: %u, rate: %u, ch in/out: %u/%u, format: %u/%u\n", + params->codec.id, params->codec.sample_rate, params->codec.ch_in, + params->codec.ch_out, params->codec.format, params->codec.pcm_format); + + if (spcm->prepared[dir]) { + /* + * This can only happen if user space re-configures the device + * without closing it, for example after DRAIN completion + */ + ret = sof_ipc4_compr_stream_free(sdev, spcm, cstream); + if (ret) + return ret; + } + + /* save the compress params */ + compr_params = &spcm->cparams[dir]; + memcpy(compr_params, params, sizeof(*params)); + + /* + * Force format, rate and channels and use PCM hw_params structure to + * set up the pipelines. + */ + fmt = hw_param_mask(&p, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(fmt); + /* Use correct format based on the used codec */ + switch (params->codec.id) { + case SND_AUDIOCODEC_PCM: + /* The format is provided by user space, it must be validated */ + if (snd_pcm_format_width((snd_pcm_format_t)params->codec.format) <= 0) { + spcm_err(spcm, dir, "Invalid PCM format: %u\n", + params->codec.format); + return -EINVAL; + } + snd_mask_set_format(fmt, (snd_pcm_format_t)params->codec.format); + break; + case SND_AUDIOCODEC_VORBIS: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + break; + case SND_AUDIOCODEC_FLAC: + { + struct snd_dec_flac *dec_flac = ¶ms->codec.options.flac_d; + + if (dec_flac->sample_size == 16) + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + else + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + break; + } + default: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + } + + interval = hw_param_interval(&p, SNDRV_PCM_HW_PARAM_CHANNELS); + interval->min = compr_params->codec.ch_out; + interval->max = compr_params->codec.ch_out; + + interval = hw_param_interval(&p, SNDRV_PCM_HW_PARAM_RATE); + interval->min = compr_params->codec.sample_rate; + interval->max = compr_params->codec.sample_rate; + + ret = sof_ipc4_compr_alloc_pages(sdev->dev, &spcm->stream[dir], + component, cstream); + if (ret < 0) + return ret; + + spcm_dbg(spcm, dir, + "buffer_size: %llu, fragment_size: %u (fragments: %u)\n", + cstream->runtime->buffer_size, cstream->runtime->fragment_size, + (u32)cstream->runtime->buffer_size / cstream->runtime->fragment_size); + + interval = hw_param_interval(&p, SNDRV_PCM_HW_PARAM_PERIOD_BYTES); + interval->min = cstream->runtime->fragment_size; + interval->max = cstream->runtime->fragment_size; + + interval = hw_param_interval(&p, SNDRV_PCM_HW_PARAM_BUFFER_BYTES); + interval->min = cstream->runtime->buffer_size; + interval->max = cstream->runtime->buffer_size; + + platform_params = &spcm->platform_params[dir]; + ret = snd_sof_compr_platform_hw_params(sdev, cstream, compr_params, + platform_params); + if (ret < 0) { + spcm_err(spcm, dir, "platform compress hw params failed\n"); + goto free_pages; + } + + /* set up the list of DAPM widgets if not already done */ + if (!spcm->stream[dir].list) { + ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, &p, + platform_params, dir); + if (ret < 0) + goto free_pages; + } + + process = sof_ipc4_compr_get_module(spcm, dir); + if (!process) { + ret = -EINVAL; + goto free_list; + } + + compr_data = kzalloc(sizeof(*compr_data), GFP_KERNEL); + if (!compr_data) { + ret = -ENOMEM; + goto free_list; + } + + memcpy(&compr_data->codec, &compr_params->codec, sizeof(compr_data->codec)); + compr_data->dir = dir; + + process->init_ext_module_data = compr_data; + process->init_ext_module_size = sizeof(*compr_data); + + /* + * Make sure that the DSP is booted up, which might not be the + * case if the on-demand DSP boot is used + */ + ret = snd_sof_boot_dsp_firmware(sdev); + if (ret) + goto clear_init_ext; + + /* set the host DMA ID */ + if (tplg_ops && tplg_ops->host_config) + tplg_ops->host_config(sdev, host_swidget, platform_params); + + /* set up the widgets and pipelines in the DSP */ + ret = sof_widget_list_setup(sdev, spcm, &p, platform_params, dir); + if (ret < 0) { + spcm_err(spcm, dir, "widget list set up failed\n"); + goto clear_init_ext; + } + + memcpy(&spcm->params[dir], &p, sizeof(p)); + spcm->prepared[dir] = true; + + time_info = sof_ipc4_sps_to_time_info(&spcm->stream[dir]); + if (time_info) { + /* delay calculation supported */ + time_info->stream_start_offset = SOF_IPC4_INVALID_STREAM_POSITION; + time_info->llp_offset = 0; + + sof_ipc4_build_time_info(sdev, &spcm->stream[dir]); + } + + process->init_ext_module_data = NULL; + process->init_ext_module_size = 0; + + return 0; + +clear_init_ext: + process->init_ext_module_data = NULL; + process->init_ext_module_size = 0; + +free_list: + sof_widget_list_unprepare(sdev, spcm, dir); + +free_pages: + snd_compr_free_pages(cstream); + + return ret; +} + +static int sof_ipc4_compr_get_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_codec *params) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_sof_pcm *spcm; + /* TODO: we don't query the supported codecs for now, if the + * application asks for an unsupported codec the set_params() will fail. + */ + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) { + dev_err(sdev->dev, "%s: can't find spcm\n", __func__); + return -EINVAL; + } + + spcm_dbg(spcm, cstream->direction, "Entry: get_params\n"); + + memcpy(params, &spcm->cparams[cstream->direction].codec, + sizeof(*params)); + + return 0; +} + +static int sof_ipc4_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *cstream, int cmd) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); + struct snd_sof_pcm *spcm; + int dir = cstream->direction; + bool trigger_platform = false; + int ret = 0; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) { + dev_err(sdev->dev, "%s: can't find spcm\n", __func__); + return -EINVAL; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* + * The host DMA is left running on stop and drain to match the + * firmware, which only pauses the pipeline, and it is only + * stopped during hw_free. A stream restarted after stop or + * drain - which the compress core allows for capture - would + * continue from a position the two sides no longer agree on. + */ + if (spcm->compr_started[dir]) { + spcm_err(spcm, dir, "Stream restart is not supported\n"); + return -EINVAL; + } + fallthrough; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spcm->pending_stop[dir] = false; + trigger_platform = true; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spcm->pending_stop[dir] = false; + break; + case SND_COMPR_TRIGGER_DRAIN: + spcm->pending_stop[dir] = true; + break; + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + case SND_COMPR_TRIGGER_NEXT_TRACK: + /* Gapless playback is not supported */ + spcm_dbg(spcm, dir, "Unsupported trigger cmd: %d\n", cmd); + return -EOPNOTSUPP; + default: + spcm_dbg(spcm, dir, "Unhandled trigger cmd: %d\n", cmd); + return 0; + } + + spcm_dbg(spcm, dir, "Entry: trigger (cmd: %d)\n", cmd); + + ret = pcm_ops->trigger(component, NULL, spcm, cmd, dir); + if (ret < 0) { + spcm_err(spcm, dir, "pcm_ops->trigger failed for cmd %d\n", cmd); + return ret; + } + + if (!ret && trigger_platform) { + ret = snd_sof_compr_platform_trigger(sdev, cstream, cmd); + if (ret < 0) + spcm_err(spcm, dir, + "platform compress trigger start failed %d\n", + ret); + } + + if (!ret && cmd == SNDRV_PCM_TRIGGER_START) + spcm->compr_started[dir] = true; + + return ret; +} + +static int sof_ipc4_compr_copy_playback(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *crtd = cstream->runtime; + u64 offset, n; + void *ptr; + int ret; + + div64_u64_rem(crtd->total_bytes_available, crtd->buffer_size, &offset); + ptr = crtd->dma_area + offset; + n = crtd->buffer_size - offset; + + if (count < n) { + ret = copy_from_user(ptr, buf, count); + } else { + ret = copy_from_user(ptr, buf, n); + ret += copy_from_user(crtd->dma_area, buf + n, count - n); + } + + return count - ret; +} + +static int sof_ipc4_compr_copy_capture(struct snd_compr_runtime *crtd, + char __user *buf, size_t count) +{ + u64 offset, n; + void *ptr; + int ret; + + div64_u64_rem(crtd->total_bytes_transferred, crtd->buffer_size, &offset); + ptr = crtd->dma_area + offset; + n = crtd->buffer_size - offset; + + if (count < n) { + ret = copy_to_user(buf, ptr, count); + } else { + ret = copy_to_user(buf, ptr, n); + ret += copy_to_user(buf + n, crtd->dma_area, count - n); + } + + return count - ret; +} + +static int sof_ipc4_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *crtd = cstream->runtime; + + if (count > crtd->buffer_size) + count = crtd->buffer_size; + + if (cstream->direction == SND_COMPRESS_PLAYBACK) + return sof_ipc4_compr_copy_playback(component, cstream, buf, count); + + return sof_ipc4_compr_copy_capture(crtd, buf, count); +} + +static int sof_ipc4_compr_pointer(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct sof_ipc4_timestamp_info *time_info; + struct snd_pcm_hw_params *params; + struct snd_sof_pcm_stream *sps; + struct snd_sof_pcm *spcm; + u64 dai_cnt = 0; + int ret; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + params = &spcm->params[cstream->direction]; + + ret = snd_sof_compr_platform_pointer(sdev, cstream, tstamp); + if (ret < 0) { + spcm_err(spcm, cstream->direction, + "platform compress pointer failed %d\n", ret); + return ret; + } + + sps = &spcm->stream[cstream->direction]; + time_info = sof_ipc4_sps_to_time_info(sps); + if (!time_info) + goto host_only; + + /* + * stream_start_offset is updated to memory window by FW based on + * pipeline statistics and it may be invalid if host query happens before + * the statistics is complete. And it will not change after the first + * initialization. + */ + if (time_info->stream_start_offset == SOF_IPC4_INVALID_STREAM_POSITION) { + ret = sof_ipc4_get_stream_start_offset(sdev, NULL, sps, time_info); + if (ret < 0) + goto host_only; + } + + if (!time_info->llp_offset) { + dai_cnt = snd_sof_compr_get_dai_frame_counter(sdev, cstream); + } else { + struct sof_ipc4_llp_reading_slot llp; + + sof_mailbox_read(sdev, time_info->llp_offset, &llp, sizeof(llp)); + dai_cnt = ((u64)llp.reading.llp_u << 32) | llp.reading.llp_l; + } + + if (dai_cnt) { + dai_cnt = sof_ipc4_frames_dai_to_host(time_info, dai_cnt); + dai_cnt += time_info->stream_end_offset; + if (dai_cnt < time_info->stream_start_offset) + dai_cnt = 0; + else + dai_cnt -= time_info->stream_start_offset; + } + +host_only: + tstamp->sampling_rate = params_rate(params); + tstamp->pcm_io_frames = dai_cnt; + + return 0; +} + +void sof_ipc4_compr_drain_done(struct snd_sof_dev *sdev, void *ipc_message) +{ + struct sof_ipc4_msg *ipc4_msg = ipc_message; + struct sof_ipc4_notify_module_data *ndata = ipc4_msg->data_ptr; + struct snd_sof_widget *swidget, *host_swidget; + bool widget_found = false; + struct snd_sof_pcm *spcm; + int dir; + + /* Find the swidget based on ndata->module_id and ndata->instance_id */ + swidget = sof_ipc4_find_swidget_by_ids(sdev, ndata->module_id, + ndata->instance_id); + if (!swidget) { + dev_err(sdev->dev, "%s: Failed to find widget for module %u.%u\n", + __func__, ndata->module_id, ndata->instance_id); + return; + } + + if (!swidget->spipe || !swidget->spipe->pipe_widget) + return; + + /* Find the swidget of the host copier on the same pipeline */ + list_for_each_entry(host_swidget, &sdev->widget_list, list) { + if (WIDGET_IS_AIF(host_swidget->id) && + host_swidget->pipeline_id == swidget->pipeline_id) { + widget_found = true; + break; + } + } + + if (!widget_found) { + dev_err(sdev->dev, "%s: Host widget not found for pipeline: %s\n", + __func__, swidget->spipe->pipe_widget->widget->name); + return; + } + + /* Look up the spcm of the host copier */ + spcm = snd_sof_find_spcm_comp(sdev->component, host_swidget->comp_id, &dir); + if (!spcm) { + dev_err(sdev->dev, "%s: Stream cannot be found for %s\n", __func__, + host_swidget->widget->name); + return; + } + + spcm_dbg(spcm, dir, "Entry: EOS done\n"); + + if (spcm->stream[dir].cstream) + snd_compr_drain_notify(spcm->stream[dir].cstream); +} + +const struct snd_compress_ops sof_ipc4_compressed_ops = { + .open = sof_ipc4_compr_open, + .free = sof_ipc4_compr_free, + .get_caps = sof_ipc4_compr_get_caps, + .set_params = sof_ipc4_compr_set_params, + .get_params = sof_ipc4_compr_get_params, + .trigger = sof_ipc4_compr_trigger, + .pointer = sof_ipc4_compr_pointer, + .copy = sof_ipc4_compr_copy, +}; diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index f65c2a804a36..cbf948d84791 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -1325,4 +1325,7 @@ const struct sof_ipc_pcm_ops ipc4_pcm_ops = { .delay = sof_ipc4_pcm_delay, .ipc_first_on_start = true, .platform_stop_during_hw_free = true, +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) + .compress_ops = &sof_ipc4_compressed_ops, +#endif }; diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index 07df6799c7a8..b35930c16c6d 100644 --- a/sound/soc/sof/ipc4-priv.h +++ b/sound/soc/sof/ipc4-priv.h @@ -130,6 +130,9 @@ extern const struct sof_ipc_tplg_ops ipc4_tplg_ops; extern const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops; extern const struct sof_ipc_pcm_ops ipc4_pcm_ops; extern const struct sof_ipc_fw_tracing_ops ipc4_mtrace_ops; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) +extern const struct snd_compress_ops sof_ipc4_compressed_ops; +#endif int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 instance_id, u32 state); int sof_ipc4_mtrace_update_pos(struct snd_sof_dev *sdev, int core); @@ -164,4 +167,11 @@ int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev, struct sof_ipc4_timestamp_info *time_info); u64 sof_ipc4_frames_dai_to_host(struct sof_ipc4_timestamp_info *time_info, u64 value); +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) +void sof_ipc4_compr_drain_done(struct snd_sof_dev *sdev, void *ipc_message); +#else +static inline void sof_ipc4_compr_drain_done(struct snd_sof_dev *sdev, + void *ipc_message) { } +#endif + #endif diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 151cfa9e6b7c..4ef72e680390 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -358,10 +358,12 @@ struct snd_sof_pcm { struct snd_sof_pcm_stream stream[2]; struct list_head list; /* list in sdev pcm list */ struct snd_pcm_hw_params params[2]; + struct snd_compr_params cparams[2]; /* applicable for compress devices */ struct snd_sof_platform_stream_params platform_params[2]; bool prepared[2]; /* PCM_PARAMS set successfully */ bool setup_done[2]; /* the setup of the SOF PCM device is done */ bool pending_stop[2]; /* only used if (!pcm_ops->platform_stop_during_hw_free) */ + bool compr_started[2]; /* compress stream has been started */ /* Must be last - ends in a flex-array member. */ struct snd_soc_tplg_pcm pcm; From 176f4f893401ad68ab1f1cc65109861bc5f867d4 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:52 +0300 Subject: [PATCH 582/924] ASoC: SOF: ipc4: Handle compressed drain done notification from firmware The decoder module sends a drain done notification when the last chunk of the stream after the EOS from host has been decoded. The notification is a module notification with 0xc0c0 as magic number in event_id upper 16 bit. Call sof_ipc4_compr_drain_done() when the notification arrives to handle it. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-26-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- include/sound/sof/ipc4/header.h | 3 ++- sound/soc/sof/ipc4.c | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h index dd1e0d1cf0d8..c0216f4b14c2 100644 --- a/include/sound/sof/ipc4/header.h +++ b/include/sound/sof/ipc4/header.h @@ -647,9 +647,10 @@ struct sof_ipc4_notify_module_data { * The event_data contains the struct sof_ipc4_control_msg_payload of the control * which sent the notification. */ -#define SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_MASK GENMASK(31, 16) +#define SOF_IPC4_NOTIFY_MODULE_EVENTID_SOF_MAGIC_MASK GENMASK(31, 16) #define SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL 0xA15A0000 #define SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_PARAMID_MASK GENMASK(15, 0) +#define SOF_IPC4_NOTIFY_MODULE_EVENTID_COMPR_MAGIC_VAL 0xC0C00000 /* * Macros for creating struct sof_ipc4_module_init_ext_init payload diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c index e77f73390bd8..b9dd798e59bc 100644 --- a/sound/soc/sof/ipc4.c +++ b/sound/soc/sof/ipc4.c @@ -776,12 +776,21 @@ static void sof_ipc4_module_notification_handler(struct snd_sof_dev *sdev, } /* Handle ALSA kcontrol notification */ - if ((data->event_id & SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_MASK) == - SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL) { + switch (data->event_id & SOF_IPC4_NOTIFY_MODULE_EVENTID_SOF_MAGIC_MASK) { + case SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL: + { const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; if (tplg_ops->control->update) tplg_ops->control->update(sdev, ipc4_msg); + + break; + } + case SOF_IPC4_NOTIFY_MODULE_EVENTID_COMPR_MAGIC_VAL: + sof_ipc4_compr_drain_done(sdev, ipc4_msg); + break; + default: + break; } } From ed161257703b084aa2f34378656df0235c7ff0c4 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:53 +0300 Subject: [PATCH 583/924] ASoC: SOF: Intel: Kconfig: Remove redundant IPC version selects There is no need to select IPC3 and IPC4 along with INTEL_CNL as INTEL_CNL selects both. Similarly, INTEL_MTL selects IPC4, so there is no need to do that for LNL, PTL and NVL. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-27-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/Kconfig | 7 ------- 1 file changed, 7 deletions(-) diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig index 915abbef398d..840d5a36b958 100644 --- a/sound/soc/sof/intel/Kconfig +++ b/sound/soc/sof/intel/Kconfig @@ -186,8 +186,6 @@ config SND_SOC_SOF_INTEL_ICL tristate select SND_SOC_SOF_HDA_GENERIC select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE - select SND_SOC_SOF_IPC3 - select SND_SOC_SOF_IPC4 select SND_SOC_SOF_INTEL_CNL config SND_SOC_SOF_ICELAKE @@ -214,8 +212,6 @@ config SND_SOC_SOF_INTEL_TGL tristate select SND_SOC_SOF_HDA_GENERIC select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE - select SND_SOC_SOF_IPC3 - select SND_SOC_SOF_IPC4 select SND_SOC_SOF_INTEL_CNL config SND_SOC_SOF_TIGERLAKE @@ -268,7 +264,6 @@ config SND_SOC_SOF_INTEL_LNL tristate select SND_SOC_SOF_HDA_GENERIC select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE - select SND_SOC_SOF_IPC4 select SND_SOC_SOF_INTEL_MTL config SND_SOC_SOF_LUNARLAKE @@ -285,7 +280,6 @@ config SND_SOC_SOF_INTEL_PTL tristate select SND_SOC_SOF_HDA_COMMON select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE - select SND_SOC_SOF_IPC4 select SND_SOC_SOF_INTEL_LNL config SND_SOC_SOF_PANTHERLAKE @@ -302,7 +296,6 @@ config SND_SOC_SOF_INTEL_NVL tristate select SND_SOC_SOF_HDA_COMMON select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE - select SND_SOC_SOF_IPC4 select SND_SOC_SOF_INTEL_PTL config SND_SOC_SOF_NOVALAKE From 2c7b19f782b6a50b14a293a9ee4bfd6ec10b368b Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 7 Oct 2026 11:49:54 +0300 Subject: [PATCH 584/924] ASoC: SOF: Intel: Kconfig: Select compress support for TGL+ platforms Select SOF_COMPRESS for TGL and newer platforms, on Intel devices the compressed support is available with IPC4 only. Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-28-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig index 840d5a36b958..02b41e4ccab2 100644 --- a/sound/soc/sof/intel/Kconfig +++ b/sound/soc/sof/intel/Kconfig @@ -213,6 +213,7 @@ config SND_SOC_SOF_INTEL_TGL select SND_SOC_SOF_HDA_GENERIC select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE select SND_SOC_SOF_INTEL_CNL + select SND_SOC_SOF_COMPRESS config SND_SOC_SOF_TIGERLAKE tristate "SOF support for Tigerlake" @@ -249,6 +250,7 @@ config SND_SOC_SOF_INTEL_MTL select SND_SOC_SOF_HDA_GENERIC select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE select SND_SOC_SOF_IPC4 + select SND_SOC_SOF_COMPRESS config SND_SOC_SOF_METEORLAKE tristate "SOF support for Meteorlake" From 85b9b3b3111f52e66d53a05906cfcecb4f3ceb4d Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 7 Oct 2026 11:49:55 +0300 Subject: [PATCH 585/924] ASoC: SOF: topology: Add support for decoder and encoder widgets Decoder and encoder modules fall under process modules in SOF. Signed-off-by: Ranjani Sridharan Co-developed-by: Peter Ujfalusi Signed-off-by: Peter Ujfalusi Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20261007084955.1256-29-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-topology.c | 11 +++++++++++ sound/soc/sof/sof-audio.h | 4 +++- sound/soc/sof/topology.c | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 566913025cc7..74a6da9705da 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -3589,6 +3589,8 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget msg = &asrc->msg; break; } + case snd_soc_dapm_decoder: + case snd_soc_dapm_encoder: case snd_soc_dapm_effect: { struct sof_ipc4_process *process = swidget->private; @@ -4361,6 +4363,15 @@ static const struct sof_ipc_tplg_widget_ops tplg_ipc4_widget_ops[SND_SOC_DAPM_TY process_token_list, ARRAY_SIZE(process_token_list), NULL, sof_ipc4_prepare_process_module, NULL}, + /* for all practical purposes a decoder is like an effect type widget */ + [snd_soc_dapm_decoder] = {sof_ipc4_widget_setup_comp_process, + sof_ipc4_widget_free_comp_process, + process_token_list, ARRAY_SIZE(process_token_list), + NULL, sof_ipc4_prepare_process_module, NULL}, + [snd_soc_dapm_encoder] = {sof_ipc4_widget_setup_comp_process, + sof_ipc4_widget_free_comp_process, + process_token_list, ARRAY_SIZE(process_token_list), + NULL, sof_ipc4_prepare_process_module, NULL}, }; const struct sof_ipc_tplg_ops ipc4_tplg_ops = { diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 4ef72e680390..672879371d14 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -43,7 +43,9 @@ #define WIDGET_IS_AIF(id) ((id) == snd_soc_dapm_aif_in || (id) == snd_soc_dapm_aif_out) #define WIDGET_IS_AIF_OR_DAI(id) (WIDGET_IS_DAI(id) || WIDGET_IS_AIF(id)) #define WIDGET_IS_COPIER(id) (WIDGET_IS_AIF_OR_DAI(id) || (id) == snd_soc_dapm_buffer) -#define WIDGET_IS_PROCESS(id) ((id) == snd_soc_dapm_effect) +#define WIDGET_IS_PROCESS(id) ((id) == snd_soc_dapm_effect || \ + (id) == snd_soc_dapm_decoder || \ + (id) == snd_soc_dapm_encoder) #define SOF_DAI_PARAM_INTEL_SSP_MCLK 0 #define SOF_DAI_PARAM_INTEL_SSP_BCLK 1 diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 8338133899ab..e2cb90133650 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1530,6 +1530,8 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, list_add(&dai->list, &sdev->dai_list); swidget->private = dai; break; + case snd_soc_dapm_decoder: + case snd_soc_dapm_encoder: case snd_soc_dapm_effect: /* check we have some tokens - we need at least process type */ if (le32_to_cpu(tw->priv.size) == 0) { From e8f30155e96af2409d22fe7ed5da73776c77fedd Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Wed, 7 Oct 2026 17:25:24 +0800 Subject: [PATCH 586/924] ASoC: rt766: trigger JD when system reboots The initial jack detection at boot time may be missed after a warm reboot if the blind writes are not reloaded. This patch triggers jack detection from the .set_jack callback to ensure that the jack status is updated after a system reboot. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20261007092524.3711965-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt766-sdca.c | 8 +++++++- sound/soc/codecs/rt766-sdca.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c index 28b4118e05c9..2e891b99dc27 100644 --- a/sound/soc/codecs/rt766-sdca.c +++ b/sound/soc/codecs/rt766-sdca.c @@ -176,8 +176,11 @@ static irqreturn_t rt766_sdca_irq_jd_handler(int irq, void *data) if (!rt766->hs_jack) return IRQ_HANDLED; - if (!rt766->component->card || !rt766->component->card->instantiated) + if (!rt766->component->card || !rt766->component->card->instantiated) { + /* trigger again if card is not instantiated */ + regmap_write(rt766->regmap, RT766_GE_DET_IRQ_MNL, 0x01); return IRQ_HANDLED; + } mutex_lock(&rt766->disable_irq_lock); if (!rt766->disable_irq) @@ -304,6 +307,9 @@ static int rt766_sdca_set_jack_detect(struct snd_soc_component *component, if (rt766->hid_func_data) rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, rt766->component, rt766->irq_info, false); + } else { + /* trigger GE interrupt */ + regmap_write(rt766->regmap, RT766_GE_DET_IRQ_MNL, 0x01); } pm_runtime_put_autosuspend(component->dev); diff --git a/sound/soc/codecs/rt766-sdca.h b/sound/soc/codecs/rt766-sdca.h index de4064007eb9..61af5e9c077a 100644 --- a/sound/soc/codecs/rt766-sdca.h +++ b/sound/soc/codecs/rt766-sdca.h @@ -50,6 +50,7 @@ struct rt766_sdca_priv { #define RT766_DEV_ID1 0xc405 #define RT766_DEV_ID0 0xc406 #define RT766_BOND_LATCH_ID 0xc407 +#define RT766_GE_DET_IRQ_MNL 0xd203 #define RT766_HP_POWER_STATE 0x1000004 #define RT766_HP_FSM_CTL2_1 0x100000d From 3a05cc2d739486fa3c63aa32e6653ce55dae4819 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Fri, 2 Oct 2026 10:52:30 +0800 Subject: [PATCH 587/924] ASoC: tas2781: Fix TAS2781/TAS5825 PPC version logic conflict Fix cross-team code conflict in shared PPC version check path, ensure TAS2781 and TAS5825 apply their respective correct firmware parsing routines, eliminate version matching misbehavior between the two models. Fixes: f4ee43ae6ea8 ("ALSA: hda: Add TAS5825 support") Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20261002025230.1959-1-shenghao-ding@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2781-fmwlib.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c index 182aa98e29c3..f309a57337b0 100644 --- a/sound/soc/codecs/tas2781-fmwlib.c +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -1992,7 +1992,8 @@ static void dspbin_type_check(struct tasdevice_priv *tas_priv, if (tas_priv->chip_id == TAS2573) return; if ((tas_priv->dspbin_typ != TASDEV_BASIC) && - (ppcver < PPC3_VERSION_TAS5825_BASE)) + (ppcver < PPC3_VERSION_TAS5825_BASE || + tas_priv->chip_id == TAS2781)) tas_priv->fw_parse_fct_param_address = fw_parse_fct_param_address; } @@ -2003,7 +2004,8 @@ static int dspfw_default_callback(struct tasdevice_priv *tas_priv, int rc = 0; if (drv_ver == 0x100) { - if (ppcver >= PPC3_VERSION_TAS5825_BASE) { + if (ppcver >= PPC3_VERSION_TAS5825_BASE && + tas_priv->chip_id == TAS5825) { tas_priv->fw_parse_variable_header = fw_parse_variable_header_kernel; tas_priv->fw_parse_program_data = From 5a0c90cb911c16a8526955679386f5fb6ffc2ee6 Mon Sep 17 00:00:00 2001 From: Maurizio Casciano Date: Fri, 25 Sep 2026 15:59:14 +0200 Subject: [PATCH 588/924] ASoC: Intel: Add Cherry Trail RT5677 machine driver The Lenovo Yoga Book YB1-X91F/L uses the Cherry Trail SSP2 link with an RT5677 codec and a TS3A227E headset detector. Its amplifiers and headphone path also need board-specific GPIO sequencing. Add a Cherry Trail RT5677 machine driver derived from Yauhen Kharuzhy's earlier implementation. Clone the card, DAI links and link components per device so probing never mutates global templates. Allocate jack storage with the device lifetime, and configure MCLK and PLL, jack buttons, microphones, and output controls. Propagate clock, GPIO and ASRC setup failures so DAPM and probe callers do not continue with partially configured hardware. If the second speaker GPIO update fails, restore the first GPIO to its previous state. Reproduce the mode-3 enable pulse and settling delay required by the external speaker boost amplifier. The RT5677 GPIO2 and GPIO4 mappings are supplied as software-node properties by the Yoga Book x86-android-tablets quirk. The ACPI GPIO mapping in this driver covers the external speaker enable described in the codec device's _CRS. Select the card identity from the firmware parent. The SOF path uses the "cht rt5677" card name and "SOF" driver name, while the legacy SST path uses the conventional "cht-rt5677" card name. The firmware paths are mutually exclusive and share the rest of the machine driver. The card registers on both paths without snd_soc_fixup_dai_links_platform_name(). The same kernel was booted with automatic SOF selection and with snd_intel_dspcfg.dsp_driver=2 for SST. PCM0 playback and capture plus PCM1 deep-buffer playback passed on both paths. SOF passed S16_LE, S24_LE and S32_LE; SST advertises and passed S16_LE. The built-in speakers were audible on both paths, and internal-microphone capture contained signal. No ASoC or DSP stream errors were logged during the final tests. Link: https://github.com/jekhor/yogabook-linux-kernel/commit/4e41d0ff72c1d1fd8d5c59dda247e4ca8a1da999 Link: https://lore.kernel.org/linux-sound/20260611-asoc-yogabook-v2-v3-3-3128447b67b6@gmail.com/ Link: https://lore.kernel.org/r/20260829225729.395270-1-mauriziocasciano7@gmail.com Assisted-by: LLM Co-developed-by: Yauhen Kharuzhy Signed-off-by: Yauhen Kharuzhy Signed-off-by: Maurizio Casciano Link: https://patch.msgid.link/20260925135915.338670-2-mauriziocasciano7@gmail.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/Kconfig | 14 + sound/soc/intel/boards/Makefile | 2 + sound/soc/intel/boards/cht_rt5677.c | 654 ++++++++++++++++++++++++++++ 3 files changed, 670 insertions(+) create mode 100644 sound/soc/intel/boards/cht_rt5677.c diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index 919d3f5f5328..553ad123d6e6 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -161,6 +161,20 @@ config SND_SOC_INTEL_CHT_BSW_RT5672_MACH Say Y or m if you have such a device. This is a recommended option. If unsure select "N". +config SND_SOC_INTEL_CHT_RT5677_MACH + tristate "Cherrytrail with RT5677 codec" + depends on I2C && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST + select SND_SOC_ACPI + select SND_SOC_RT5677 + select SND_SOC_TS3A227E + help + This adds support for Cherry Trail platforms with an RT5677 codec + and TS3A227E headset-jack detector. It currently supports the + Lenovo Yoga Book YB1-X91F/L. + Say Y or m if you have such a device. If unsure select "N". + config SND_SOC_INTEL_CHT_BSW_RT5645_MACH tristate "Cherrytrail & Braswell with RT5645/5650 codec" depends on I2C && ACPI diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index 25a1a9066cbf..28940ca02aa7 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -9,6 +9,7 @@ snd-soc-sst-bytcr-rt5640-y := bytcr_rt5640.o snd-soc-sst-bytcr-rt5651-y := bytcr_rt5651.o snd-soc-sst-bytcr-wm5102-y := bytcr_wm5102.o snd-soc-sst-cht-bsw-rt5672-y := cht_bsw_rt5672.o +snd-soc-sst-cht-rt5677-y := cht_rt5677.o snd-soc-sst-cht-bsw-rt5645-y := cht_bsw_rt5645.o snd-soc-sst-cht-bsw-max98090_ti-y := cht_bsw_max98090_ti.o snd-soc-sst-cht-bsw-nau8824-y := cht_bsw_nau8824.o @@ -41,6 +42,7 @@ obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-rt5640.o obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5651_MACH) += snd-soc-sst-bytcr-rt5651.o obj-$(CONFIG_SND_SOC_INTEL_BYTCR_WM5102_MACH) += snd-soc-sst-bytcr-wm5102.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o +obj-$(CONFIG_SND_SOC_INTEL_CHT_RT5677_MACH) += snd-soc-sst-cht-rt5677.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH) += snd-soc-sst-cht-bsw-rt5645.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH) += snd-soc-sst-cht-bsw-max98090_ti.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_NAU8824_MACH) += snd-soc-sst-cht-bsw-nau8824.o diff --git a/sound/soc/intel/boards/cht_rt5677.c b/sound/soc/intel/boards/cht_rt5677.c new file mode 100644 index 000000000000..0f5e169fe96f --- /dev/null +++ b/sound/soc/intel/boards/cht_rt5677.c @@ -0,0 +1,654 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * cht_rt5677.c - ASoC machine driver for Cherry Trail with RT5677 + * + * Copyright (C) 2026 Yauhen Kharuzhy + * + * Based on the mainline cht_bsw_rt5672.c driver and Lenovo's + * cht_bl_dpcm_rt5677.c Android driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5677.h" +#include "../../codecs/ts3a227e.h" +#include "../atom/sst-atom-controls.h" + +#define RT5677_I2C "i2c-rt5677" +/* x86-android-tablets creates this client with dev_name "ts3a227e". */ +#define TS3A227E_I2C "i2c-ts3a227e" + +/* Platform clock 3 provides the codec's 19.2 MHz I2S master clock. */ +#define CHT_PLAT_CLK_3_HZ 19200000 +#define CHT_CODEC_DAI "rt5677-aif1" + +struct cht_rt5677_private { + char codec_name[SND_ACPI_I2C_ID_LEN]; + struct clk *mclk; + struct gpio_desc *gpio_spk_en1; + struct gpio_desc *gpio_spk_en2; + struct gpio_desc *gpio_hp_en; +}; + +static int cht_rt5677_platform_clock_enable(struct snd_soc_card *card, + struct snd_soc_dai *codec_dai) +{ + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(card); + int ret; + + ret = clk_prepare_enable(ctx->mclk); + if (ret) { + dev_err(card->dev, "enabling MCLK failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5677_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, 48000 * 512); + if (ret) { + dev_err(card->dev, "setting codec PLL failed: %d\n", ret); + goto disable_mclk; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_PLL1, + 48000 * 512, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(card->dev, "setting codec sysclk failed: %d\n", ret); + goto disable_mclk; + } + + return 0; + +disable_mclk: + clk_disable_unprepare(ctx->mclk); + return ret; +} + +static int cht_rt5677_platform_clock_disable(struct snd_soc_card *card, + struct snd_soc_dai *codec_dai) +{ + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(card); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_RCCLK, + 48000 * 512, SND_SOC_CLOCK_IN); + if (ret) + dev_warn(card->dev, "setting codec idle sysclk failed: %d\n", ret); + + clk_disable_unprepare(ctx->mclk); + + return ret; +} + +static int cht_rt5677_platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctl, + int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct snd_soc_dai *codec_dai; + + codec_dai = snd_soc_card_get_codec_dai(card, CHT_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "codec DAI not found\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) + return cht_rt5677_platform_clock_enable(card, codec_dai); + + return cht_rt5677_platform_clock_disable(card, codec_dai); +} + +static int cht_rt5677_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctl, int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(card); + + return gpiod_set_value_cansleep(ctx->gpio_hp_en, + SND_SOC_DAPM_EVENT_ON(event)); +} + +static int cht_rt5677_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctl, int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(card); + bool enable = SND_SOC_DAPM_EVENT_ON(event); + int rollback_ret; + int ret; + int i; + + /* Program the external boost amplifier for mode 3 before enabling it. */ + if (enable) { + for (i = 0; i < 4; i++) { + ret = gpiod_set_value_cansleep(ctx->gpio_spk_en1, + !(i & 1)); + if (ret) + goto disable_boost; + + udelay(2); + } + } + + ret = gpiod_set_value_cansleep(ctx->gpio_spk_en1, enable); + if (ret) + return ret; + + ret = gpiod_set_value_cansleep(ctx->gpio_spk_en2, enable); + if (ret) { + rollback_ret = gpiod_set_value_cansleep(ctx->gpio_spk_en1, + !enable); + if (rollback_ret) + dev_warn(card->dev, "rolling back speaker GPIO failed: %d\n", + rollback_ret); + return ret; + } + + if (enable) + msleep(50); + + return 0; + +disable_boost: + rollback_ret = gpiod_set_value_cansleep(ctx->gpio_spk_en1, 0); + if (rollback_ret) + dev_warn(card->dev, "disabling speaker boost GPIO failed: %d\n", + rollback_ret); + + return ret; +} + +static const struct snd_soc_dapm_widget cht_rt5677_widgets[] = { + SND_SOC_DAPM_HP("Headphone", cht_rt5677_hp_event), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", cht_rt5677_spk_event), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + cht_rt5677_platform_clock_control, + SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route cht_rt5677_map[] = { + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, + {"DMIC L1", NULL, "Int Mic"}, + {"DMIC R1", NULL, "Int Mic"}, + {"Headphone", NULL, "LOUT1"}, + {"Headphone", NULL, "LOUT2"}, + {"Speaker", NULL, "LOUT1"}, + {"Speaker", NULL, "LOUT2"}, + + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + {"ssp2 Rx", NULL, "AIF1 Capture"}, + {"Headphone", NULL, "Platform Clock"}, + {"Speaker", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Int Mic", NULL, "Platform Clock"}, +}; + +static const struct snd_kcontrol_new cht_rt5677_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static int cht_rt5677_aif1_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); + int ret; + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5677_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, params_rate(params) * 512); + if (ret) { + dev_err(rtd->dev, "setting codec PLL failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_PLL1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->dev, "setting codec sysclk failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBC_CFC); + if (ret) { + dev_err(codec_dai->dev, "setting TDM format failed: %d\n", ret); + return ret; + } + + /* Four 25-bit DSP_B slots carry 24-bit samples; the codec uses slots 0 and 1. */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x3, 0x3, 4, 25); + if (ret) { + dev_err(rtd->dev, "setting codec TDM slots failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int cht_rt5677_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + struct snd_soc_component *component = codec_dai->component; + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(runtime->card); + int ret; + + /* + * The codec derives its asynchronous sample-rate conversion clocks from + * I2S1 while the SSP link runs from the Cherry Trail platform clock. + */ + ret = rt5677_sel_asrc_clk_src(component, RT5677_DA_STEREO_FILTER | + RT5677_AD_STEREO1_FILTER | + RT5677_I2S1_SOURCE, + RT5677_CLK_SEL_I2S1_ASRC); + if (ret) { + dev_err(runtime->dev, "setting stereo ASRC clock failed: %d\n", ret); + return ret; + } + + /* Mono ADC L uses the codec system clock rather than the I2S1 clock. */ + ret = rt5677_sel_asrc_clk_src(component, RT5677_AD_MONO_L_FILTER, RT5677_CLK_SEL_SYS2); + if (ret) { + dev_err(runtime->dev, "setting mono ASRC clock failed: %d\n", ret); + return ret; + } + + /* Firmware may leave MCLK enabled without updating the CCF count. */ + ret = clk_prepare_enable(ctx->mclk); + if (ret) { + dev_err(runtime->dev, "preparing MCLK failed: %d\n", ret); + return ret; + } + clk_disable_unprepare(ctx->mclk); + + ret = clk_set_rate(ctx->mclk, CHT_PLAT_CLK_3_HZ); + if (ret) { + dev_err(runtime->dev, "setting MCLK rate failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int cht_rt5677_codec_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); + + /* The DSP will convert the FE rate to 48k, stereo, 24bits */ + rate->min = 48000; + rate->max = 48000; + channels->min = 2; + channels->max = 2; + + /* + * Configure SSP2 for the 24-bit format expected by the codec. The + * ssp2-port front end still advertises S16_LE and converts the stream. + */ + snd_mask_none(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT)); + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static struct snd_soc_jack_pin cht_rt5677_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int cht_rt5677_headset_init(struct snd_soc_component *component) +{ + struct snd_soc_card *card = component->card; + struct snd_soc_jack *jack; + int jack_type; + int ret; + + jack = devm_kzalloc(card->dev, sizeof(*jack), GFP_KERNEL); + if (!jack) + return -ENOMEM; + + /* + * TI supports four headset buttons: + * KEY_MEDIA + * KEY_VOICECOMMAND + * KEY_VOLUMEUP + * KEY_VOLUMEDOWN + */ + jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3; + + ret = snd_soc_card_jack_new_pins(card, "Headset Jack", jack_type, + jack, cht_rt5677_jack_pins, + ARRAY_SIZE(cht_rt5677_jack_pins)); + if (ret) { + dev_err(card->dev, "creating headset jack failed: %d\n", ret); + return ret; + } + + return ts3a227e_enable_jack_detect(component, jack); +} + +static int cht_rt5677_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static const struct snd_soc_ops cht_rt5677_aif1_ops = { + .startup = cht_rt5677_aif1_startup, +}; + +static const struct snd_soc_ops cht_rt5677_be_ssp2_ops = { + .hw_params = cht_rt5677_aif1_hw_params, +}; + +static const struct snd_soc_aux_dev cht_rt5677_headset_dev = { + .dlc = COMP_AUX(TS3A227E_I2C), + .init = cht_rt5677_headset_init, +}; + +SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); +SND_SOC_DAILINK_DEF(media, DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai"))); +SND_SOC_DAILINK_DEF(deepbuffer, DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai"))); +SND_SOC_DAILINK_DEF(ssp2_port, DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port"))); +SND_SOC_DAILINK_DEF(ssp2_codec, DAILINK_COMP_ARRAY(COMP_CODEC(RT5677_I2C, CHT_CODEC_DAI))); +SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform"))); + +static const struct snd_soc_dai_link cht_rt5677_dailink[] = { + /* Front End DAI links */ + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .nonatomic = true, + .dynamic = 1, + .ops = &cht_rt5677_aif1_ops, + SND_SOC_DAILINK_REG(media, dummy, platform), + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .nonatomic = true, + .dynamic = 1, + .playback_only = 1, + .ops = &cht_rt5677_aif1_ops, + SND_SOC_DAILINK_REG(deepbuffer, dummy, platform), + }, + + /* Back End DAI links */ + { + /* SSP2 - Codec */ + .name = "SSP2-Codec", + .id = 0, + .no_pcm = 1, + .nonatomic = true, + .init = cht_rt5677_codec_init, + .be_hw_params_fixup = cht_rt5677_codec_fixup, + .ops = &cht_rt5677_be_ssp2_ops, + SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform), + }, +}; + +static const struct snd_soc_card cht_rt5677_card = { + .owner = THIS_MODULE, + .num_links = ARRAY_SIZE(cht_rt5677_dailink), + .num_aux_devs = 1, + .dapm_widgets = cht_rt5677_widgets, + .num_dapm_widgets = ARRAY_SIZE(cht_rt5677_widgets), + .dapm_routes = cht_rt5677_map, + .num_dapm_routes = ARRAY_SIZE(cht_rt5677_map), + .controls = cht_rt5677_controls, + .num_controls = ARRAY_SIZE(cht_rt5677_controls), +}; + +static const struct acpi_gpio_params speaker_enable_gpio = { 2, 0, false }; +static const struct acpi_gpio_mapping cht_rt5677_acpi_gpios[] = { + { "speaker-enable-gpios", &speaker_enable_gpio, 1 }, + { } +}; + +#define SOF_CARD_NAME "cht rt5677" +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "cht-rt5677" +#define DRIVER_NAME NULL + +static void cht_rt5677_gpiod_put(void *data) +{ + gpiod_put(data); +} + +static void cht_rt5677_remove_driver_gpios(void *data) +{ + acpi_dev_remove_driver_gpios(data); +} + +static int cht_rt5677_get_gpio(struct device *dev, struct device *codec_dev, + const char *con_id, struct gpio_desc **gpio) +{ + int ret; + + *gpio = gpiod_get(codec_dev, con_id, GPIOD_OUT_LOW); + if (IS_ERR(*gpio)) { + ret = PTR_ERR(*gpio); + return dev_err_probe(dev, ret, "getting %s GPIO failed\n", con_id); + } + + ret = devm_add_action_or_reset(dev, cht_rt5677_gpiod_put, *gpio); + if (ret) + return dev_err_probe(dev, ret, "registering %s GPIO cleanup failed\n", + con_id); + + return 0; +} + +static int cht_rt5677_clone_dai_links(struct device *dev, struct snd_soc_card *card) +{ + struct snd_soc_dai_link_component *components; + struct snd_soc_dai_link *links; + size_t size; + int i; + + links = devm_kmemdup(dev, cht_rt5677_dailink, + sizeof(cht_rt5677_dailink), GFP_KERNEL); + if (!links) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(cht_rt5677_dailink); i++) { + if (links[i].num_cpus) { + size = sizeof(*components) * links[i].num_cpus; + components = devm_kmemdup(dev, links[i].cpus, size, GFP_KERNEL); + if (!components) + return -ENOMEM; + links[i].cpus = components; + } + + if (links[i].num_codecs) { + size = sizeof(*components) * links[i].num_codecs; + components = devm_kmemdup(dev, links[i].codecs, size, GFP_KERNEL); + if (!components) + return -ENOMEM; + links[i].codecs = components; + } + + if (links[i].num_platforms) { + size = sizeof(*components) * links[i].num_platforms; + components = devm_kmemdup(dev, links[i].platforms, size, GFP_KERNEL); + if (!components) + return -ENOMEM; + links[i].platforms = components; + } + } + + card->dai_link = links; + return 0; +} + +static int snd_cht_rt5677_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cht_rt5677_private *ctx; + struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); + struct snd_soc_aux_dev *aux_dev; + struct snd_soc_dai_link *dai_links; + struct snd_soc_card *card; + struct acpi_device *adev; + struct device *codec_dev; + bool sof_parent; + int ret; + int i; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + card = devm_kmemdup(dev, &cht_rt5677_card, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + ret = cht_rt5677_clone_dai_links(dev, card); + if (ret) + return ret; + dai_links = card->dai_link; + + aux_dev = devm_kmemdup(dev, &cht_rt5677_headset_dev, + sizeof(cht_rt5677_headset_dev), GFP_KERNEL); + if (!aux_dev) + return -ENOMEM; + + card->aux_dev = aux_dev; + + strscpy(ctx->codec_name, RT5677_I2C, sizeof(ctx->codec_name)); + + /* Use the ACPI-enumerated codec name when firmware describes the codec. */ + adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1); + if (adev) { + snprintf(ctx->codec_name, sizeof(ctx->codec_name), + "i2c-%s", acpi_dev_name(adev)); + + acpi_dev_put(adev); + for (i = 0; i < card->num_links; i++) { + if (dai_links[i].codecs->name && + !strcmp(dai_links[i].codecs->name, + RT5677_I2C)) { + dai_links[i].codecs->name = ctx->codec_name; + break; + } + } + } + + codec_dev = bus_find_device_by_name(&i2c_bus_type, NULL, + ctx->codec_name); + if (!codec_dev) + return dev_err_probe(dev, -EPROBE_DEFER, + "waiting for codec %s\n", ctx->codec_name); + + adev = ACPI_COMPANION(codec_dev); + if (adev) { + /* + * Firmware describes the external speaker enable in _CRS. The + * x86-android-tablets quirk supplies software-node properties for + * the RT5677 GPIO2 and GPIO4 speaker/headphone enables. + */ + ret = acpi_dev_add_driver_gpios(adev, cht_rt5677_acpi_gpios); + if (ret) { + dev_err_probe(dev, ret, "adding codec GPIO mappings failed\n"); + goto out_put_codec; + } + + ret = devm_add_action_or_reset(dev, cht_rt5677_remove_driver_gpios, adev); + if (ret) { + dev_err_probe(dev, ret, "registering GPIO mapping cleanup failed\n"); + goto out_put_codec; + } + } + + ret = cht_rt5677_get_gpio(dev, codec_dev, "speaker-enable", &ctx->gpio_spk_en1); + if (ret) + goto out_put_codec; + + ret = cht_rt5677_get_gpio(dev, codec_dev, "speaker-enable2", &ctx->gpio_spk_en2); + if (ret) + goto out_put_codec; + + ret = cht_rt5677_get_gpio(dev, codec_dev, "headphone-enable", &ctx->gpio_hp_en); + if (ret) + goto out_put_codec; + + put_device(codec_dev); + + card->dev = dev; + + ctx->mclk = devm_clk_get(dev, "pmc_plt_clk_3"); + if (IS_ERR(ctx->mclk)) + return dev_err_probe(dev, PTR_ERR(ctx->mclk), "getting MCLK failed\n"); + + snd_soc_card_set_drvdata(card, ctx); + + sof_parent = snd_soc_acpi_sof_parent(dev); + + if (sof_parent) { + card->name = SOF_CARD_NAME; + card->driver_name = SOF_DRIVER_NAME; + } else { + card->name = CARD_NAME; + card->driver_name = DRIVER_NAME; + } + + ret = devm_snd_soc_register_card(dev, card); + if (ret) + return dev_err_probe(dev, ret, "registering sound card failed\n"); + + return 0; + +out_put_codec: + put_device(codec_dev); + return ret; +} + +static struct platform_driver snd_cht_rt5677_driver = { + .driver = { + .name = "cht-rt5677", + .pm = &snd_soc_pm_ops, + }, + .probe = snd_cht_rt5677_probe, +}; + +module_platform_driver(snd_cht_rt5677_driver); + +MODULE_DESCRIPTION("Cherry Trail RT5677 machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cht-rt5677"); From a0448ff851875fdbcf1646d001a38300cb31b5fd Mon Sep 17 00:00:00 2001 From: Maurizio Casciano Date: Fri, 25 Sep 2026 15:59:15 +0200 Subject: [PATCH 589/924] ASoC: Intel: cht: Add Yoga Book RT5677 machine entry The Yoga Book YB1-X91F/L exposes its RT5677 codec as ACPI device 10EC5677. Add a Cherry Trail machine-table entry that supplies the legacy SST firmware and board names and the SOF topology name. The Intel DSP configuration selects one firmware path at probe time. SST loads intel/fw_sst_22a8.bin and uses the cht_rt5677 board name; SOF loads sof-cht-rt5677.tplg through the standard firmware search path. The same kernel and machine entry were boot-tested with both paths on a YB1-X91L. The SOF topology sources are maintained and distributed separately by the SOF project. Assisted-by: LLM Co-developed-by: Yauhen Kharuzhy Signed-off-by: Yauhen Kharuzhy Signed-off-by: Maurizio Casciano Link: https://patch.msgid.link/20260925135915.338670-3-mauriziocasciano7@gmail.com Signed-off-by: Mark Brown --- sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c index e4c3492a0c28..442ef30ffcc7 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c @@ -137,6 +137,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { .board = "cht-bsw", .sof_tplg_filename = "sof-cht-rt5670.tplg", }, + { + /* Lenovo Yoga Book YB1-X91F/L */ + .id = "10EC5677", + .drv_name = "cht-rt5677", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "cht_rt5677", + .sof_tplg_filename = "sof-cht-rt5677.tplg", + }, { .comp_ids = &rt5645_comp_ids, .drv_name = "cht-bsw-rt5645", From 5fcdb40a4776cc6f8d3179aa9cf9663fc18c7861 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 7 Oct 2026 18:08:55 +0700 Subject: [PATCH 590/924] ASoC: mediatek: common: Handle constraint return value in FE startup snd_pcm_hw_constraint_integer() may return 1, which is then returned from mtk_afe_fe_startup(). ASoC only treats 0 as success when marking the DAI as started, so .shutdown() can be skipped on rollback. Return 0 explicitly on success. Fixes: 283b612429a2 ("ASoC: mediatek: implement mediatek common structure") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20261007110858.509341-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/common/mtk-afe-fe-dai.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c index 948f606cbaf2..ec99bc4a5d5e 100644 --- a/sound/soc/mediatek/common/mtk-afe-fe-dai.c +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -75,8 +75,10 @@ int mtk_afe_fe_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + return ret; + } /* dynamic allocate irq to memif */ if (memif->irq_usage < 0) { @@ -88,10 +90,10 @@ int mtk_afe_fe_startup(struct snd_pcm_substream *substream, } else { dev_err(afe->dev, "%s() error: no more asys irq\n", __func__); - ret = -EBUSY; + return -EBUSY; } } - return ret; + return 0; } EXPORT_SYMBOL_GPL(mtk_afe_fe_startup); From 71253751586b8ce37570b8e15cfbc74f0dd06c3d Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 7 Oct 2026 18:08:56 +0700 Subject: [PATCH 591/924] ASoC: mediatek: mt8188: Handle constraint return value in FE startup snd_pcm_hw_constraint_minmax() may return 1, which is then returned from mt8188_afe_fe_startup(). ASoC only treats 0 as success when marking the DAI as started, so .shutdown() can be skipped on rollback. Return 0 explicitly on success. Fixes: bf106bf09376 ("ASoC: mediatek: mt8188: add platform driver") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20261007110858.509341-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-pcm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c index 7b1f5d05f4d6..6d8afcf138e4 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c @@ -320,6 +320,9 @@ static int mt8188_afe_fe_startup(struct snd_pcm_substream *substream, MT8188_MEMIF_DL7_MAX_PERIOD_SIZE); if (ret < 0) dev_dbg(afe->dev, "hw_constraint_minmax failed\n"); + else + ret = 0; + out: return ret; } From 1f234d33e42e7c38f3ea6012ca93ff579c43ed58 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 7 Oct 2026 18:08:57 +0700 Subject: [PATCH 592/924] ASoC: mediatek: mt8189: Handle constraint return value in FE startup snd_pcm_hw_constraint_integer() may return 1, which is then returned from mt8189_fe_startup(). ASoC only treats 0 as success when marking the DAI as started, so .shutdown() can be skipped on rollback. Return 0 explicitly on success. Fixes: 7eb153585598 ("ASoC: mediatek: mt8189: add platform driver") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20261007110858.509341-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-pcm.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c index 77cf2b604f6c..a75457667353 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c @@ -155,8 +155,10 @@ static int mt8189_fe_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { dev_warn(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + return ret; + } /* dynamic allocate irq to memif */ if (memif->irq_usage < 0) { @@ -168,11 +170,11 @@ static int mt8189_fe_startup(struct snd_pcm_substream *substream, } else { dev_err(afe->dev, "%s() error: no more asys irq\n", __func__); - ret = -EBUSY; + return -EBUSY; } } - return ret; + return 0; } static void mt8189_fe_shutdown(struct snd_pcm_substream *substream, From 8de3f4e8e81148dd4cdd19e622d4be1fe07a7f52 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 7 Oct 2026 18:08:58 +0700 Subject: [PATCH 593/924] ASoC: mediatek: mt8365: Handle constraint return value in FE startup snd_pcm_hw_constraint_integer() may return 1, which is then returned from mt8365_afe_fe_startup(). ASoC only treats 0 as success when marking the DAI as started, so .shutdown() can be skipped on rollback. Return 0 explicitly on success. Fixes: e1991d102bc2 ("ASoC: mediatek: mt8365: Add the AFE driver support") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20261007110858.509341-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8365/mt8365-afe-pcm.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c index e9497246f37c..314235557fdf 100644 --- a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +++ b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c @@ -514,11 +514,14 @@ static int mt8365_afe_fe_startup(struct snd_pcm_substream *substream, snd_soc_set_runtime_hwparams(substream, afe->mtk_afe_hardware); ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + return ret; + } mt8365_afe_enable_main_clk(afe); - return ret; + + return 0; } static void mt8365_afe_fe_shutdown(struct snd_pcm_substream *substream, From c3e35f355fadf6be660e1fd356bb22140dda8e46 Mon Sep 17 00:00:00 2001 From: Devin Christensen Date: Wed, 7 Oct 2026 16:32:14 +0000 Subject: [PATCH 594/924] ALSA: hda/ca0132: Add Sound BlasterX AE-5 external RGB LED strip support The Sound BlasterX AE-5 and AE-5 Plus expose a 3-pin 5V addressable RGB header driven directly by the CA0113 PCIe-to-HD-Audio bridge. Rather than using the CA0132 DSP or MMIO GPIO, the CA0113 contains an autonomous HDA link sniffer that snoops a designated HDA DMA playback stream, extracts the 24-bit GRB color payload, and serializes it as 800 kHz single-wire NRZ pulses to the strip (WS2812B protocol). Expose the strip through volatile card control elements: "AE-5 LED Strip" (RW, BYTES): 300-byte RGB payload representing up to 100 addressable LEDs (3 bytes per LED). "AE-5 LED Strip Count" (RW, INTEGER): Active LED count (1..100). The controls are marked with IFACE_CARD and SNDRV_CTL_ELEM_ACCESS_VOLATILE so changes do not trigger unnecessary audio daemon wakeups or state restoration by alsactl. Writes allocate an idle HDA playback stream, format a 32 KB DMA buffer populated with repeating WS2812 frames (preamble, audio words per LED, and reset gap), configure the CA0113 BAR2 serializers and stream-tag pin mux, and pulse the stream long enough for the reset latch to fire. Access is serialized with a per-spec mutex, and the controls are only registered on QUIRK_AE5 devices. The strip transport uses snd_hdac_dsp_prepare() / snd_hdac_dsp_cleanup(), which require CONFIG_SND_HDA_DSP_LOADER. All strip-related code and struct fields are conditionally compiled under IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER), with a no-op stub for ae5_add_strip_controls() when disabled. Signed-off-by: Devin Christensen Link: https://patch.msgid.link/20261007163207.2621040-1-dchristensen8@proton.me Signed-off-by: Takashi Iwai --- sound/hda/codecs/ca0132.c | 391 +++++++++++++++++++++++++++++++++++++- 1 file changed, 390 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c index 0f8fb62f021d..896091c1144f 100644 --- a/sound/hda/codecs/ca0132.c +++ b/sound/hda/codecs/ca0132.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -1060,6 +1061,8 @@ enum dsp_download_state { * CA0132 specific */ +#define AE5_STRIP_MAX_LEDS 100 + struct ca0132_spec { struct hda_gen_spec gen; @@ -1162,6 +1165,13 @@ struct ca0132_spec { * Renames PlayEnhancement and CrystalVoice too. */ bool use_alt_controls; + +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) + /* AE-5 WS2812 strip control */ + struct mutex ae5_strip_mutex; + u32 ae5_strip_cur_colors[AE5_STRIP_MAX_LEDS]; + int ae5_strip_cur_num_leds; +#endif }; /* @@ -6846,6 +6856,15 @@ static int ca0132_alt_add_mic_boost_enum(struct hda_codec *codec) } +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) +static int ae5_add_strip_controls(struct hda_codec *codec); +#else +static inline int ae5_add_strip_controls(struct hda_codec *codec) +{ + return 0; +} +#endif + /* * Add headphone gain enumerated control for the AE-5. This switches between * three modes, low, medium, and high. When non-headphone outputs are selected, @@ -7149,6 +7168,10 @@ static int ca0132_build_controls(struct hda_codec *codec) switch (ca0132_quirk(spec)) { case QUIRK_AE5: + err = ae5_add_strip_controls(codec); + if (err < 0) + return err; + fallthrough; case QUIRK_AE7: err = ae5_add_headphone_gain_enum(codec); if (err < 0) @@ -8316,8 +8339,367 @@ static void sbz_setup_defaults(struct hda_codec *codec) } /* - * Setup default parameters for the Sound BlasterX AE-5 DSP. + * Sound BlasterX AE-5 External WS2812B RGB LED Strip Transport */ +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) +#define AE5_STRIP_DMA_SIZE 0x8000 +#define AE5_STRIP_TOTAL_WORDS (AE5_STRIP_DMA_SIZE / sizeof(u32)) +#define AE5_STRIP_PREAMBLE_WORDS 5 +#define AE5_STRIP_WORDS_PER_LED 4 +#define AE5_STRIP_RESET_GAP_WORDS 83 + +#define CA0113_SERIALIZER_BASE(spec, ch) ((spec)->mem_base + 0x42c + (ch) * 0x40) +#define CA0113_SERIALIZER_CLK(spec, ch) ((spec)->mem_base + 0x43c + (ch) * 0x40) +#define CA0113_SERIALIZER_OUT(spec, ch) ((spec)->mem_base + 0x454 + (ch) * 0x40) + +static void ae5_strip_encode_24(u32 u, u32 *words) +{ + /* + * 24-bit pixel -> 4 x 32-bit audio words (6 bits per word). + * Hardware audio serializer transmits MSB first (bits 31 down to 0). + * For word i (i8 = 23, 17, 11, 5), bit_idx 0..5 (MSB to LSB within the word) + * is placed at n = 29 - bit_idx * 4: + * Bit 0 -> 0b100 << n (375ns high, 1042ns low): BIT(n + 2) + * Bit 1 -> 0b111 << n (1083ns high, 333ns low): BIT(n + 2) | BIT(n + 1) | BIT(n) + */ + int i8 = 23; + int i, bit_idx; + + for (i = 0; i < 4; i++) { + u32 out = 0; + + for (bit_idx = 0; bit_idx < 6; bit_idx++) { + int bit = (u >> (i8 - bit_idx)) & 1; + int n = 29 - bit_idx * 4; + + out |= (bit ? (BIT(2) | BIT(1) | BIT(0)) : BIT(2)) << n; + } + words[i] = out; + i8 -= 6; + } +} + +static struct hdac_stream *ae5_strip_find_stream(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + struct hdac_bus *bus = &codec->bus->core; + struct hdac_stream *s, *dsp = NULL, *first = NULL; + + guard(spinlock_irqsave)(&bus->reg_lock); + list_for_each_entry(s, &bus->stream_list, list) { + if (s->direction != SNDRV_PCM_STREAM_PLAYBACK) + continue; + if (s->opened || s->running || s->locked) + continue; + if (!first) + first = s; + /* The DSP DMA consumes the DSP-loader stream (tag in dsp_stream_id). */ + if (spec->dsp_stream_id && s->stream_tag == spec->dsp_stream_id) + dsp = s; + } + + s = dsp ? dsp : first; + if (s) + s->opened = 1; + + return s; +} + +/* + * Caller must hold spec->ae5_strip_mutex. + */ +static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors, int num_leds) +{ + struct ca0132_spec *spec = codec->spec; + struct hdac_bus *bus = &codec->bus->core; + struct snd_dma_buffer dmab; + struct hdac_stream *hstr = NULL; + unsigned int format, stream_tag; + u32 enc_words[4]; + __le32 *dst; + int word_idx = 0; + int frame_words; + int led, w, i; + int retries = 20; /* ~100ms max */ + + if (!spec || !spec->mem_base) + return -ENODEV; + if (num_leds <= 0 || num_leds > AE5_STRIP_MAX_LEDS) + return -EINVAL; + + lockdep_assert_held(&spec->ae5_strip_mutex); + + CLASS(snd_hda_power_pm, pm)(codec); + if (pm.err < 0) + return pm.err; + + /* + * 44.1kHz, 24-bit, 2-channel format (0x4031) matches Windows CtxHda + * HDAUDIO_STREAM_FORMAT. + */ + format = snd_hdac_stream_format(2, 24, 44100); + + for (;;) { + hstr = ae5_strip_find_stream(codec); + if (!hstr) { + if (--retries <= 0) { + codec_warn(codec, "AE5 strip: no free azx stream after retries\n"); + return -EBUSY; + } + usleep_range(5000, 10000); + continue; + } + + stream_tag = snd_hdac_dsp_prepare(hstr, format, AE5_STRIP_DMA_SIZE, &dmab); + if ((int)stream_tag > 0) + break; + + /* Prepare failed (e.g. -EBUSY due to race with playback starting); retry */ + scoped_guard(spinlock_irq, &bus->reg_lock) + hstr->opened = 0; + + if (--retries <= 0) { + codec_warn(codec, "AE5 strip: snd_hdac_dsp_prepare failed %d\n", + (int)stream_tag); + return (int)stream_tag; + } + usleep_range(5000, 10000); + } + + /* 1. Populate the 32KB DMA buffer with repeating WS2812 frames + reset gaps */ + memset(dmab.area, 0, AE5_STRIP_DMA_SIZE); + dst = (__le32 *)dmab.area; + + /* Words per frame: preamble + (num_leds * words_per_led) + reset gap */ + frame_words = AE5_STRIP_PREAMBLE_WORDS + (num_leds * AE5_STRIP_WORDS_PER_LED) + + AE5_STRIP_RESET_GAP_WORDS; + + while (word_idx + frame_words <= AE5_STRIP_TOTAL_WORDS) { + /* Preamble per Windows CtxHda FUN_00042944 */ + for (w = 0; w < AE5_STRIP_PREAMBLE_WORDS; w++) { + *dst++ = 0; + word_idx++; + } + /* LEDs */ + for (led = 0; led < num_leds; led++) { + ae5_strip_encode_24(grb_colors[led], enc_words); + for (w = 0; w < AE5_STRIP_WORDS_PER_LED; w++) { + *dst++ = cpu_to_le32(enc_words[w]); + word_idx++; + } + } + /* Zero reset gap (> 1.8 ms; WS2812 requires > 50 us low) */ + for (w = 0; w < AE5_STRIP_RESET_GAP_WORDS; w++) { + *dst++ = 0; + word_idx++; + } + } + while (word_idx < AE5_STRIP_TOTAL_WORDS) { + *dst++ = 0; + word_idx++; + } + /* Ensure DMA buffer writes are committed before triggering the HDA stream */ + wmb(); + + /* 2. Configure CA0113 BAR2 hardware registers */ + /* Power up clocks (CtxHdb 0x141b7 / 0x14210) */ + writel(readl(spec->mem_base + 0xc04) | 0x7, spec->mem_base + 0xc04); + + /* Base serializer initialization (CtxHdb 0x14ba4) */ + writel(readl(spec->mem_base + 0x400) | 1, spec->mem_base + 0x400); + for (i = 0; i < 4; i++) { + writel(readl(CA0113_SERIALIZER_BASE(spec, i)) & ~1, + CA0113_SERIALIZER_BASE(spec, i)); + writel(0, CA0113_SERIALIZER_CLK(spec, i)); + } + writel(readl(spec->mem_base + 0x408) | 1, spec->mem_base + 0x408); + writel(readl(spec->mem_base + 0x40c) | 1, spec->mem_base + 0x40c); + writel((readl(spec->mem_base + 0x410) & ~0xb) | 0x14, spec->mem_base + 0x410); + + /* Configure serializers 0..3 clocking (CtxHdb 0x14a6c) */ + for (i = 0; i < 4; i++) + writel(readl(CA0113_SERIALIZER_CLK(spec, i)) | 0x33, + CA0113_SERIALIZER_CLK(spec, i)); + + /* BAR2 + 0x100 format & channel enables: + * - base 0x70f + * - bit 28 = 1 (24-bit audio format via CtxHdb 0x14f58) + * - bits 29..31 = 1 (44.1 kHz sample rate via CtxHdb 0x14fa4) + * - bits 4..7 = 0xf (stage 1 enables) + * - bits 12..15 = 0xf (stage 2 enables) + * => 0x3000ff8f + */ + writel((readl(spec->mem_base + 0x100) & ~0xe000ffff) | 0x3000ff8f, spec->mem_base + 0x100); + + /* Enable serializer output drivers for pins 0..3 (CtxHdb 0x14e94) */ + for (i = 0; i < 4; i++) + writel(readl(CA0113_SERIALIZER_OUT(spec, i)) | 1, + CA0113_SERIALIZER_OUT(spec, i)); + + /* Route all channels 0..3 to stream_tag (CtxHdb 0x1502c) */ + writel((stream_tag << 28) | (stream_tag << 20) | (stream_tag << 12) | (stream_tag << 4), + spec->mem_base + 0x104); + + /* Enable CA0113 MMIO GPIO 0 & 1 */ + ca0113_mmio_gpio_set(codec, 0, true); + ca0113_mmio_gpio_set(codec, 1, true); + + codec_dbg(codec, "AE5 strip: send_frame: num_leds=%d stream_tag=%u (0x%08x)\n", + num_leds, stream_tag, readl(spec->mem_base + 0x104)); + + /* 3. Trigger stream transmission on HDA link */ + snd_hdac_dsp_trigger(hstr, true); + /* + * WS2812 latches on the >50us reset gap after one frame. The 32KB buffer holds ~64 + * repeating frame+reset cycles; at ~1.4ms/frame a 15ms run transmits ~10 complete + * cycles - more than enough to latch reliably. Keep it short so rapid sysfs writes + * (OpenRGB effects/theme changes) are not serialized behind a 1s blocking sleep. + */ + usleep_range(15000, 20000); + snd_hdac_dsp_trigger(hstr, false); + codec_dbg(codec, "AE5 strip: trigger complete\n"); + + /* Teardown: clear 0x104 pin mux back to 0 */ + writel(0x00000000, spec->mem_base + 0x104); + + snd_hdac_dsp_cleanup(hstr, &dmab); + scoped_guard(spinlock_irq, &bus->reg_lock) + hstr->opened = 0; + + return 0; +} + +static int ae5_strip_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = AE5_STRIP_MAX_LEDS * 3; + return 0; +} + +static int ae5_strip_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int i; + + guard(mutex)(&spec->ae5_strip_mutex); + for (i = 0; i < AE5_STRIP_MAX_LEDS; i++) { + u32 grb = spec->ae5_strip_cur_colors[i]; + + ucontrol->value.bytes.data[i * 3 + 0] = (grb >> 8) & 0xff; + ucontrol->value.bytes.data[i * 3 + 1] = (grb >> 16) & 0xff; + ucontrol->value.bytes.data[i * 3 + 2] = grb & 0xff; + } + return 0; +} + +static int ae5_strip_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + u32 colors[AE5_STRIP_MAX_LEDS]; + int i, num_leds, change, ret; + + guard(mutex)(&spec->ae5_strip_mutex); + for (i = 0; i < AE5_STRIP_MAX_LEDS; i++) { + u8 r = ucontrol->value.bytes.data[i * 3 + 0]; + u8 g = ucontrol->value.bytes.data[i * 3 + 1]; + u8 b = ucontrol->value.bytes.data[i * 3 + 2]; + + colors[i] = ((u32)g << 16) | ((u32)r << 8) | (u32)b; + } + change = memcmp(spec->ae5_strip_cur_colors, colors, + sizeof(spec->ae5_strip_cur_colors)) != 0; + num_leds = spec->ae5_strip_cur_num_leds; + ret = ae5_strip_send_frame(codec, colors, num_leds); + if (ret < 0) + return ret; + if (change) + memcpy(spec->ae5_strip_cur_colors, colors, + sizeof(spec->ae5_strip_cur_colors)); + return change; +} + +static int ae5_strip_num_leds_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = AE5_STRIP_MAX_LEDS; + return 0; +} + +static int ae5_strip_num_leds_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + guard(mutex)(&spec->ae5_strip_mutex); + ucontrol->value.integer.value[0] = spec->ae5_strip_cur_num_leds; + return 0; +} + +static int ae5_strip_num_leds_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int val = ucontrol->value.integer.value[0]; + int ret; + + if (val < 1 || val > AE5_STRIP_MAX_LEDS) + return -EINVAL; + + guard(mutex)(&spec->ae5_strip_mutex); + if (spec->ae5_strip_cur_num_leds == val) + return 0; + ret = ae5_strip_send_frame(codec, spec->ae5_strip_cur_colors, val); + if (ret < 0) + return ret; + spec->ae5_strip_cur_num_leds = val; + return 1; +} + +static const struct snd_kcontrol_new ae5_strip_ctls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "AE-5 LED Strip", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = ae5_strip_ctl_info, + .get = ae5_strip_ctl_get, + .put = ae5_strip_ctl_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "AE-5 LED Strip Count", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = ae5_strip_num_leds_info, + .get = ae5_strip_num_leds_get, + .put = ae5_strip_num_leds_put, + }, +}; + +static int ae5_add_strip_controls(struct hda_codec *codec) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(ae5_strip_ctls); i++) { + err = snd_hda_ctl_add(codec, 0, + snd_ctl_new1(&ae5_strip_ctls[i], codec)); + if (err < 0) + return err; + } + return 0; +} +#endif /* CONFIG_SND_HDA_DSP_LOADER */ + static void ae5_setup_defaults(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; @@ -8741,6 +9123,10 @@ static void ca0132_init_chip(struct hda_codec *codec) unsigned int on; mutex_init(&spec->chipio_mutex); +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) + mutex_init(&spec->ae5_strip_mutex); + spec->ae5_strip_cur_num_leds = 10; +#endif /* * The Windows driver always does this upon startup, which seems to @@ -9626,6 +10012,9 @@ static void ca0132_free(struct hda_codec *codec) #ifdef CONFIG_PCI if (spec->mem_base) pci_iounmap(codec->bus->pci, spec->mem_base); +#endif +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) + mutex_destroy(&spec->ae5_strip_mutex); #endif kfree(spec->spec_init_verbs); kfree(codec->spec); From 8bd561f8a6051f4e287dd0d076f17a1af5bff009 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:20:37 +0200 Subject: [PATCH 595/924] ALSA: seq: Drop the bogus RCU guard from clientptr() In the recent refactoring with RCU, clientptr() takes guard(rcu)() around the client table lookup, but the RCU read-side section ends as soon as the function returns, so the returned pointer isn't protected at all. This gives a false impression as if that the callers were safe, and confuse reviewers including Sashiko. Actually, the callers (snd_seq_delete_kernel_client(), snd_seq_kernel_client_ctl() and snd_seq_kernel_client_write_poll()) never relied on any lock; the caller is the owner of the client (a kernel client passing its own id, or a user client via its opened file), hence the client can't be released concurrently by others. So just drop the confusing and useless RCU guard, read the table via rcu_dereference_protected(), and document the lifetime rule. Along with it, fold __clientptr() into its only user client_use_ptr(); the id range is already checked there, and it's never called with clients_lock held, so a plain rcu_dereference() suffices. Fixes: 7a287e4615d6 ("ALSA: seq: Use RCU for the client table") Reported-by: Sashiko Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172051.13240-2-tiwai@suse.de --- sound/core/seq/seq_clientmgr.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 239809ce48d7..ba5619d0b0b0 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -95,23 +95,18 @@ static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client) return snd_seq_total_cells(client->pool) > 0; } -/* return pointer to client structure for specified id; call under RCU read-lock */ -static struct snd_seq_client *__clientptr(int clientid) +/* return pointer to client structure for specified id; + * the caller must guarantee the client's lifetime by itself, as neither RCU + * nor a use_lock reference is taken here. + */ +static struct snd_seq_client *clientptr(int clientid) { if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", clientid); return NULL; } - return rcu_dereference_check(clienttab[clientid], - lockdep_is_held(&clients_lock)); -} - -/* return pointer to client structure for specified id */ -static struct snd_seq_client *clientptr(int clientid) -{ - guard(rcu)(); - return __clientptr(clientid); + return rcu_dereference_protected(clienttab[clientid], true); } static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) @@ -124,7 +119,7 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) return NULL; } scoped_guard(rcu) { - client = __clientptr(clientid); + client = rcu_dereference(clienttab[clientid]); if (client) return snd_seq_client_ref(client); if (clienttablock[clientid]) @@ -159,7 +154,7 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) } } scoped_guard(rcu) { - client = __clientptr(clientid); + client = rcu_dereference(clienttab[clientid]); if (client) return snd_seq_client_ref(client); } From c0a10d0e0e0d35361d2c81e1192db66ba726475d Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:20:38 +0200 Subject: [PATCH 596/924] ALSA: pcm: Fix TOCTOU state overwrite in snd_pcm_drop() snd_pcm_drop() checks the current state at the beginning, and bails out if it's in an invalid state (OPEN or DISCONNECTED). However, since the check is done before the PCM stream lock, this can lead to a Time-of-Check to Time-of-Use (TOCTOU) race against the other forcible state change like the device disconnection like below: CPU 0 CPU 1 ----- ----- snd_pcm_drop() runtime->state check snd_pcm_dev_disconnect() guard(pcm_stream_lock_irq) runtime->state = SNDRV_PCM_STATE_DISCONNECTED guard(pcm_stream_lock_irq) snd_pcm_stop(SNDRV_PCM_STATE_SETUP) <== inconsistent state For avoiding the inconsistent state change, this patch moves the runtime state check inside the stream lock guard. Reported-by: Sashiko Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172051.13240-3-tiwai@suse.de --- sound/core/pcm_native.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 6efaebc7f8b4..defbb2977efe 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2289,11 +2289,11 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream) return -ENXIO; runtime = substream->runtime; + guard(pcm_stream_lock_irq)(substream); if (runtime->state == SNDRV_PCM_STATE_OPEN || runtime->state == SNDRV_PCM_STATE_DISCONNECTED) return -EBADFD; - guard(pcm_stream_lock_irq)(substream); /* resume pause */ if (runtime->state == SNDRV_PCM_STATE_PAUSED) snd_pcm_pause(substream, false); From 27cee4141c8de82cf051104ca830c3a209816033 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:20:39 +0200 Subject: [PATCH 597/924] ALSA: hda: Fix potential UAF for gating jack snd_hda_jack_set_gating_jack() assigns gated and gating variables from the succeeding calls of snd_hda_jack_tbl_get(). This works normally, but the second call might kick off the reallocation of the jack table, hence the first pointer might become invalid, which leads to a UAF. For avoiding that pitfall, this patch changes the code to two phases: the first to only create two entries, and the second phase to get the pointers from the table, so that no relocation can happen any longer. Fixes: 0619ba8c17b1 ("ALSA: hda - Allow jack state to depend on another jack") Reported-by: Sashiko Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172051.13240-4-tiwai@suse.de --- sound/hda/common/jack.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c index 1d6b0f0e6f27..c3efab3b5bfe 100644 --- a/sound/hda/common/jack.c +++ b/sound/hda/common/jack.c @@ -389,12 +389,15 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable); int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid, hda_nid_t gating_nid) { - struct hda_jack_tbl *gated = snd_hda_jack_tbl_new(codec, gated_nid, 0); - struct hda_jack_tbl *gating = - snd_hda_jack_tbl_new(codec, gating_nid, 0); + struct hda_jack_tbl *gated, *gating; WARN_ON(codec->dp_mst); + if (!snd_hda_jack_tbl_new(codec, gated_nid, 0) || + !snd_hda_jack_tbl_new(codec, gating_nid, 0)) + return -ENOMEM; + gated = snd_hda_jack_tbl_get_mst(codec, gated_nid, 0); + gating = snd_hda_jack_tbl_get_mst(codec, gating_nid, 0); if (!gated || !gating) return -EINVAL; From c01fb2ed90c0770560ea9cc1644f205956b26a70 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:20:40 +0200 Subject: [PATCH 598/924] ALSA: usb-audio: Fix invalid UAC2/3 mixer unit matrix evaluation The bitmap matrix in the mixer unit descriptor for UAC2 and UAC3 has rather the size of input-pins x output-pins, while the current USB-audio driver code wrongly assumes the UAC1 bitmap matrix size, which is input-channels x output-pins. That is, when input pins have multiple channels, the column size differs and it leads to the accesses at a wrong position. This patch corrects the access of the bitmap matrix for UAC2/UAC3. For making the code cleaner, split the parser to UAC1 and UAC2/3, too. Reported-by: Sashiko Fixes: 23caaf19b11e ("ALSA: usb-mixer: Add support for Audio Class v2.0") Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172051.13240-5-tiwai@suse.de --- sound/usb/mixer.c | 112 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 34 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index b6e22244e03a..9d8007ea95d5 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -2377,7 +2377,7 @@ static bool mixer_bitmap_overflow(struct uac_mixer_unit_descriptor *desc, */ static void build_mixer_unit_ctl(struct mixer_build *state, struct uac_mixer_unit_descriptor *desc, - int in_pin, int in_ch, int num_outs, + int in_ch, int num_outs, int unitid, struct usb_audio_term *iterm) { struct usb_mixer_elem_info *cval; @@ -2472,34 +2472,13 @@ static int parse_audio_input_terminal(struct mixer_build *state, int unitid, /* * parse a mixer unit */ -static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, - void *raw_desc) +/* UAC1 mixer unit */ +static int parse_audio_mixer_unit_v1(struct mixer_build *state, int unitid, + int input_pins, int num_outs, + struct uac_mixer_unit_descriptor *desc) { - struct uac_mixer_unit_descriptor *desc = raw_desc; struct usb_audio_term iterm; - int input_pins, num_ins, num_outs; - int pin, ich, err; - - err = uac_mixer_unit_get_channels(state, desc); - if (err < 0) { - usb_audio_err(state->chip, - "invalid MIXER UNIT descriptor %d\n", - unitid); - return err; - } - - num_outs = err; - input_pins = desc->bNrInPins; - - if (state->mixer->protocol == UAC_VERSION_2 || - state->mixer->protocol == UAC_VERSION_3) { - if (input_pins * num_outs > 256) { - usb_audio_err(state->chip, - "invalid channels for MIXER UNIT %d: input=%d, output=%d\n", - unitid, input_pins, num_outs); - return -EINVAL; - } - } + int num_ins, pin, ich, och, err; num_ins = 0; ich = 0; @@ -2518,25 +2497,90 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, num_ins, num_outs)) break; for (; ich < num_ins; ich++) { - int och, ich_has_controls = 0; - for (och = 0; och < num_outs; och++) { __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol); - if (check_matrix_bitmap(c, ich, och, num_outs)) { - ich_has_controls = 1; + if (check_matrix_bitmap(c, ich, och, num_outs)) break; - } } - if (ich_has_controls) - build_mixer_unit_ctl(state, desc, pin, ich, num_outs, + if (och < num_outs) + build_mixer_unit_ctl(state, desc, ich, num_outs, unitid, &iterm); } } return 0; } +/* UAC2/UAC3 mixer unit */ +static int parse_audio_mixer_unit_v2(struct mixer_build *state, int unitid, + int input_pins, int num_outs, + struct uac_mixer_unit_descriptor *desc) +{ + struct usb_audio_term iterm; + int pin, och, err; + + if (input_pins * num_outs > 256 || + mixer_bitmap_overflow(desc, state->mixer->protocol, + input_pins, num_outs)) { + usb_audio_err(state->chip, + "invalid channels for MIXER UNIT %d: input=%d, output=%d\n", + unitid, input_pins, num_outs); + return -EINVAL; + } + + for (pin = 0; pin < input_pins; pin++) { + err = parse_audio_unit(state, desc->baSourceID[pin]); + if (err < 0) + continue; + if (!num_outs) + continue; + err = check_input_term(state, desc->baSourceID[pin], &iterm); + if (err < 0) + return err; + + for (och = 0; och < num_outs; och++) { + __u8 *c = uac_mixer_unit_bmControls(desc, + state->mixer->protocol); + + if (check_matrix_bitmap(c, pin, och, num_outs)) + break; + } + if (och < num_outs) + build_mixer_unit_ctl(state, desc, pin, num_outs, + unitid, &iterm); + } + return 0; +} + +static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, + void *raw_desc) +{ + struct uac_mixer_unit_descriptor *desc = raw_desc; + int num_outs; + + num_outs = uac_mixer_unit_get_channels(state, desc); + if (num_outs < 0) { + usb_audio_err(state->chip, + "invalid MIXER UNIT descriptor %d\n", + unitid); + return num_outs; + } + + switch (state->mixer->protocol) { + case UAC_VERSION_1: + default: + return parse_audio_mixer_unit_v1(state, unitid, + desc->bNrInPins, num_outs, + desc); + case UAC_VERSION_2: + case UAC_VERSION_3: + return parse_audio_mixer_unit_v2(state, unitid, + desc->bNrInPins, num_outs, + desc); + } +} + /* * Processing Unit / Extension Unit */ From f701f6738de2d11e206a74d2a81465051dbf1ba3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:20:41 +0200 Subject: [PATCH 599/924] ALSA: usb-audio: Fix data race at mixer_ctl_feature_info() The info callback for USB-audio mixer controls for feature unit has a dynamic initialization of the contents with the check of cval->initialized flag. But, since the info callback may be concurrently called, this may lead to a data race, giving back an inconsistent state. Similarly, get and put callbacks may have concurrent accesses and can get bogus states. For avoiding the data race, introduce a mutex locking for the controls and protect against concurrent info callback calls. Reported-by: Sashiko Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172051.13240-6-tiwai@suse.de --- sound/usb/mixer.c | 14 +++++++++++++- sound/usb/mixer.h | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 9d8007ea95d5..695d51f7aeb4 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1531,6 +1531,7 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int ret; + guard(mutex)(&cval->head.mixer->lock); if (cval->val_type == USB_MIXER_BOOLEAN || cval->val_type == USB_MIXER_INV_BOOLEAN) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; @@ -1565,6 +1566,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int c, cnt, val, err; + guard(mutex)(&cval->head.mixer->lock); ucontrol->value.integer.value[0] = cval->min; if (cval->cmask) { cnt = 0; @@ -1595,10 +1597,12 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); - int max_val = get_max_exposed(cval); + int max_val; int c, cnt, val, oval, err; int changed = 0; + guard(mutex)(&cval->head.mixer->lock); + max_val = get_max_exposed(cval); if (cval->cmask) { cnt = 0; for (c = 0; c < MAX_CHANNELS; c++) { @@ -1646,6 +1650,7 @@ static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, err; + guard(mutex)(&cval->head.mixer->lock); err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); if (err < 0) return filter_error(cval, err); @@ -2592,6 +2597,7 @@ static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int err, val; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &val); if (err < 0) { ucontrol->value.integer.value[0] = cval->min; @@ -2609,6 +2615,7 @@ static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, oval, err; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &oval); if (err < 0) return filter_error(cval, err); @@ -2960,6 +2967,7 @@ static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, err; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &val); if (err < 0) { ucontrol->value.enumerated.item[0] = 0; @@ -2977,6 +2985,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, oval, err; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &oval); if (err < 0) return filter_error(cval, err); @@ -3249,6 +3258,7 @@ static void snd_usb_mixer_free(struct usb_mixer_interface *mixer) } usb_free_urb(mixer->rc_urb); kfree(mixer->rc_setup_packet); + mutex_destroy(&mixer->lock); kfree(mixer); } @@ -3893,6 +3903,8 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif) return -ENOMEM; } + mutex_init(&mixer->lock); + mixer->hostif = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0]; switch (get_iface_desc(mixer->hostif)->bInterfaceProtocol) { case UAC_VERSION_1: diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index cf45c39cbccc..2ff4490f97c2 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -18,6 +18,7 @@ struct usb_mixer_interface { struct usb_host_interface *hostif; struct list_head list; unsigned int ignore_ctl_error; + struct mutex lock; /* lock for feature unit callbacks */ /* UAC2 status interrupt endpoint; owned by mixer.c */ struct urb *urb; /* array[MAX_ID_ELEMS], indexed by unit id */ From 905a07c711baf184d01aebd3cf9028a89bcd8b7f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:20:42 +0200 Subject: [PATCH 600/924] ALSA: pcmtest: Fix a bogus pointer read in snd_pcmtst_pcm_pointer() Sashiko reported a potential bogus value for a pcmtest driver when a concurrent call to PCM pointer is invoked while the pcmtest's timer callback is running: since the position is updated in the timer callback without locking, the following wrapping in inc_buf_pos() might be screwed up: if (v_iter->buf_pos >= bytes) v_iter->buf_pos %= bytes; Although it was reported as an OOB, the actual return is corrected inside buffer_size, so no corruption is expected in this scenario, but an error message could show a bogus value. Also, the whole state is read and modified locklessly in the timer callback, which can be racy against the pause operation, too. For avoiding those races, simply put the PCM stream lock in the timer callback (while the snd_pcm_period_elapsed() must be changed to its *_under_stream_lock() variant for avoiding the deadlock). Reported-by: Sashiko Fixes: 315a3d57c64c ("ALSA: Implement the new Virtual PCM Test Driver") Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172051.13240-7-tiwai@suse.de --- sound/drivers/pcmtest.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c index 186e982d42e1..fea9580593e6 100644 --- a/sound/drivers/pcmtest.c +++ b/sound/drivers/pcmtest.c @@ -345,6 +345,7 @@ static void timer_timeout(struct timer_list *data) v_iter = timer_container_of(v_iter, data, timer_instance); substream = v_iter->substream; + guard(pcm_stream_lock_irqsave)(substream); if (v_iter->suspend) return; @@ -358,7 +359,7 @@ static void timer_timeout(struct timer_list *data) v_iter->period_pos += v_iter->b_rw; if (v_iter->period_pos >= v_iter->period_bytes) { v_iter->period_pos %= v_iter->period_bytes; - snd_pcm_period_elapsed(substream); + snd_pcm_period_elapsed_under_stream_lock(substream); } if (!v_iter->suspend) From f224bd39d2bf95ebe5f81e710293cabac3d22636 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:20:43 +0200 Subject: [PATCH 601/924] ALSA: usb-audio: Fix mixer bitmap cache over 32 channels USB-audio driver keeps the bitmap for the cached mixer channels, but since a 32bit integer is used, it's currently broken for over 32 channels. As the driver is supposed to support up to 64 channels, this patch extends the bitmap properly -- now to be more flexible, use the standard bitmap instead of the manual bit shifts. Some checks for master channels are replaced in a slightly different manner (checking the channel index 0) instead of the full cval->cached check, so that it fits better in the bitmap helper usage. Fixes: 16ee07bfa935 ("ALSA: usb-audio: Extend max number of channels to 64") Reported-by: Sashiko Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172051.13240-8-tiwai@suse.de --- sound/usb/mixer.c | 20 ++++++++++---------- sound/usb/mixer.h | 2 +- sound/usb/mixer_scarlett.c | 18 +++++++++--------- sound/usb/mixer_us16x08.c | 20 ++++++++++---------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 695d51f7aeb4..49a8a26f1a96 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -423,7 +423,7 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, { int err; - if (cval->cached & BIT(channel)) { + if (test_bit(channel, cval->cached)) { *value = cval->cache_val[index]; return 0; } @@ -440,7 +440,7 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, cval->control, channel, err); return err; } - cval->cached |= BIT(channel); + set_bit(channel, cval->cached); cval->cache_val[index] = *value; return 0; } @@ -604,7 +604,7 @@ int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, value); if (err < 0) return err; - cval->cached |= BIT(channel); + set_bit(channel, cval->cached); cval->cache_val[index] = value; return 0; } @@ -1449,7 +1449,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, * properly. */ if (ret) - cval->cached = 0; + bitmap_zero(cval->cached, MAX_CHANNELS + 1); cval->initialized = 1; } @@ -3669,7 +3669,7 @@ void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid) info = mixer_elem_list_to_info(list); /* invalidate cache, so the value is read from the device */ if (!info->get_cur_broken) - info->cached = 0; + bitmap_zero(info->cached, MAX_CHANNELS + 1); snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &list->kctl->id); } @@ -3736,7 +3736,7 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, __u8 channel = value & 0xff; unsigned int count = 0; - if (channel >= MAX_CHANNELS) { + if (channel > MAX_CHANNELS) { usb_audio_dbg(mixer->chip, "%s(): bogus channel number %d\n", __func__, channel); @@ -3768,9 +3768,9 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, /* invalidate cache, so the value is read from the device */ if (!info->get_cur_broken) { if (channel) - info->cached &= ~BIT(channel); + clear_bit(channel, info->cached); else /* master channel */ - info->cached = 0; + bitmap_zero(info->cached, MAX_CHANNELS + 1); } snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, @@ -4008,7 +4008,7 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list) for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & BIT(c))) continue; - if (cval->cached & BIT(c + 1)) { + if (test_bit(c + 1, cval->cached)) { err = snd_usb_set_cur_mix_value(cval, c + 1, idx, cval->cache_val[idx]); if (err < 0) @@ -4018,7 +4018,7 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list) } } else { /* master */ - if (cval->cached) + if (test_bit(0, cval->cached)) snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val); } diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 2ff4490f97c2..0d0a8b343756 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -93,7 +93,7 @@ struct usb_mixer_elem_info { int min, max, res; int max_exposed; /* control API exposes the value in 0..max_exposed */ int dBmin, dBmax; - int cached; + DECLARE_BITMAP(cached, MAX_CHANNELS + 1); int cache_val[MAX_CHANNELS]; u8 initialized; u8 min_mute; diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c index 369968565c19..6808730ef047 100644 --- a/sound/usb/mixer_scarlett.c +++ b/sound/usb/mixer_scarlett.c @@ -292,7 +292,7 @@ static int forte_get_ctl_value(struct usb_mixer_elem_info *elem, int *value) /* Device may not support reading input controls. * Return cached value or default to avoid blocking module load. */ - if (elem->cached) + if (test_bit(0, elem->cached)) *value = elem->cache_val[0]; else *value = 0; /* Default: first option */ @@ -353,7 +353,7 @@ static int forte_input_gain_put(struct snd_kcontrol *kctl, err = forte_set_ctl_value(elem, val); if (err < 0) return err; - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -364,7 +364,7 @@ static int forte_input_gain_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) forte_set_ctl_value(elem, *elem->cache_val); return 0; } @@ -405,7 +405,7 @@ static int forte_ctl_enum_put(struct snd_kcontrol *kctl, err = forte_set_ctl_value(elem, val); if (err < 0) return err; - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -416,7 +416,7 @@ static int forte_ctl_enum_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) forte_set_ctl_value(elem, *elem->cache_val); return 0; } @@ -454,7 +454,7 @@ static int forte_ctl_switch_put(struct snd_kcontrol *kctl, err = forte_set_ctl_value(elem, val); if (err < 0) return err; - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -465,7 +465,7 @@ static int forte_ctl_switch_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) forte_set_ctl_value(elem, *elem->cache_val); return 0; } @@ -532,7 +532,7 @@ static int scarlett_ctl_resume(struct usb_mixer_elem_list *list) int i; for (i = 0; i < elem->channels; i++) - if (elem->cached & (1 << i)) + if (test_bit(i, elem->cached)) snd_usb_set_cur_mix_value(elem, i, i, elem->cache_val[i]); return 0; @@ -692,7 +692,7 @@ static int scarlett_ctl_enum_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) snd_usb_set_cur_mix_value(elem, 0, 0, *elem->cache_val); return 0; } diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c index 14fb1ad764a7..8e5ccd3282a7 100644 --- a/sound/usb/mixer_us16x08.c +++ b/sound/usb/mixer_us16x08.c @@ -236,7 +236,7 @@ static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -295,7 +295,7 @@ static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -336,7 +336,7 @@ static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -404,7 +404,7 @@ static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -508,7 +508,7 @@ static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol, } store->val[val_idx][index] = val; - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -567,7 +567,7 @@ static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -631,7 +631,7 @@ static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol, store->val[b_idx][p_idx][index] = val; /* store new value in EQ band cache */ - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -1352,7 +1352,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) } for (i = 0; i < 8; i++) elem->cache_val[i] = i < 2 ? i : i + 2; - elem->cached = 0xff; + bitmap_set(elem->cached, 0, 8); /* create compressor mixer elements */ comp_store = snd_us16x08_create_comp_store(); @@ -1374,7 +1374,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) if (err < 0) return err; elem->cache_val[0] = master_controls[i].default_val; - elem->cached = 1; + set_bit(0, elem->cached); } /* add channel controls */ @@ -1394,7 +1394,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) elem->cache_val[j] = channel_controls[i].default_val; } - elem->cached = 0xffff; + bitmap_set(elem->cached, 0, SND_US16X08_MAX_CHANNELS); } /* create eq store */ From a6913835735469751f56473f5212eb291a4805e7 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:20:44 +0200 Subject: [PATCH 602/924] ALSA: core: Add missing barriers for power_ref vs card->shutdown snd_power_ref_and_wait() increments card->power_ref and then reads card->shutdown (and the power state) locklessly, while snd_card_disconnect() sets card->shutdown and then waits for power_ref to drop to zero via snd_power_sync_ref(). Due to the missing barrier on both sides, the caller proceeds with shutdown still seen as false, while the disconnect side sees a zero refcount and continues tearing down incorrectly. For addressing the potential race, add smp_mb__after_atomic() after the refcount increment and smp_mb() before snd_power_sync_ref() in snd_card_disconnect(). Fixes: e94fdbd7b25d ("ALSA: control: Track in-flight control read/write/tlv accesses") Reported-by: Sashiko Assisted-by: LLM Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172051.13240-9-tiwai@suse.de --- sound/core/init.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/core/init.c b/sound/core/init.c index 7878c0e6103c..dbe2acfa59fe 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -535,6 +535,8 @@ void snd_card_disconnect(struct snd_card *card) clear_bit(card->number, snd_cards_lock); } + /* order card->shutdown store against power_ref read */ + smp_mb(); snd_power_sync_ref(card); } EXPORT_SYMBOL(snd_card_disconnect); @@ -1152,6 +1154,8 @@ EXPORT_SYMBOL(snd_card_file_remove); int snd_power_ref_and_wait(struct snd_card *card) { snd_power_ref(card); + /* order power_ref increment against card->shutdown read */ + smp_mb__after_atomic(); if (snd_power_get_state(card) != SNDRV_CTL_POWER_D0) { wait_event_cmd(card->power_sleep, card->shutdown || From 9d3e19c192b97b4c4c0abcafdbf1edb1e56cee88 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:25:19 +0200 Subject: [PATCH 603/924] ALSA: seq: Fix missing direction and ump_group handling in 32bit compat ioctl I forgot to cover the two new fields, direction and ump_group, in struct snd_seq_port_info for the 32bit compat ioctls of SNDRV_SEQ_IOCTL_GET_PORT_INFO & co, which ended up with the garbage copies in those fields. Add the handling of those two fields in the compat layer. Also, avoid over-copying the 64bit snd_seq_port_info but only the first part (before the field kernel); this avoids the copy of kernel-space garbage value in the reserved field. Fixes: ff166a9d19fa ("ALSA: seq: Add port direction to snd_seq_port_info") Reported-by: Sashiko Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172533.14667-2-tiwai@suse.de --- sound/core/seq/seq_compat.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c index 80110501da6f..57aaa39afb9d 100644 --- a/sound/core/seq/seq_compat.c +++ b/sound/core/seq/seq_compat.c @@ -25,7 +25,9 @@ struct snd_seq_port_info32 { u32 kernel; /* reserved for kernel use (must be NULL) */ u32 flags; /* misc. conditioning */ unsigned char time_queue; /* queue # for timestamping */ - char reserved[59]; /* for future use */ + unsigned char direction; /* port usage direction (r/w/bidir) */ + unsigned char ump_group; /* 0 = UMP EP (no conversion), 1-16 = UMP group number */ + char reserved[57]; /* for future use */ }; static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned int cmd, @@ -40,7 +42,9 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned if (copy_from_user(data, data32, sizeof(*data32)) || get_user(data->flags, &data32->flags) || - get_user(data->time_queue, &data32->time_queue)) + get_user(data->time_queue, &data32->time_queue) || + get_user(data->direction, &data32->direction) || + get_user(data->ump_group, &data32->ump_group)) return -EFAULT; data->kernel = NULL; @@ -50,9 +54,12 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned if (err < 0) return err; - if (copy_to_user(data32, data, sizeof(*data32)) || + if (copy_to_user(data32, data, + offsetof(struct snd_seq_port_info32, kernel)) || put_user(data->flags, &data32->flags) || - put_user(data->time_queue, &data32->time_queue)) + put_user(data->time_queue, &data32->time_queue) || + put_user(data->direction, &data32->direction) || + put_user(data->ump_group, &data32->ump_group)) return -EFAULT; return err; From d0f2ca62e89f3c666d0ec203eaa23b8d965e5443 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:25:20 +0200 Subject: [PATCH 604/924] ALSA: pcmtest: Fix leak at probe error When an error happens at probe of pcmtest driver before snd_card_register() succeeds, we have to release the card explicitly, but it's not done, leading to a potential memory leak. Also, pcmtest driver manages struct pcmtst object unnecessarily complicatedly by a dynamic allocation and release. This patch fixes and cleans up the resource management: - Use card's private_data allocation for struct sndpcmtst at the card creation, which is released automatically together with the card - Since the allocation of pcmtest is gone, snd_pcmtst_create() is rather pointless, so just directly calling snd_pcmtst_new_pcm() - Use the new auto-clean mechanism to handle the error case at probe. - Many release handlers became superfluous and dropped In the end we got a good amount of code reduction. Fixes: 315a3d57c64c ("ALSA: Implement the new Virtual PCM Test Driver") Reported-by: Sashiko Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172533.14667-3-tiwai@suse.de --- sound/drivers/pcmtest.c | 66 +++++++---------------------------------- 1 file changed, 10 insertions(+), 56 deletions(-) diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c index fea9580593e6..b4e333b4c438 100644 --- a/sound/drivers/pcmtest.c +++ b/sound/drivers/pcmtest.c @@ -446,20 +446,6 @@ static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substr return bytes_to_frames(substream->runtime, v_iter->buf_pos); } -static int snd_pcmtst_free(struct pcmtst *pcmtst) -{ - if (!pcmtst) - return 0; - kfree(pcmtst); - return 0; -} - -// These callbacks are required, but empty - all freeing occurs in pdev_remove -static int snd_pcmtst_dev_free(struct snd_device *device) -{ - return 0; -} - static void pcmtst_pdev_release(struct device *dev) { } @@ -563,40 +549,9 @@ static int snd_pcmtst_new_pcm(struct pcmtst *pcmtst) return err; } -static int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev, - struct pcmtst **r_pcmtst) -{ - struct pcmtst *pcmtst; - int err; - static const struct snd_device_ops ops = { - .dev_free = snd_pcmtst_dev_free, - }; - - pcmtst = kzalloc_obj(*pcmtst); - if (!pcmtst) - return -ENOMEM; - pcmtst->card = card; - pcmtst->pdev = pdev; - - err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pcmtst, &ops); - if (err < 0) - goto _err_free_chip; - - err = snd_pcmtst_new_pcm(pcmtst); - if (err < 0) - goto _err_free_chip; - - *r_pcmtst = pcmtst; - return 0; - -_err_free_chip: - snd_pcmtst_free(pcmtst); - return err; -} - static int pcmtst_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct pcmtst *pcmtst; int err; @@ -604,10 +559,16 @@ static int pcmtst_probe(struct platform_device *pdev) if (err) return err; - err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, + sizeof(*pcmtst), &card); if (err < 0) return err; - err = snd_pcmtst_create(card, pdev, &pcmtst); + + pcmtst = card->private_data; + pcmtst->card = card; + pcmtst->pdev = pdev; + + err = snd_pcmtst_new_pcm(pcmtst); if (err < 0) return err; @@ -620,17 +581,11 @@ static int pcmtst_probe(struct platform_device *pdev) return err; platform_set_drvdata(pdev, pcmtst); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static void pdev_remove(struct platform_device *pdev) -{ - struct pcmtst *pcmtst = platform_get_drvdata(pdev); - - snd_pcmtst_free(pcmtst); -} - static struct platform_device pcmtst_pdev = { .name = "pcmtest", .dev.release = pcmtst_pdev_release, @@ -638,7 +593,6 @@ static struct platform_device pcmtst_pdev = { static struct platform_driver pcmtst_pdrv = { .probe = pcmtst_probe, - .remove = pdev_remove, .driver = { .name = "pcmtest", }, From 7973a5fb95d106369b8e77ac88c38136b2aeb865 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:25:21 +0200 Subject: [PATCH 605/924] ALSA: pcmtest: Use platform_device_register_simple() It's more standard for this kind of device that doesn't need any special handling. This allows us to delete the stub release callback. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172533.14667-4-tiwai@suse.de --- sound/drivers/pcmtest.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c index b4e333b4c438..393f132a438b 100644 --- a/sound/drivers/pcmtest.c +++ b/sound/drivers/pcmtest.c @@ -446,10 +446,6 @@ static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substr return bytes_to_frames(substream->runtime, v_iter->buf_pos); } -static void pcmtst_pdev_release(struct device *dev) -{ -} - static int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; @@ -586,15 +582,14 @@ static int pcmtst_probe(struct platform_device *pdev) return 0; } -static struct platform_device pcmtst_pdev = { - .name = "pcmtest", - .dev.release = pcmtst_pdev_release, -}; +#define SND_PCMTEST_DRIVER "pcmtest" + +static struct platform_device *pcmtst_pdev; static struct platform_driver pcmtst_pdrv = { .probe = pcmtst_probe, .driver = { - .name = "pcmtest", + .name = SND_PCMTEST_DRIVER, }, }; @@ -706,12 +701,14 @@ static int __init mod_init(void) err = init_debug_files(buf_allocated); if (err) goto err_free_patterns; - err = platform_device_register(&pcmtst_pdev); - if (err) + pcmtst_pdev = platform_device_register_simple(SND_PCMTEST_DRIVER, -1, NULL, 0); + if (IS_ERR(pcmtst_pdev)) { + err = PTR_ERR(pcmtst_pdev); goto err_clear_debug; + } err = platform_driver_register(&pcmtst_pdrv); if (err) { - platform_device_unregister(&pcmtst_pdev); + platform_device_unregister(pcmtst_pdev); goto err_clear_debug; } @@ -730,7 +727,7 @@ static void __exit mod_exit(void) free_pattern_buffers(); platform_driver_unregister(&pcmtst_pdrv); - platform_device_unregister(&pcmtst_pdev); + platform_device_unregister(pcmtst_pdev); } MODULE_DESCRIPTION("Virtual ALSA driver for PCM testing/fuzzing"); From a3e8eec7692eac706ba0934fa18453e46acd1a08 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:25:22 +0200 Subject: [PATCH 606/924] ALSA: info: Fix memory leak at card removal The fix for potential deadlock at disconnection by the commit c7a606519533 ("ALSA: info: Fix potential deadlock at disconnection") clears card->proc_root pointer at snd_info_card_disconnect(), in order just to be sure. But this leads to a memory leak of card->proc_root content that should have been released at a later call of snd_info_card_free(). For addressing the memory leak, simply drop the card->proc_root clearance at snd_info_card_disconnect(). Since card->proc_root might be still looked at in snd_info_card_id_change() that may be running concurrently during the connection procedure, add a sanity check there, too. Fixes: c7a606519533 ("ALSA: info: Fix potential deadlock at disconnection") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20261006174002.703431-1-tiwai%40suse.de Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172533.14667-5-tiwai@suse.de --- sound/core/info.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/core/info.c b/sound/core/info.c index 3b8ccda04e1a..c4d57d0809ad 100644 --- a/sound/core/info.c +++ b/sound/core/info.c @@ -528,6 +528,8 @@ int snd_info_card_register(struct snd_card *card) void snd_info_card_id_change(struct snd_card *card) { guard(mutex)(&info_mutex); + if (card->shutdown) + return; if (card->proc_root_link) { proc_remove(card->proc_root_link); card->proc_root_link = NULL; @@ -555,7 +557,6 @@ void snd_info_card_disconnect(struct snd_card *card) if (card->proc_root) snd_info_clear_entries(card->proc_root); card->proc_root_link = NULL; - card->proc_root = NULL; } /* From f7faf5d9b5668919cae84a6247f8f7b4e2c0227c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:25:23 +0200 Subject: [PATCH 607/924] ALSA: aloop: Avoid a bad mixure of guard() and goto The rewrite of aloop driver code using guard() leaded to a mixture of guard() and some goto; although it currently works, it isn't really a good match. For avoiding the bad match, just move the code using goto into a function and wrap it with guard(). Fixes: ebd9b6c91d4e ("ALSA: aloop: Use guard() for mutex locks") Reported-by: Sashiko Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172533.14667-6-tiwai@suse.de --- sound/drivers/aloop.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index 5f0e2dc96769..0aaa64b189da 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -1375,7 +1375,7 @@ static const struct loopback_ops loopback_snd_timer_ops = { .dpcm_info = loopback_snd_timer_dpcm_info, }; -static int loopback_open(struct snd_pcm_substream *substream) +static int __loopback_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct loopback *loopback = substream->private_data; @@ -1384,7 +1384,6 @@ static int loopback_open(struct snd_pcm_substream *substream) int err = 0; int dev = get_cable_index(substream); - guard(mutex)(&loopback->cable_lock); dpcm = kzalloc_obj(*dpcm); if (!dpcm) return -ENOMEM; @@ -1479,6 +1478,14 @@ static int loopback_open(struct snd_pcm_substream *substream) return err; } +static int loopback_open(struct snd_pcm_substream *substream) +{ + struct loopback *loopback = substream->private_data; + + guard(mutex)(&loopback->cable_lock); + return __loopback_open(substream); +} + static int loopback_close(struct snd_pcm_substream *substream) { struct loopback *loopback = substream->private_data; From 344e74ac50aff89674a1bea153f008bfb31ed952 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:25:24 +0200 Subject: [PATCH 608/924] ALSA: core: Fix leaks at snd_card_init() error paths When the snd_card object is initialized internally in snd_card_init(), the error path of card->value_buf allocation returns straightly as an error without invoking the destructor, which would leak resources. Also, the other error paths in snd_card_init() release the card object only via put_device(), and this misses the cleanups that are done in snd_card_disconnect(); namely, the reserved slot bit in snd_cards_lock is never cleared, so the card index remains occupied, and the debugfs directory created for the card is left over. Fix them by calling snd_card_free() at the error path, which performs both the disconnection and the release properly. As snd_card_free() calls snd_device_free_all() internally, the explicit call is dropped, and the error labels are unified. Fixes: 84446536f63d ("ALSA: control: Verify put() result when in debug mode") Reported-by: Sashiko Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172533.14667-7-tiwai@suse.de --- sound/core/init.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/core/init.c b/sound/core/init.c index dbe2acfa59fe..9deb5e920d0d 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -334,7 +334,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, err = snd_info_card_create(card); if (err < 0) { dev_err(parent, "unable to create card info\n"); - goto __error_ctl; + goto __error; } #ifdef CONFIG_SND_DEBUG @@ -343,16 +343,16 @@ static int snd_card_init(struct snd_card *card, struct device *parent, #endif #ifdef CONFIG_SND_CTL_DEBUG card->value_buf = kmalloc_obj(*card->value_buf); - if (!card->value_buf) - return -ENOMEM; + if (!card->value_buf) { + err = -ENOMEM; + goto __error; + } #endif return 0; - __error_ctl: - snd_device_free_all(card); __error: - put_device(&card->card_dev); - return err; + snd_card_free(card); + return err; } /** From 7e1f297fcef8fc3376774ed40e5f2b5ad9098e43 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:25:25 +0200 Subject: [PATCH 609/924] ALSA: seq: Don't lose partial read failure snd_seq_read() returns an error even if one or more events have been successfully read before the error (except for -EAGAIN case), but this rather doesn't follow the POSIX standard that wants the processed bytes. Change the behavior to return the processed bytes when it's positive, instead. Reported-by: Sashiko Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007172533.14667-8-tiwai@suse.de --- sound/core/seq/seq_clientmgr.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index ba5619d0b0b0..0711a9e23b5f 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -480,11 +480,11 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, if (err < 0) { if (cell) snd_seq_fifo_cell_putback(fifo, cell); - if (err == -EAGAIN && result > 0) - err = 0; } - return (err < 0) ? err : result; + if (result > 0) + return result; + return err < 0 ? err : 0; } From 5676e013f16ac4b616d433af24647d0ce4b067aa Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:10:54 +0200 Subject: [PATCH 610/924] ALSA: hda: Use linked list for jack table So far we've used snd_array infrastructure for managing the hda_jack_tbl elements, but it turned out that this may lead to a UAF when a different order of destruction procedure is applied; namely, each hda_jack_tbl object creates a snd_jack object and sets the private_data pointing to the hda_jack_tbl, and private_free to release it. It looks harmless, but since snd_array may reallocate the whole table at growing, the formerly referred jack pointer may become stale. Fortunately, currently there is no call pattern that triggers this issue, so it's only hypothetical, but we should address it in anyway. There are several ways to address this, and at this time, we rather redesign the hda_jack_tbl allocations: instead of snd_array(), do a normal kmalloc() + linked list. There will be more kmalloc calls than the original code, but as the number of jacks are usually at most handful, it must not be a problem. This change allows us the persistent jack pointer, so no stale pointer access can happen any longer. This also fixes another reported issue about the stale jack pointer reference in snd_hda_jack_tbl_new(), too. Fixes: 31ef22579302 ("ALSA: hda - Integrate input-jack stuff into kctl-jack") Reported-by: Sashiko Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007171057.7462-1-tiwai@suse.de --- include/sound/hda_codec.h | 6 ++- sound/hda/common/codec.c | 1 - sound/hda/common/hda_jack.h | 1 + sound/hda/common/jack.c | 79 ++++++++++++++++++--------------- sound/soc/sof/intel/hda-codec.c | 4 +- 5 files changed, 51 insertions(+), 40 deletions(-) diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index 1d05f991f2ce..c7f29ad64e02 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -28,6 +28,7 @@ struct hda_codec; struct hda_pcm; struct hda_pcm_stream; struct hda_codec_ops; +struct hda_jack_tbl; /* * codec bus @@ -274,7 +275,8 @@ struct hda_codec { struct hda_codec *codec, hda_nid_t nid); /* jack detection */ - struct snd_array jacktbl; + struct hda_jack_tbl *jacktbl, *jacktbl_last; /* linked list head/tail */ + int jacktbl_used; /* number of jacktbl elements */ unsigned long jackpoll_interval; /* In jiffies. Zero means no poll, rely on unsol events */ struct delayed_work jackpoll_work; @@ -519,7 +521,7 @@ void snd_hda_update_power_acct(struct hda_codec *codec); static inline bool hda_codec_need_resume(struct hda_codec *codec) { - return !codec->relaxed_resume && codec->jacktbl.used; + return !codec->relaxed_resume && codec->jacktbl_used; } /* diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index c61fd79c48f3..b79346d01021 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -921,7 +921,6 @@ snd_hda_codec_device_init(struct hda_bus *bus, unsigned int codec_addr, snd_array_init(&codec->driver_pins, sizeof(struct hda_pincfg), 16); snd_array_init(&codec->cvt_setups, sizeof(struct hda_cvt_setup), 8); snd_array_init(&codec->spdif_out, sizeof(struct hda_spdif_out), 16); - snd_array_init(&codec->jacktbl, sizeof(struct hda_jack_tbl), 16); snd_array_init(&codec->verbs, sizeof(struct hda_verb *), 8); INIT_LIST_HEAD(&codec->conn_list); INIT_LIST_HEAD(&codec->pcm_list_head); diff --git a/sound/hda/common/hda_jack.h b/sound/hda/common/hda_jack.h index e9b9970c59ed..a06b315e4b05 100644 --- a/sound/hda/common/hda_jack.h +++ b/sound/hda/common/hda_jack.h @@ -44,6 +44,7 @@ struct hda_jack_tbl { int type; int button_state; struct snd_jack *jack; + struct hda_jack_tbl *next; }; struct hda_jack_keymap { diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c index c3efab3b5bfe..6b8aa1a6c008 100644 --- a/sound/hda/common/jack.c +++ b/sound/hda/common/jack.c @@ -76,12 +76,11 @@ static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id) struct hda_jack_tbl * snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - if (!nid || !jack) + if (!nid) return NULL; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid == nid && jack->dev_id == dev_id) return jack; return NULL; @@ -98,12 +97,11 @@ struct hda_jack_tbl * snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, unsigned char tag, int dev_id) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - if (!tag || !jack) + if (!tag) return NULL; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->tag == tag && jack->dev_id == dev_id) return jack; return NULL; @@ -113,12 +111,11 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag); static struct hda_jack_tbl * any_jack_tbl_get_from_nid(struct hda_codec *codec, hda_nid_t nid) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - if (!nid || !jack) + if (!nid) return NULL; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid == nid) return jack; return NULL; @@ -142,12 +139,19 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) if (jack) return jack; - jack = snd_array_new(&codec->jacktbl); + jack = kzalloc_obj(*jack); if (!jack) return NULL; jack->nid = nid; jack->dev_id = dev_id; jack->jack_dirty = 1; + if (!codec->jacktbl) + codec->jacktbl = jack; + else + codec->jacktbl_last->next = jack; + codec->jacktbl_last = jack; + codec->jacktbl_used++; + if (existing_nid_jack) { jack->tag = existing_nid_jack->tag; @@ -158,7 +162,7 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) */ jack->jack_detect = existing_nid_jack->jack_detect; } else { - jack->tag = codec->jacktbl.used; + jack->tag = codec->jacktbl_used; } return jack; @@ -166,10 +170,9 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) void snd_hda_jack_tbl_disconnect(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - for (i = 0; i < codec->jacktbl.used; i++, jack++) { + for (jack = codec->jacktbl; jack; jack = jack->next) { if (!codec->bus->shutdown && jack->jack) snd_device_disconnect(codec->card, jack->jack); } @@ -177,22 +180,31 @@ void snd_hda_jack_tbl_disconnect(struct hda_codec *codec) void snd_hda_jack_tbl_clear(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack, *jack_next; - for (i = 0; i < codec->jacktbl.used; i++, jack++) { + for (jack = codec->jacktbl; jack; jack = jack_next) { struct hda_jack_callback *cb, *next; - /* free jack instances manually when clearing/reconfiguring */ - if (!codec->bus->shutdown && jack->jack) - snd_device_free(codec->card, jack->jack); + jack_next = jack->next; + + if (jack->jack) { + /* fingers away from stale data */ + jack->jack->private_data = NULL; + jack->jack->private_free = NULL; + /* free jack instances manually when clearing/reconfiguring */ + if (!codec->bus->shutdown) + snd_device_free(codec->card, jack->jack); + } for (cb = jack->callback; cb; cb = next) { next = cb->next; kfree(cb); } + kfree(jack); } - snd_array_free(&codec->jacktbl); + + codec->jacktbl = codec->jacktbl_last = NULL; + codec->jacktbl_used = 0; } #define get_jack_plug_state(sense) !!(sense & AC_PINSENSE_PRESENCE) @@ -238,10 +250,9 @@ static void jack_detect_update(struct hda_codec *codec, */ void snd_hda_jack_set_dirty_all(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid) jack->jack_dirty = 1; } @@ -478,19 +489,17 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_set_button_state); void snd_hda_jack_report_sync(struct hda_codec *codec) { struct hda_jack_tbl *jack; - int i, state; + int state; /* update all jacks at first */ - jack = codec->jacktbl.list; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid) jack_detect_update(codec, jack); /* report the updated jacks; it's done after updating all jacks * to make sure that all gating jacks properly have been set */ - jack = codec->jacktbl.list; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid) { if (!jack->jack || jack->block_report) continue; @@ -758,10 +767,10 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_unsol_event); */ void snd_hda_jack_poll_all(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i, changes = 0; + struct hda_jack_tbl *jack; + int changes = 0; - for (i = 0; i < codec->jacktbl.used; i++, jack++) { + for (jack = codec->jacktbl; jack; jack = jack->next) { unsigned int old_sense; if (!jack->nid || !jack->jack_dirty || jack->phantom_jack) continue; diff --git a/sound/soc/sof/intel/hda-codec.c b/sound/soc/sof/intel/hda-codec.c index fd371850b0d6..91f7ca22a4a7 100644 --- a/sound/soc/sof/intel/hda-codec.c +++ b/sound/soc/sof/intel/hda-codec.c @@ -89,7 +89,7 @@ void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev, bool enable) list_for_each_codec(codec, hbus) { /* only set WAKEEN when needed for HDaudio codecs */ mask |= BIT(codec->core.addr); - if (codec->jacktbl.used) + if (codec->jacktbl_used) val |= BIT(codec->core.addr); } } else { @@ -118,7 +118,7 @@ void hda_codec_jack_check(struct snd_sof_dev *sdev) * Wake up all jack-detecting codecs regardless whether an event * has been recorded in STATESTS */ - if (codec->jacktbl.used) + if (codec->jacktbl_used) pm_request_resume(&codec->core.dev); } EXPORT_SYMBOL_NS_GPL(hda_codec_jack_check, "SND_SOC_SOF_HDA_AUDIO_CODEC"); From 3af4ed9f927c469f23d03a76e3b438ed09715bd3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 19:12:29 +0200 Subject: [PATCH 611/924] ALSA: hda: Avoid non-operational GPU warning at regular device removal When a hda-intel driver is removed manually and it has a GPU with switcheroo, it always gives a warning like "GPU sound probed, but not operational:..." no matter whether GPU has been actually operational or not. This warning was originally put for machines that really suffer from the non-operational GPU HDMI audio, but it's seen at the common path a normal device removal also goes over. For avoiding a confusing warning, move the check and the warning at the error path of azx_probe_continue() that is the point where the non-functional GPU gets detected and given up. Fixes: 2b360ba9a493 ("ALSA: hda: intel: Fix Optimus when GPU has no sound") Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007171231.8147-1-tiwai@suse.de --- sound/hda/controllers/intel.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 24015c73a67f..068fc522658a 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -1380,7 +1380,6 @@ static void azx_free(struct azx *chip) * actually stop the chip) to allow GPU to suspend via * vga_switcheroo, and print a warning. */ - dev_warn(&pci->dev, "GPU sound probed, but not operational: please add a quirk to driver_denylist\n"); pm_runtime_disable(&pci->dev); pm_runtime_set_suspended(&pci->dev); pm_runtime_enable(&pci->dev); @@ -2448,6 +2447,8 @@ static int azx_probe_continue(struct azx *chip) out_free: if (err < 0) { pci_set_drvdata(pci, NULL); + if (hda->vga_switcheroo_registered) + dev_warn(&pci->dev, "GPU sound probed, but not operational: please add a quirk to driver_denylist\n"); snd_card_free(chip->card); return err; } From f3607b6952ca9a5638eeef4f301daa7933ce24b1 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 23:56:46 +0200 Subject: [PATCH 612/924] ALSA: hda: Stop unsol events and jack polling before codec unbind At unbinding a codec driver, hda_codec_driver_remove() calls the codec's remove callback that releases the driver resources, followed by snd_hda_codec_cleanup_for_unbind(). Meanwhile, the unsolicited events are processed asynchronously in bus->unsol_work, and snd_hdac_bus_process_unsol_events() checks only codec->registered flag before calling the driver's unsol_event callback without the lock. Since codec->registered is cleared only in snd_hda_codec_cleanup_for_unbind(), and there is no flush of the unsol work at unbinding, the unsol event handler may run concurrently during the running remove callback, which may lead to a UAF. The same problem applies to the jack polling work, which is canceled only after the remove callback. For addressing those races, introduce a new flag unsol_disabled to hdac_device, to be checked it in the unsol event worker, while a new helper snd_hdac_device_disable_unsol() sets this flag and flushes the pending unsol work. The helper is called at hda_codec_driver_remove() together with the cancel of jackpoll_work to assure that no asynchronous jack handling can run. The flag is cleared again at probing the codec driver, while the events are still blocked by the registered flag until the codec gets registered. Reported-by: Sashiko Assisted-by: LLM Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007215650.159871-2-tiwai@suse.de --- include/sound/hdaudio.h | 2 ++ sound/hda/common/bind.c | 7 +++++++ sound/hda/core/bus.c | 21 ++++++++++++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 4cbbb1744740..285f7c4d262d 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -97,6 +97,7 @@ struct hdac_device { bool caps_overwriting:1; /* caps overwrite being in process */ bool cache_coef:1; /* cache COEF read/write too */ unsigned int registered:1; /* codec was registered */ + bool unsol_disabled; /* unsol events blocked; protected by bus->reg_lock */ }; /* device/driver type used for matching */ @@ -123,6 +124,7 @@ int snd_hdac_device_init(struct hdac_device *dev, struct hdac_bus *bus, const char *name, unsigned int addr); void snd_hdac_device_exit(struct hdac_device *dev); int snd_hdac_device_register(struct hdac_device *codec); +void snd_hdac_device_disable_unsol(struct hdac_device *codec); void snd_hdac_device_unregister(struct hdac_device *codec); int snd_hdac_device_set_chip_name(struct hdac_device *codec, const char *name); int snd_hdac_codec_modalias(const struct hdac_device *hdac, char *buf, size_t size); diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c index 6a728a773556..4772ca154a29 100644 --- a/sound/hda/common/bind.c +++ b/sound/hda/common/bind.c @@ -100,6 +100,9 @@ static int hda_codec_driver_probe(struct device *dev) if (WARN_ON(!codec->preset)) return -EINVAL; + /* unsol events are still blocked until registered */ + codec->core.unsol_disabled = false; + err = snd_hda_codec_set_name(codec, codec->preset->name); if (err < 0) goto error; @@ -160,6 +163,10 @@ static int hda_codec_driver_remove(struct device *dev) return codec->bus->core.ext_ops->hdev_detach(&codec->core); } + /* stop asynchronous jack handling before freeing driver resources */ + snd_hdac_device_disable_unsol(&codec->core); + cancel_delayed_work_sync(&codec->jackpoll_work); + snd_hda_codec_disconnect_pcms(codec); snd_hda_jack_tbl_disconnect(codec); snd_refcount_sync(&codec->pcm_ref); diff --git a/sound/hda/core/bus.c b/sound/hda/core/bus.c index 20fe1c4a2977..8d4ed9834aaa 100644 --- a/sound/hda/core/bus.c +++ b/sound/hda/core/bus.c @@ -180,7 +180,7 @@ static void snd_hdac_bus_process_unsol_events(struct work_struct *work) if (!(caddr & (1 << 4))) /* no unsolicited event? */ continue; codec = bus->caddr_tbl[caddr & 0x0f]; - if (!codec || !codec->registered) + if (!codec || !codec->registered || codec->unsol_disabled) continue; spin_unlock_irq(&bus->reg_lock); drv = drv_to_hdac_driver(codec->dev.driver); @@ -191,6 +191,25 @@ static void snd_hdac_bus_process_unsol_events(struct work_struct *work) spin_unlock_irq(&bus->reg_lock); } +/** + * snd_hdac_device_disable_unsol - block and flush unsol events for the codec + * @codec: the HDA core device + * + * Stop dispatching the unsolicited events to the given codec, and wait for + * the pending unsol event handler to finish. Called at unbinding the codec + * driver before releasing the driver resources. + */ +void snd_hdac_device_disable_unsol(struct hdac_device *codec) +{ + struct hdac_bus *bus = codec->bus; + + spin_lock_irq(&bus->reg_lock); + codec->unsol_disabled = true; + spin_unlock_irq(&bus->reg_lock); + flush_work(&bus->unsol_work); +} +EXPORT_SYMBOL_GPL(snd_hdac_device_disable_unsol); + /** * snd_hdac_bus_add_device - Add a codec to bus * @bus: HDA core bus From f2c6e4f4575ff5318865c77da0153eb64809e8b8 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 23:56:47 +0200 Subject: [PATCH 613/924] ASoC: codecs: hda: Stop unsol events and jack polling before codec removal The ASoC HD-audio codec driver has the same race as the legacy HDA codec driver unbind: hda_codec_remove() calls the codec's remove callback that releases the driver resources, followed by snd_hda_codec_cleanup_for_unbind(), while the unsolicited event work and the jack polling work may still run concurrently and access the released resources, leading to a UAF. Apply the same fix as done for the legacy driver: call snd_hdac_device_disable_unsol() and cancel jackpoll_work at the beginning of hda_codec_remove(), and clear the unsol_disabled flag again at hda_codec_probe() so that the events get delivered after the codec is re-registered. Assisted-by: LLM Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007215650.159871-3-tiwai@suse.de --- sound/soc/codecs/hda.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/codecs/hda.c b/sound/soc/codecs/hda.c index 237b0b060457..5188c9d74c8b 100644 --- a/sound/soc/codecs/hda.c +++ b/sound/soc/codecs/hda.c @@ -218,6 +218,9 @@ static int hda_codec_probe(struct snd_soc_component *component) goto err; } + /* unsol events are still blocked until registered */ + codec->core.unsol_disabled = false; + ret = driver->ops->probe(codec, codec->preset); if (ret < 0) { dev_err(&hdev->dev, "codec init failed: %d\n", ret); @@ -276,6 +279,10 @@ static void hda_codec_remove(struct snd_soc_component *component) /* Don't allow any more runtime suspends */ pm_runtime_forbid(&hdev->dev); + /* stop asynchronous jack handling before freeing driver resources */ + snd_hdac_device_disable_unsol(hdev); + cancel_delayed_work_sync(&codec->jackpoll_work); + hda_codec_unregister_dais(codec, component); if (driver->ops->remove) From af027278d8d9915b7cb32736404719f1aebd66a3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Oct 2026 23:56:48 +0200 Subject: [PATCH 614/924] ALSA: hda: Make hdac_device.registered a plain bool The registered field of hdac_device is a bitfield sharing the same word with other bitfields like lazy_cache, and those are updated in a non-atomic read-modify-write manner without any lock. Meanwhile, the registered flag is read from the unsolicited event worker concurrently, so the concurrent updates of the neighboring bitfields may race with it. Convert it to a plain bool for avoiding such a subtle race. Also replace the assignments of 0/1 with false/true accordingly. Assisted-by: LLM Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261007215650.159871-4-tiwai@suse.de --- include/sound/hdaudio.h | 2 +- sound/hda/common/codec.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 285f7c4d262d..04fd4794bcee 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -96,7 +96,7 @@ struct hdac_device { bool lazy_cache:1; /* don't wake up for writes */ bool caps_overwriting:1; /* caps overwrite being in process */ bool cache_coef:1; /* cache COEF read/write too */ - unsigned int registered:1; /* codec was registered */ + bool registered; /* codec was registered */ bool unsol_disabled; /* unsol events blocked; protected by bus->reg_lock */ }; diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index b79346d01021..c99fe61c29db 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -761,7 +761,7 @@ void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec) /* pm_runtime_put() is called in snd_hdac_device_exit() */ pm_runtime_get_noresume(hda_codec_dev(codec)); pm_runtime_disable(hda_codec_dev(codec)); - codec->core.registered = 0; + codec->core.registered = false; } snd_hda_codec_disconnect_pcms(codec); @@ -815,7 +815,7 @@ void snd_hda_codec_register(struct hda_codec *codec) pm_runtime_enable(hda_codec_dev(codec)); /* it was powered up in snd_hda_codec_new(), now all done */ snd_hda_power_down(codec); - codec->core.registered = 1; + codec->core.registered = true; } } EXPORT_SYMBOL_GPL(snd_hda_codec_register); From 7e1ad3c55baa80399f1727dfd2303fa4d1558d9a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 8 Oct 2026 00:34:46 +0200 Subject: [PATCH 615/924] ALSA: hda: Don't free card at probe error in vga_switcheroo handler The vga_switcheroo handler azx_vs_set_state() calls azx_probe_contine() for enabling the audio, and if an error happens during the probe, azx_probe_continue() releases its associated sound card object at the error path. The card release procedure includes the call of vga_switcheroo_unregister_client(), and this triggers the vgasr_mutex lock again that had been already held by the caller (vga_switcheroo_process_delayed_switch()) -- leading to a deadlock. (Moreover, the card release will cause a potential UAF at the error message in azx_vs_set_state() since the card object is already gone; this was the original report by Sashiko.) For avoiding the unneeded crash, it's better to avoid the card release in this error path, so we make azx_probe_continue() behaving conditionally. In return, set hda->init_failed flag, so that any further (re-)attempts will be filtered after that point. The check of hda->init_failed is dropped from azx_vs_can_switch(). It's because otherwise vga_switcheroo can't be switched to turn off GPU once when a probe error happens at the audio side. Fixes: 39173303c838 ("ALSA: hda: Free card instance properly at probe errors") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20261007213728.155092-1-tiwai%40suse.de Link: https://patch.msgid.link/20261007223448.172443-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/hda/controllers/intel.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 068fc522658a..88b0207fde9c 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -1184,7 +1184,7 @@ static const struct dev_pm_ops azx_pm = { }; -static int azx_probe_continue(struct azx *chip); +static int azx_probe_continue(struct azx *chip, bool free_on_error); #ifdef SUPPORT_VGA_SWITCHEROO static struct pci_dev *get_bound_vga(struct pci_dev *pci); @@ -1211,8 +1211,10 @@ static void azx_vs_set_state(struct pci_dev *pci, if (!disabled) { dev_info(chip->card->dev, "Start delayed initialization\n"); - if (azx_probe_continue(chip) < 0) + if (azx_probe_continue(chip, false) < 0) { + hda->init_failed = 1; dev_err(chip->card->dev, "initialization error\n"); + } } } else { dev_info(chip->card->dev, "%s via vga_switcheroo\n", @@ -1251,8 +1253,6 @@ static bool azx_vs_can_switch(struct pci_dev *pci) struct hda_intel *hda = container_of(chip, struct hda_intel, chip); wait_for_completion(&hda->probe_wait); - if (hda->init_failed) - return false; if (chip->disabled || !hda->probe_continued) return true; if (snd_hda_lock_devices(&chip->bus)) @@ -1737,7 +1737,7 @@ static void azx_check_snoop_available(struct azx *chip) static void azx_probe_work(struct work_struct *work) { struct hda_intel *hda = container_of(work, struct hda_intel, probe_work.work); - azx_probe_continue(&hda->chip); + azx_probe_continue(&hda->chip, true); } static int default_bdl_pos_adj(struct azx *chip) @@ -2355,7 +2355,7 @@ static const unsigned int azx_max_codecs[AZX_NUM_DRIVERS] = { [AZX_DRIVER_TERA] = 1, }; -static int azx_probe_continue(struct azx *chip) +static int azx_probe_continue(struct azx *chip, bool free_on_error) { struct hda_intel *hda = container_of(chip, struct hda_intel, chip); struct hdac_bus *bus = azx_bus(chip); @@ -2446,10 +2446,12 @@ static int azx_probe_continue(struct azx *chip) out_free: if (err < 0) { - pci_set_drvdata(pci, NULL); if (hda->vga_switcheroo_registered) dev_warn(&pci->dev, "GPU sound probed, but not operational: please add a quirk to driver_denylist\n"); - snd_card_free(chip->card); + if (free_on_error) { + pci_set_drvdata(pci, NULL); + snd_card_free(chip->card); + } return err; } From 1c36207da79455402d981125fa09716991364e66 Mon Sep 17 00:00:00 2001 From: Matej Sochan Date: Wed, 7 Oct 2026 22:37:46 +0200 Subject: [PATCH 616/924] ALSA: hda/realtek: Fix silent speakers on Lenovo Legion S7 15IMH05 The speakers on the Legion S7 15IMH05 (PCI SSID 17aa:3811, ALC287) stay silent after a cold boot with the current quirk, which applies ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS. They only start working after a suspend/resume cycle. The amplifier setup is sent through COEF 0x24/0x26 on NID 0x20. On this machine the first pass of that sequence appears to only wake the amplifiers. Sending the identical sequence a second time makes both speakers work, provided the second write comes a few milliseconds after the first: back-to-back writes leave the speakers silent, a 7 ms gap makes only the right speaker work, and 8 ms or more is reliable. A dump of all COEF registers of NID 0x20 taken before and after the second write shows no difference. Add a dedicated fixup that writes the sequence, waits 20 ms to keep a margin over the measured minimum, and writes it again on every codec init. It chains to the same AUTOMUTE fixup as before. To share the sequence, move the verbs of ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS into a named array; this has no functional change for the other users of that fixup. Fixes: 67f4c61a73e9 ("ALSA: hda/realtek: Add quirk for Legion S7 15IMH") Cc: stable@vger.kernel.org Link: https://github.com/CachyOS/linux-cachyos/issues/792 Signed-off-by: Matej Sochan Link: https://patch.msgid.link/20261007-legion-s7-v1-1-8ea7fb7ee766@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 92 +++++++++++++++++++------------ 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 447c0ba358d8..a68b6a9b71aa 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4377,6 +4377,7 @@ enum { ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST, ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS, ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, + ALC287_FIXUP_LEGION_S7_15IMH05_AMP, ALC287_FIXUP_YOGA7_14ITL_SPEAKERS, ALC298_FIXUP_LENOVO_C940_DUET7, ALC287_FIXUP_LENOVO_YOGA_BOOK_9I, @@ -4529,6 +4530,53 @@ static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec, alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400); } +static const struct hda_verb legion_amp_verbs[] = { + /* left speaker */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x41 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x1a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + /* right speaker */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x42 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + {} +}; + +static void alc287_fixup_legion_s7_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_INIT) + return; + + snd_hda_sequence_write(codec, legion_amp_verbs); + /* + * The amplifiers accept the setup only a few ms after the first + * write (8 ms is the measured minimum on the test machine), so + * keep a margin. + */ + msleep(20); + snd_hda_sequence_write(codec, legion_amp_verbs); +} + static const struct hda_fixup alc269_fixups[] = { [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { .type = HDA_FIXUP_PINS, @@ -6593,41 +6641,13 @@ static const struct hda_fixup alc269_fixups[] = { }, [ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS] = { .type = HDA_FIXUP_VERBS, - //.v.verbs = legion_15imhg05_coefs, - .v.verbs = (const struct hda_verb[]) { - // set left speaker Legion 7i. - { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x41 }, - - { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x1a }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - - { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - - // set right speaker Legion 7i. - { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x42 }, - - { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x2a }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - - { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - {} - }, + .v.verbs = legion_amp_verbs, + .chained = true, + .chain_id = ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, + }, + [ALC287_FIXUP_LEGION_S7_15IMH05_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_legion_s7_amp, .chained = true, .chain_id = ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, }, @@ -8348,7 +8368,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x38d5, "Lenovo Yoga Pro 9 16IMH9", ALC287_FIXUP_TAS2781_I2C), HDA_CODEC_QUIRK(0x17aa, 0x38d6, "Lenovo Yoga Pro 9 16IMH9", ALC287_FIXUP_TAS2781_I2C), HDA_CODEC_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), - SND_PCI_QUIRK(0x17aa, 0x3811, "Legion S7 15IMH05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3811, "Legion S7 15IMH05", ALC287_FIXUP_LEGION_S7_15IMH05_AMP), SND_PCI_QUIRK(0x17aa, 0x3813, "Legion 7i 15IMHG05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3818, "Lenovo C940 / Yoga Duet 7", ALC298_FIXUP_LENOVO_C940_DUET7), SND_PCI_QUIRK(0x17aa, 0x3819, "Lenovo 13s Gen2 ITL", ALC287_FIXUP_13S_GEN2_SPEAKERS), From 45c2b3c936c7ab75756d28f9d0876912b84b5a3d Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 7 Oct 2026 14:30:36 -0500 Subject: [PATCH 617/924] ASoC: amd: acp63: add standalone ES9356 SoundWire machine The MECHREVO WUJIE 14SAH exposes a single ES9356 SDCA codec on ACP6.3 SoundWire link 0. The peripheral and its UAJ, SmartMic, SmartAmp and HID functions enumerate, but the ACP6.3 match table has no entry for it. Machine selection therefore fails and no sound card is registered. Add a link 0 machine entry containing all four ES9356 audio endpoints so the generic amd_sdw machine driver creates jack playback and capture, DMIC capture, and speaker playback links. Select the ES9356 codec from the legacy SoundWire machine configuration when SDCA support is enabled. Reported-by: Zachary Huang Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222092 Assisted-by: LLM Tested-by: Zachary Huang Signed-off-by: Mario Limonciello Reviewed-by: Vijendar Mukunda Link: https://patch.msgid.link/20261007193038.2473068-1-mario.limonciello@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/Kconfig | 1 + sound/soc/amd/acp/amd-acp63-acpi-match.c | 50 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig index b7b7b4f4a55c..a62bf0fb1efc 100644 --- a/sound/soc/amd/acp/Kconfig +++ b/sound/soc/amd/acp/Kconfig @@ -181,6 +181,7 @@ config SND_SOC_AMD_LEGACY_SDW_MACH select SND_SOC_RT715_SDW select SND_SOC_RT715_SDCA_SDW select SND_SOC_RT722_SDCA_SDW + select SND_SOC_ES9356 if SND_SOC_SDCA select SND_SOC_TAS2783_SDW if SND_SOC_SDCA && EFI help This option enables Legacy(No DSP) sound card support for SoundWire diff --git a/sound/soc/amd/acp/amd-acp63-acpi-match.c b/sound/soc/amd/acp/amd-acp63-acpi-match.c index 1dbbaba3c75b..4f448c711c2c 100644 --- a/sound/soc/amd/acp/amd-acp63-acpi-match.c +++ b/sound/soc/amd/acp/amd-acp63-acpi-match.c @@ -117,6 +117,42 @@ static const struct snd_soc_acpi_adr_device rt722_0_single_adr[] = { } }; +static const struct snd_soc_acpi_endpoint es9356_endpoints[] = { + { /* Jack Playback Endpoint */ + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + { /* DMIC Capture Endpoint */ + .num = 1, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + { /* Jack Capture Endpoint */ + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + { /* Speaker Playback Endpoint */ + .num = 3, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +static const struct snd_soc_acpi_adr_device es9356_0_adr[] = { + { + .adr = 0x00003004b3935601ull, + .num_endpoints = ARRAY_SIZE(es9356_endpoints), + .endpoints = es9356_endpoints, + .name_prefix = "es9356" + } +}; + static const struct snd_soc_acpi_endpoint cs42l43_endpoints[] = { { /* Jack Playback Endpoint */ .num = 0, @@ -465,6 +501,15 @@ static const struct snd_soc_acpi_link_adr acp63_rt722_only[] = { {} }; +static const struct snd_soc_acpi_link_adr acp63_es9356_only[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(es9356_0_adr), + .adr_d = es9356_0_adr, + }, + {} +}; + struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[] = { { .link_mask = BIT(0) | BIT(1), @@ -478,6 +523,11 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[] = { EXPORT_SYMBOL(snd_soc_acpi_amd_acp63_sof_sdw_machines); struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sdw_machines[] = { + { + .link_mask = BIT(0), + .links = acp63_es9356_only, + .drv_name = "amd_sdw", + }, { .link_mask = BIT(0), .links = acp63_rt722_only, From 6cb1f09e1f699d0c7f748057bc50d84a1e9e77fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kope=C4=87?= Date: Sat, 3 Oct 2026 21:44:58 +0200 Subject: [PATCH 618/924] ASoC: mediatek: common: Keep codec references until the card is bound MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mtk_soundcard_common_probe() drops the codec references parsed from the devicetree right after devm_snd_soc_register_card() returns. When a component is still missing, snd_soc_bind_card() queues the card for a later rebind and registration still returns success, so the card is not instantiated yet. The rebind reuses the same dai_link array, but with the codec references released both the codec name and of_node are unset, so it fails the dai link sanity check and the sound card never appears. Release the references right away only when the card was instantiated or registration failed. Otherwise keep them for the rebind and release them when the device is unbound. Fixes: bce93a162505 ("ASoC: mediatek: Add common machine soundcard driver probe mechanism") Signed-off-by: MichaÅ‚ Kopeć Link: https://patch.msgid.link/20261003194501.832558-2-michal@nozomi.space Signed-off-by: Mark Brown --- .../mediatek/common/mtk-soundcard-driver.c | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/common/mtk-soundcard-driver.c b/sound/soc/mediatek/common/mtk-soundcard-driver.c index 2d39ff23f854..4bb98412feb1 100644 --- a/sound/soc/mediatek/common/mtk-soundcard-driver.c +++ b/sound/soc/mediatek/common/mtk-soundcard-driver.c @@ -129,6 +129,11 @@ void clean_card_reference(struct snd_soc_card *card) } EXPORT_SYMBOL_GPL(clean_card_reference); +static void mtk_soundcard_put_card_reference(void *data) +{ + clean_card_reference(data); +} + int mtk_soundcard_startup(struct snd_pcm_substream *substream, enum mtk_pcm_constraint_type ctype) { @@ -325,8 +330,22 @@ int mtk_soundcard_common_probe(struct platform_device *pdev) ret = devm_snd_soc_register_card(&pdev->dev, card); - if (!needs_legacy_probe) - clean_card_reference(card); + /* + * When a component is still missing, snd_soc_bind_card() queues the + * card for a later rebind and returns success, so the card is not + * instantiated yet. That rebind reuses this same dai_link array, so + * the codec references parsed from the devicetree must be kept until + * the device is unbound: dropping them leaves both name and of_node + * unset and the rebind fails the dai link sanity check instead. + */ + if (!needs_legacy_probe) { + if (ret || snd_soc_card_is_instantiated(card)) + clean_card_reference(card); + else + ret = devm_add_action_or_reset(&pdev->dev, + mtk_soundcard_put_card_reference, + card); + } if (ret) { dev_err_probe(&pdev->dev, ret, "Cannot register card\n"); From 50c5d7f3a9b4d2a2baca40657e46a3508cdac2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kope=C4=87?= Date: Sat, 3 Oct 2026 21:44:59 +0200 Subject: [PATCH 619/924] ASoC: mediatek: Don't keep a freed topology name in the static card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The machine drivers pass a static snd_soc_card, which outlives any one probe. With SOF, mtk_soundcard_common_probe() calls snd_soc_card_set_topology_name(), which devm-allocates "sof-", stores it in card->topology_shortname and points card->name at it. If the probe then fails or defers, devm frees the string, but the static card keeps both pointers. The next probe does not allocate a new name, because topology_shortname is already set, so the card registers with whatever now occupies that memory: input: \xc0cr\xc2 DP Jack as /devices/platform/sound/sound/card0/input10 UCM matches profiles by card name, so userspace then finds no profile and the card is unusable until the next boot. Save the original name before setting the topology name, and restore it from a devm action that runs before the string is freed. Fixes: ec4489c43efe ("ASoC: soc-card: add snd_soc_card_set_topology_name()") Signed-off-by: MichaÅ‚ Kopeć Link: https://patch.msgid.link/20261003194501.832558-3-michal@nozomi.space Signed-off-by: Mark Brown --- .../mediatek/common/mtk-soundcard-driver.c | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/sound/soc/mediatek/common/mtk-soundcard-driver.c b/sound/soc/mediatek/common/mtk-soundcard-driver.c index 4bb98412feb1..a84f4ab4488b 100644 --- a/sound/soc/mediatek/common/mtk-soundcard-driver.c +++ b/sound/soc/mediatek/common/mtk-soundcard-driver.c @@ -185,6 +185,43 @@ const struct snd_soc_ops mtk_soundcard_common_capture_ops = { }; EXPORT_SYMBOL_GPL(mtk_soundcard_common_capture_ops); +struct mtk_soundcard_name { + struct snd_soc_card *card; + const char *name; +}; + +static void mtk_soundcard_restore_name(void *data) +{ + struct mtk_soundcard_name *saved = data; + + saved->card->name = saved->name; + saved->card->topology_shortname = NULL; +} + +/* + * The card is static and outlives this probe, but the topology name is + * devm-allocated. Restore the original name when the probe's resources are + * released, so that a deferred or failed probe does not leave the card name + * pointing to freed memory for the next one. + */ +static int mtk_soundcard_set_topology_name(struct device *dev, + struct snd_soc_card *card) +{ + struct mtk_soundcard_name *saved; + + saved = devm_kzalloc(dev, sizeof(*saved), GFP_KERNEL); + if (!saved) + return -ENOMEM; + + saved->card = card; + saved->name = card->name; + card->topology_shortname = NULL; + + snd_soc_card_set_topology_name(card, "sof"); + + return devm_add_action_or_reset(dev, mtk_soundcard_restore_name, saved); +} + int mtk_soundcard_common_probe(struct platform_device *pdev) { struct device_node *platform_node, *adsp_node, *accdet_node; @@ -292,7 +329,12 @@ int mtk_soundcard_common_probe(struct platform_device *pdev) card->probe = mtk_sof_card_probe; card->late_probe = mtk_sof_card_late_probe; - snd_soc_card_set_topology_name(card, "sof"); + ret = mtk_soundcard_set_topology_name(&pdev->dev, card); + if (ret) { + of_node_put(adsp_node); + of_node_put(platform_node); + return ret; + } } /* From 76775717c4adc283d5e7aed469468c8fa1ada4a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kope=C4=87?= Date: Sat, 3 Oct 2026 21:45:00 +0200 Subject: [PATCH 620/924] ASoC: mediatek: mt8188: Constrain UL8 FE to its back end's capabilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UL8 advertises up to 24 channels, but its only back end, ETDM1_IN, supports at most 16, and its bit clock is limited to 24.576 MHz. Without merged constraints, userspace can open UL8 with 48 kHz, 24 channels and 32-bit slots, which then fails in hw_params: mtk_dai_etdm_configure bck rate 36864000 not support ASoC error (-22): at snd_soc_dai_hw_params() on ETDM1_IN Set dpcm_merged_chan/rate/format, as the DL front ends already do, so the FE only offers configurations the back end can accept. Fixes: 96035d46d4b4 ("ASoC: mediatek: mt8188: add machine driver with mt6359") Signed-off-by: MichaÅ‚ Kopeć Link: https://patch.msgid.link/20261003194501.832558-4-michal@nozomi.space Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-mt6359.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index 75c90d1d165f..f07aeb637108 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -1142,6 +1142,9 @@ static struct snd_soc_dai_link mt8188_mt6359_dai_links[] = { }, .dynamic = 1, .capture_only = 1, + .dpcm_merged_chan = 1, + .dpcm_merged_rate = 1, + .dpcm_merged_format = 1, SND_SOC_DAILINK_REG(capture8), }, [DAI_LINK_UL9_FE] = { From d0b9e91b87c9d243ee95992978b02cb7d3bfee01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kope=C4=87?= Date: Sat, 3 Oct 2026 21:45:01 +0200 Subject: [PATCH 621/924] ASoC: hdmi-codec: Don't parse an empty ELD on startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bridge's get_eld() returns an all-zero ELD when no sink is connected. hdmi_codec_startup() parses it unconditionally, so every open of a disconnected HDMI/DP PCM logs: hdmi-audio-codec: HDMI: Unknown ELD version 0 Skip parsing when the ELD is empty and clear the previously parsed copy instead. The ELD hw constraint already falls back to basic stereo rates for an empty ELD, so stream setup is unchanged. plugged_cb() likewise treats a parse failure as debug-only. Signed-off-by: MichaÅ‚ Kopeć Link: https://patch.msgid.link/20261003194501.832558-5-michal@nozomi.space Signed-off-by: Mark Brown --- sound/soc/codecs/hdmi-codec.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index 7aa50c5bd3df..0e1e532dfc63 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -475,8 +475,12 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, if (ret) return ret; - snd_parse_eld(dai->dev, &hcp->eld_parsed, - hcp->eld, sizeof(hcp->eld)); + /* An empty ELD means no sink is connected; nothing to parse. */ + if (hcp->eld[0]) + snd_parse_eld(dai->dev, &hcp->eld_parsed, + hcp->eld, sizeof(hcp->eld)); + else + memset(&hcp->eld_parsed, 0, sizeof(hcp->eld_parsed)); ret = snd_pcm_hw_constraint_eld(substream->runtime, hcp->eld); if (ret) From 5bbd6696d75e6a250868f11849ebb4902b716b6d Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 6 Oct 2026 17:16:03 +0700 Subject: [PATCH 622/924] ASoC: mediatek: common: return 0 from mtk_afe_fe_startup() on success snd_pcm_hw_constraint_integer() may return 1, which is then returned from mtk_afe_fe_startup(). ASoC only treats 0 as success when marking the DAI as started, so .shutdown() can be skipped on rollback. Return 0 explicitly on success. Fixes: 283b612429a2 ("ASoC: mediatek: implement mediatek common structure") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261006101606.54079-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/common/mtk-afe-fe-dai.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c index 948f606cbaf2..ec99bc4a5d5e 100644 --- a/sound/soc/mediatek/common/mtk-afe-fe-dai.c +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -75,8 +75,10 @@ int mtk_afe_fe_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + return ret; + } /* dynamic allocate irq to memif */ if (memif->irq_usage < 0) { @@ -88,10 +90,10 @@ int mtk_afe_fe_startup(struct snd_pcm_substream *substream, } else { dev_err(afe->dev, "%s() error: no more asys irq\n", __func__); - ret = -EBUSY; + return -EBUSY; } } - return ret; + return 0; } EXPORT_SYMBOL_GPL(mtk_afe_fe_startup); From b6b3dbee98f1af51c0fceb4a57e2aba562c6cc4e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 6 Oct 2026 17:16:04 +0700 Subject: [PATCH 623/924] ASoC: mediatek: mt8188: return 0 from mt8188_afe_fe_startup() on success snd_pcm_hw_constraint_minmax() may return 1, which is then returned from mt8188_afe_fe_startup(). ASoC only treats 0 as success when marking the DAI as started, so .shutdown() can be skipped on rollback. Return 0 explicitly on success. Fixes: bf106bf09376 ("ASoC: mediatek: mt8188: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261006101606.54079-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-pcm.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c index 4010879ad1d3..87d6098caf99 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c @@ -318,8 +318,11 @@ static int mt8188_afe_fe_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 1, MT8188_MEMIF_DL7_MAX_PERIOD_SIZE); - if (ret < 0) + if (ret >= 0) + ret = 0; + else dev_dbg(afe->dev, "hw_constraint_minmax failed\n"); + out: return ret; } From f63317c826c4b1e4130e708c5cda800b8243b369 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 6 Oct 2026 17:16:05 +0700 Subject: [PATCH 624/924] ASoC: mediatek: mt8189:return 0 from mt8189_fe_startup() on success snd_pcm_hw_constraint_integer() may return 1, which is then returned from mt8189_fe_startup(). ASoC only treats 0 as success when marking the DAI as started, so .shutdown() can be skipped on rollback. Return 0 explicitly on success. Fixes: 7eb153585598 ("ASoC: mediatek: mt8189: add platform driver") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20261006101606.54079-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-pcm.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c index c0f340a6cc09..eefa96b701a5 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c @@ -155,8 +155,10 @@ static int mt8189_fe_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { dev_warn(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + return ret; + } /* dynamic allocate irq to memif */ if (memif->irq_usage < 0) { @@ -168,11 +170,11 @@ static int mt8189_fe_startup(struct snd_pcm_substream *substream, } else { dev_err(afe->dev, "%s() error: no more asys irq\n", __func__); - ret = -EBUSY; + return -EBUSY; } } - return ret; + return 0; } static void mt8189_fe_shutdown(struct snd_pcm_substream *substream, From 240f11af9c808c6e1e9d593879e08e9ffa13a76a Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 6 Oct 2026 17:16:06 +0700 Subject: [PATCH 625/924] ASoC: mediatek: mt8365: return 0 from mt8365_afe_fe_startup() on success snd_pcm_hw_constraint_integer() may return 1, which is then returned from mt8365_afe_fe_startup(). ASoC only treats 0 as success when marking the DAI as started, so .shutdown() can be skipped on rollback. Return 0 explicitly on success. Fixes: e1991d102bc2 ("ASoC: mediatek: mt8365: Add the AFE driver support") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20261006101606.54079-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8365/mt8365-afe-pcm.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c index e9497246f37c..314235557fdf 100644 --- a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +++ b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c @@ -514,11 +514,14 @@ static int mt8365_afe_fe_startup(struct snd_pcm_substream *substream, snd_soc_set_runtime_hwparams(substream, afe->mtk_afe_hardware); ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + return ret; + } mt8365_afe_enable_main_clk(afe); - return ret; + + return 0; } static void mt8365_afe_fe_shutdown(struct snd_pcm_substream *substream, From 6cf3ab38b0d53d41f00fe594b2986849bdf994ae Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 8 Oct 2026 11:51:22 +0300 Subject: [PATCH 626/924] ALSA: hda/hdmi: clamp num_cvts against cvt_nids[] in hdmi_read_pin_conn hdmi_add_cvt() stores each converter's NID into the fixed-size spec->cvt_nids[] array (used only by the Intel Haswell pin-connection fixup), but only when spec->num_cvts is still within ARRAY_SIZE(cvt_nids); the num_cvts counter itself is incremented unconditionally regardless of whether the store happened. A codec that reports more converters than cvt_nids[] can hold therefore ends up with num_cvts > ARRAY_SIZE(cvt_nids). hdmi_read_pin_conn(), on the intel_hsw_fixup path, copies num_cvts elements from cvt_nids[] with memcpy(), so once num_cvts exceeds the array size this reads past cvt_nids[] and writes the resulting garbage NIDs into per_pin->mux_nids[]. Clamp the copy length to the actual size of cvt_nids[] instead of trusting num_cvts, which is also used unbounded as the size of the separate, dynamically-sized spec->cvts array and must not be truncated at the source. Fixes: bce0d2a80e42 ("ALSA: hda - Allow unlimited pins and converters in patch_hdmi.c") Cc: stable@vger.kernel.org Signed-off-by: Peter Ujfalusi Link: https://patch.msgid.link/20261008085122.9442-1-peter.ujfalusi@linux.intel.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/hdmi/hdmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c index a8aac0b36c73..355fd3e73183 100644 --- a/sound/hda/codecs/hdmi/hdmi.c +++ b/sound/hda/codecs/hdmi/hdmi.c @@ -983,7 +983,8 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx) snd_hda_set_dev_select(codec, pin_nid, dev_id); if (spec->intel_hsw_fixup) { - conns = spec->num_cvts; + /* spec->cvt_nids[] only has ARRAY_SIZE(spec->cvt_nids) slots */ + conns = min_t(int, spec->num_cvts, ARRAY_SIZE(spec->cvt_nids)); memcpy(per_pin->mux_nids, spec->cvt_nids, sizeof(hda_nid_t) * conns); } else { From b3e7ddd694f8429217ba509b55b910a0e4361dfb Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 8 Oct 2026 11:51:33 +0300 Subject: [PATCH 627/924] ALSA: hda/hdmi: clamp sad_count in ELD proc write handler snd_hdmi_write_eld_info() parses a "sad_count N" line from the root-writable ELD proc interface and assigns N directly to e->sad_count with no upper bound, unlike the per-index "sadN_*" lines a few lines below which are already bounded to ELD_MAX_SAD. snd_hdmi_eld_update_pcm_info() later iterates "for (i = 0; i < e->sad_count; i++)" over the fixed e->sad[ELD_MAX_SAD] array, so an oversized sad_count read from the proc file causes it to walk past the array. Reject sad_count values above ELD_MAX_SAD, matching the existing per-index guard. Fixes: acb059938810 ("ALSA: hda - ELD proc interface write updates") Cc: stable@vger.kernel.org Signed-off-by: Peter Ujfalusi Link: https://patch.msgid.link/20261008085133.9528-1-peter.ujfalusi@linux.intel.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/hdmi/eld.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/hda/codecs/hdmi/eld.c b/sound/hda/codecs/hdmi/eld.c index 1464fd1c675b..fc6fe1b0873c 100644 --- a/sound/hda/codecs/hdmi/eld.c +++ b/sound/hda/codecs/hdmi/eld.c @@ -155,9 +155,11 @@ void snd_hdmi_write_eld_info(struct hdmi_eld *eld, e->aud_synch_delay = val; else if (!strcmp(name, "speakers")) e->spk_alloc = val; - else if (!strcmp(name, "sad_count")) + else if (!strcmp(name, "sad_count")) { + if (val > ELD_MAX_SAD) + continue; e->sad_count = val; - else if (!strncmp(name, "sad", 3)) { + } else if (!strncmp(name, "sad", 3)) { sname = name + 4; n = name[3] - '0'; if (name[4] >= '0' && name[4] <= '9') { From f5e1a9a3fddab8421416b68bb132ae9626d07156 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 8 Oct 2026 11:54:11 +0300 Subject: [PATCH 628/924] ALSA: x86: hdmi: fix chmap array missing terminator entry had_build_channel_allocation_map() allocates a single struct snd_pcm_chmap_elem via kzalloc() and stores it as intelhaddata->chmap->chmap. struct snd_pcm_chmap.chmap is expected to be a zero-terminated array (see snd_pcm_std_chmaps[]), but had_register_chmap_ctls() never overrides the TLV callback installed by snd_pcm_add_chmap_ctls(), so the generic pcm_chmap_ctl_tlv() from ALSA core is left in place. pcm_chmap_ctl_tlv() is reachable by any local user via SNDRV_CTL_IOCTL_TLV_READ on the "Playback Channel Map" control and walks the array with "for (map = info->chmap; map->channels; map++)". Since the driver's single element is never terminated, this walks past the allocation. The same pointer is also freed on hot-unplug and rebuilt on hot-plug under intelhaddata->mutex, while the TLV callback runs under card->controls_rwsem without that mutex, so the walk can also race a concurrent free of the array. Fix this by allocating a second, zeroed element so the array is properly terminated, and by dropping the inherited TLV callback entirely at registration time, since the driver never implements one of its own. Fixes: 5dab11d89777 ("ALSA: x86: hdmi: Add audio support for BYT and CHT") Cc: stable@vger.kernel.org Signed-off-by: Peter Ujfalusi Link: https://patch.msgid.link/20261008085411.13742-1-peter.ujfalusi@linux.intel.com Signed-off-by: Takashi Iwai --- sound/x86/intel_hdmi_audio.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sound/x86/intel_hdmi_audio.c b/sound/x86/intel_hdmi_audio.c index 9f67244d4946..542fc35ec9b7 100644 --- a/sound/x86/intel_hdmi_audio.c +++ b/sound/x86/intel_hdmi_audio.c @@ -475,7 +475,8 @@ static void had_build_channel_allocation_map(struct snd_intelhad *intelhaddata) kfree(intelhaddata->chmap->chmap); intelhaddata->chmap->chmap = NULL; - chmap = kzalloc_obj(*chmap); + /* info->chmap must be zero-terminated; allocate a spare terminator entry */ + chmap = kzalloc_objs(*chmap, 2); if (!chmap) return; @@ -575,6 +576,12 @@ static int had_register_chmap_ctls(struct snd_intelhad *intelhaddata, intelhaddata->chmap->kctl->info = had_chmap_ctl_info; intelhaddata->chmap->kctl->get = had_chmap_ctl_get; intelhaddata->chmap->chmap = NULL; + + /* drop the inherited TLV read: it walks chmap without our mutex */ + intelhaddata->chmap->kctl->tlv.c = NULL; + intelhaddata->chmap->kctl->vd[0].access &= + ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); + return 0; } From 441533711d91492815b710a79b1ec4acb5e19ca6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 8 Oct 2026 21:25:45 +0200 Subject: [PATCH 629/924] ALSA: caiaq: Register card at the final step caiaq driver calls snd_card_register() before creating the mixer elements, which is rather confusing to user-space, as the devices have been published before adding the control elements. Swap the call order to make the card registration as the final step (as written in the comment). Fixes: 8e3cd08ed8e5 ("[ALSA] caiaq - add control API and more input features") Reported-by: Sashiko Link: https://patch.msgid.link/20261008192553.300025-6-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/caiaq/device.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index a596f5f763b8..c68ed16d7ac4 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -395,6 +395,12 @@ static int setup_card(struct snd_usb_caiaqdev *cdev) return ret; } + ret = snd_usb_caiaq_control_init(cdev); + if (ret < 0) { + dev_err(dev, "Unable to set up control system (ret=%d)\n", ret); + return ret; + } + /* finally, register the card and all its sub-instances */ ret = snd_card_register(cdev->chip.card); if (ret < 0) { @@ -402,12 +408,6 @@ static int setup_card(struct snd_usb_caiaqdev *cdev) return ret; } - ret = snd_usb_caiaq_control_init(cdev); - if (ret < 0) { - dev_err(dev, "Unable to set up control system (ret=%d)\n", ret); - return ret; - } - return 0; } From 52b1213e2a086f2828500ab9c8bac45b213429bb Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 8 Oct 2026 21:25:46 +0200 Subject: [PATCH 630/924] ALSA: caiaq: Fix races at MIDI URB and trigger accesses URB completion and rawmidi trigger callbacks can race with each other, which may put the state inconsistent, causing double submissions, etc. Guard both with a new spinlock for avoiding the races. Fixes: f3f80a9205da ("ALSA: caiaq - Fix Oops with MIDI") Reported-by: Sashiko Link: https://patch.msgid.link/20261008192553.300025-7-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/caiaq/device.c | 1 + sound/usb/caiaq/device.h | 3 ++- sound/usb/caiaq/midi.c | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index c68ed16d7ac4..634dc36f2ead 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -455,6 +455,7 @@ static int create_card(struct usb_device *usb_dev, cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor), le16_to_cpu(usb_dev->descriptor.idProduct)); spin_lock_init(&cdev->spinlock); + spin_lock_init(&cdev->midi_lock); mutex_init(&cdev->ep1_out_mutex); *cardp = card; diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index dd726ca68e80..16f65e7867d1 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -77,7 +77,8 @@ struct snd_usb_caiaqdev { unsigned char midi_out_buf[EP1_BUFSIZE]; struct caiaq_device_spec spec; - spinlock_t spinlock; + spinlock_t spinlock; /* for PCM audio */ + spinlock_t midi_lock; /* midi_x_stream, midi_out_active */ wait_queue_head_t ep1_wait_queue; wait_queue_head_t prepare_wait_queue; int spec_received, audio_parm_answer; diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c index 18529484c8dc..b7d7d24937a7 100644 --- a/sound/usb/caiaq/midi.c +++ b/sound/usb/caiaq/midi.c @@ -79,6 +79,7 @@ static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *subs { struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data; + guard(spinlock_irqsave)(&cdev->midi_lock); if (up) { cdev->midi_out_substream = substream; if (!cdev->midi_out_active) @@ -151,6 +152,7 @@ void snd_usb_caiaq_midi_output_done(struct urb* urb) { struct snd_usb_caiaqdev *cdev = urb->context; + guard(spinlock_irqsave)(&cdev->midi_lock); cdev->midi_out_active = 0; if (urb->status != 0) return; From 589fccbe1e9dd8aaf075cc073a7489c0c015ee49 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 8 Oct 2026 21:25:47 +0200 Subject: [PATCH 631/924] ALSA: usb: us16x08: Fix racy accesses of mixer elements All get and put callbacks for us16x08 mixer have no proper protection, hence the concurrent accesses to multiple elements may face data races, resulting in unexpected values. Put the new mixer->lock mutex for protecting the concurrent accesses. Fixes: d2bb390a2081 ("ALSA: usb-audio: Tascam US-16x08 DSP mixer quirk") Reported-by: Sashiko Link: https://patch.msgid.link/20261008192553.300025-8-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/mixer_us16x08.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c index 8e5ccd3282a7..b447c4f7a161 100644 --- a/sound/usb/mixer_us16x08.c +++ b/sound/usb/mixer_us16x08.c @@ -189,6 +189,7 @@ static int snd_us16x08_route_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); /* route has no bias */ ucontrol->value.enumerated.item[0] = elem->cache_val[index]; @@ -204,6 +205,7 @@ static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol, char buf[sizeof(route_msg)]; int val, val_org, err; + guard(mutex)(&elem->head.mixer->lock); /* get the new value (no bias for routes) */ val = ucontrol->value.enumerated.item[0]; @@ -258,6 +260,7 @@ static int snd_us16x08_master_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); ucontrol->value.integer.value[0] = elem->cache_val[index]; return 0; @@ -272,6 +275,7 @@ static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol, int val, err; int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); /* new control value incl. bias*/ val = ucontrol->value.integer.value[0]; @@ -310,6 +314,7 @@ static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol, val = ucontrol->value.integer.value[0]; + guard(mutex)(&elem->head.mixer->lock); /* prepare the message buffer from template */ switch (elem->head.id) { case SND_US16X08_ID_BYPASS: @@ -346,6 +351,7 @@ static int snd_us16x08_bus_get(struct snd_kcontrol *kcontrol, { struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); + guard(mutex)(&elem->head.mixer->lock); switch (elem->head.id) { case SND_US16X08_ID_BUSS_OUT: ucontrol->value.integer.value[0] = elem->cache_val[0]; @@ -368,6 +374,7 @@ static int snd_us16x08_channel_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); ucontrol->value.integer.value[0] = elem->cache_val[index]; return 0; @@ -382,6 +389,7 @@ static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol, int val, err; int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -428,6 +436,7 @@ static int snd_us16x08_comp_get(struct snd_kcontrol *kcontrol, int index = ucontrol->id.index; int val_idx = COMP_STORE_IDX(elem->head.id); + guard(mutex)(&elem->head.mixer->lock); ucontrol->value.integer.value[0] = store->val[val_idx][index]; return 0; @@ -445,6 +454,7 @@ static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol, int threshold, ratio, attack, release, gain, switch_on; int err; + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -521,6 +531,7 @@ static int snd_us16x08_eqswitch_get(struct snd_kcontrol *kcontrol, struct snd_us16x08_eq_store *store = elem->private_data; int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); /* get low switch from cache is enough, cause all bands are together */ val = store->val[EQ_STORE_BAND_IDX(elem->head.id)] [EQ_STORE_PARAM_IDX(elem->head.id)][index]; @@ -540,6 +551,7 @@ static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol, int val, err = 0; int b_idx; + guard(mutex)(&elem->head.mixer->lock); /* new control value incl. bias*/ val = ucontrol->value.integer.value[0] + SND_US16X08_KCBIAS(kcontrol); @@ -582,6 +594,7 @@ static int snd_us16x08_eq_get(struct snd_kcontrol *kcontrol, int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1; int p_idx = EQ_STORE_PARAM_IDX(elem->head.id); + guard(mutex)(&elem->head.mixer->lock); val = store->val[b_idx][p_idx][index]; ucontrol->value.integer.value[0] = val; @@ -601,6 +614,7 @@ static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol, int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1; int p_idx = EQ_STORE_PARAM_IDX(elem->head.id); + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -729,6 +743,7 @@ static int snd_us16x08_meter_get(struct snd_kcontrol *kcontrol, struct snd_us16x08_meter_store *store = elem->private_data; u8 meter_urb[64] = {0}; + guard(mutex)(&elem->head.mixer->lock); switch (kcontrol->private_value) { case 0: { char tmp[sizeof(mix_init_msg1)]; @@ -788,6 +803,7 @@ static int snd_us16x08_meter_put(struct snd_kcontrol *kcontrol, struct snd_us16x08_meter_store *store = elem->private_data; int val; + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ From e2ef553dfe923959632e580bcad8583d922868ac Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 8 Oct 2026 21:25:49 +0200 Subject: [PATCH 632/924] ALSA: line6: Reject too small max packet sizes Although the LINE6 driver has a sanity check for the given max packet sizes, it still has an implicit requirement of the minimal size being bytes-per-frame; e.g. the impulse test signal assuming the fixed size, and when a too small size is specified by a malformed USB descriptor, this may lead to an OOB access. Change the sanity check conditions to reject too small max packet sizes for avoiding the scenario above. Fixes: 3450121997ce ("ALSA: line6: Fix write on zero-sized buffer") Reported-by: Sashiko Link: https://patch.msgid.link/20261008192553.300025-10-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/line6/pcm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 2932eaf157f4..998869dc4613 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -554,7 +554,11 @@ int line6_init_pcm(struct usb_line6 *line6, line6pcm->max_packet_size_out = usb_maxpacket(line6->usbdev, usb_sndisocpipe(line6->usbdev, ep_write)); - if (!line6pcm->max_packet_size_in || !line6pcm->max_packet_size_out) { + /* reject max packet sizes smaller than bytes-per-frame; + * (the magic number 6 is taken from the playback case) + */ + if (line6pcm->max_packet_size_in < 6 || + line6pcm->max_packet_size_out < 6) { dev_err(line6pcm->line6->ifcdev, "cannot get proper max packet size\n"); return -EINVAL; From d69798357b5d562bcda1af488cda3ead4d039f8f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 8 Oct 2026 21:25:50 +0200 Subject: [PATCH 633/924] ALSA: line6: Fix potential OOB write in line6_capture_copy() line6_capture_copy() copies the data for the original byte size, but the buffer overwrap is checked against the frame size. Because of it, when the data size isn't aligned in frames, the remaining bytes might be still copied above the buffer size. For avoiding the potential OOB write, correct the copied data size in line6_capture_copy() to be aligned with frames. Fixes: 1027f476f507 ("staging: line6: sync with upstream") Reported-by: Sashiko Link: https://patch.msgid.link/20261008192553.300025-11-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/line6/capture.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 6dbaf30232f9..3e1f40d6b481 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -117,7 +117,8 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) } else { /* copy single chunk */ memcpy(runtime->dma_area + - line6pcm->in.pos_done * bytes_per_frame, fbuf, fsize); + line6pcm->in.pos_done * bytes_per_frame, fbuf, + frames * bytes_per_frame); } line6pcm->in.pos_done += frames; From 360e688f6d24ccd3e9485f57ad451ec1cecfb94c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 8 Oct 2026 21:25:48 +0200 Subject: [PATCH 634/924] ASoC: fsl_asrc_m2m: Fix bogus compress task pointer assignments When a creation of input or output compress-offload task fails in fsl-asrc driver, the task->input or task->output pointer is left with ERR_PTR(), which is non-NULL. Then it's handled in the compress offload core and it tries to release via dma_buf_put(), resulting in an access to a wrong address. Clear the bogus pointers properly before returning an error. Fixes: 24a01710f627 ("ASoC: fsl_asrc_m2m: Add memory to memory function") Reported-by: Sashiko Link: https://patch.msgid.link/20261008192553.300025-9-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/soc/fsl/fsl_asrc_m2m.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index 4bc40f328f58..42b0e21f281c 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -475,6 +475,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, task->input = dma_buf_export(&exp_info_in); if (IS_ERR(task->input)) { ret = PTR_ERR(task->input); + task->input = NULL; return ret; } @@ -485,6 +486,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, task->output = dma_buf_export(&exp_info_out); if (IS_ERR(task->output)) { ret = PTR_ERR(task->output); + task->output = NULL; return ret; } From b83b78df0e53d9dd872d24e49a99658815b12770 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 8 Oct 2026 21:25:41 +0200 Subject: [PATCH 635/924] ALSA: hda: intel: Cancel delayed work at shutdown, too The shutdown procedure needs to cancel the delayed probe work in case where the probe work stalls or is slow but still possible to reactivated. Fixes: c0f1886de7e1 ("ALSA: hda: intel: Allow repeatedly probing on codec configuration errors") Reported-by: Sashiko Link: https://patch.msgid.link/20261008192553.300025-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/hda/controllers/intel.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 88b0207fde9c..2b5b5d555f2c 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -2484,12 +2484,17 @@ static void azx_remove(struct pci_dev *pci) static void azx_shutdown(struct pci_dev *pci) { struct snd_card *card = pci_get_drvdata(pci); + struct hda_intel *hda; struct azx *chip; if (!card) return; chip = card->private_data; - if (chip && chip->running) + if (!chip) + return; + hda = container_of(chip, struct hda_intel, chip); + cancel_delayed_work_sync(&hda->probe_work); + if (chip->running) __azx_shutdown_chip(chip, true); } From 874323b68c93c6808e034dc0113681e822e6fb42 Mon Sep 17 00:00:00 2001 From: Olivier Kaloudoff Date: Fri, 9 Oct 2026 01:08:44 +0200 Subject: [PATCH 636/924] ALSA: usb-audio: Add mic resolution quirk for Logitech Webcam C200 The C200 (046d:0802) reports a mic capture volume range of 1536..7680 with a resolution of 1 (6144 steps), triggering "Warning! Unlikely big volume step count (=6144), linear volume or wrong cval->res?" at probe and exposing a uselessly fine-grained mixer control. This is the same firmware behavior as the other UVC-era Logitech webcams already carrying QUIRK_FLAG_MIC_RES_384 (C500, C600, C210, C270, C310, C510, C525, ...), so apply the same quirk: CTL_MSG_DELAY_1M (which the Logitech VENDOR_FLG would provide if the device-specific entry did not short-circuit the lookup) plus MIC_RES_384. Verified on real hardware: with the quirk the warning disappears and the driver logs "set resolution quirk: cval->res = 384". Signed-off-by: Olivier Kaloudoff Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261008230844.11246-1-olivier.kaloudoff@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 170f68db74fa..7a5c95de86ce 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2494,6 +2494,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_DISABLE_AUTOSUSPEND), DEVICE_FLG(0x045e, 0x070f, /* MS LifeChat LX-3000 Headset */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x046d, 0x0802, /* Logitech Webcam C200 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), DEVICE_FLG(0x046d, 0x0807, /* Logitech Webcam C500 */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), DEVICE_FLG(0x046d, 0x0808, /* Logitech Webcam C600 */ From 0572664718e2e0fd5270b60d1f706daa3261c5b9 Mon Sep 17 00:00:00 2001 From: Dimitri Wegner Date: Fri, 9 Oct 2026 13:44:53 +0200 Subject: [PATCH 637/924] ALSA: usb-audio: Add feedback snap quirk for Mayflower ARC AMP The Mayflower ARC AMP DAC (USB ID 0493:4170, MAG Technology front-end) at times gets stuck reporting feedback of nominal+1 sample/frame (97 instead of 96 at 96 kHz) and does not recover. The driver follows this feedback, and audible pops occur every 1-2s until the stream is restarted. Add a quirk for this device that ignores feedback values more than 1/128 (~0.8%) away from nominal and uses the nominal rate instead. Normal feedback corrections are far smaller than that and pass through unchanged. Other devices are not affected. Signed-off-by: Dimitri Wegner Link: https://patch.msgid.link/20261009-mayflower-fb-v1-1-a84e82e05ac9@zerovolt.org Signed-off-by: Takashi Iwai --- sound/usb/card.h | 1 + sound/usb/endpoint.c | 16 ++++++++++++++++ sound/usb/quirks.c | 10 ++++++++++ 3 files changed, 27 insertions(+) diff --git a/sound/usb/card.h b/sound/usb/card.h index 8299ac241c60..f956bcf00f77 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -123,6 +123,7 @@ struct snd_usb_endpoint { unsigned int syncmaxsize; /* sync endpoint packet size */ unsigned int fill_max:1; /* fill max packet size always */ unsigned int tenor_fb_quirk:1; /* corrupted feedback data */ + unsigned int snap_fb_quirk:1; /* ignore implausible feedback */ unsigned int datainterval; /* log_2 of data packet interval */ unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */ unsigned char silence_value; diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 879d0451536e..9c844c55ad3f 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -1908,6 +1908,22 @@ static void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, else f >>= -ep->freqshift; + if (unlikely(sender->snap_fb_quirk) && + (f < ep->freqn - ep->freqn / 128 || + f > ep->freqn + ep->freqn / 128)) { + /* + * Feedback deviates from nominal far beyond what real + * clock differences can explain. This firmware can get + * stuck at nominal+1 sample per frame, causing audible + * pops. Trust the nominal rate instead. + */ + dev_warn_ratelimited(&ep->chip->dev->dev, + "EP 0x%x: ignoring implausible feedback 0x%x.%04x (nominal 0x%x.%04x)\n", + ep->ep_num, f >> 16, f & 0xffff, + ep->freqn >> 16, ep->freqn & 0xffff); + f = ep->freqn; + } + if (likely(f >= ep->freqn - ep->freqn / 8 && f <= ep->freqmax)) { /* * If the frequency looks valid, set it. diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 7a5c95de86ce..e0de33aa3abd 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2198,6 +2198,16 @@ void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep) ep->chip->usb_id == USB_ID(0x1852, 0x5034)) && /* T+A Dac8 */ ep->syncmaxsize == 4) ep->tenor_fb_quirk = 1; + + /* + * Mayflower ARC AMP DAC (MAG Technology USB front-end): the + * feedback can get stuck at nominal+1 sample per frame, causing + * pops every 1-2 sec until stream restart. Snap implausible + * feedback to nominal. + */ + if (ep->chip->usb_id == USB_ID(0x0493, 0x4170) && + ep->type == SND_USB_ENDPOINT_TYPE_SYNC) + ep->snap_fb_quirk = 1; } /* quirk applied after snd_usb_ctl_msg(); not applied during boot quirks */ From 0688699b3612ebd44118a033ab3101f087d7a832 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 9 Oct 2026 08:29:42 +0300 Subject: [PATCH 638/924] ASoC: SOF: Intel: Make sure that link and host DMA is coupled after use Couple the Host and Link DMA when the stream is put. When the hda_dsp_stream_hw_free() is called the Link DMA might be still locked and we would leave the DMAs decoupled. hda_dsp_stream_hw_free() is not called for code loader use or in case of probes or trace use for example. Signed-off-by: Peter Ujfalusi Reviewed-by: Bard Liao Link: https://patch.msgid.link/20261009052942.17346-1-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda-stream.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c index ac2a62c4e41f..bdd4831e050c 100644 --- a/sound/soc/sof/intel/hda-stream.c +++ b/sound/soc/sof/intel/hda-stream.c @@ -293,9 +293,8 @@ static int _hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stre { const struct sof_intel_dsp_desc *chip_info = get_chip_info(sdev->pdata); struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct hdac_ext_stream *hext_stream_match = NULL; struct hdac_bus *bus = sof_to_bus(sdev); - struct sof_intel_hda_stream *hda_stream; - struct hdac_ext_stream *hext_stream; struct hdac_ext_stream *link_stream; struct hdac_stream *s; bool dmi_l1_enable = true; @@ -308,6 +307,9 @@ static int _hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stre * that are DMI L1 incompatible. */ list_for_each_entry(s, &bus->stream_list, list) { + struct sof_intel_hda_stream *hda_stream; + struct hdac_ext_stream *hext_stream; + hext_stream = stream_to_hdac_ext_stream(s); hda_stream = container_of(hext_stream, struct sof_intel_hda_stream, hext_stream); @@ -315,6 +317,7 @@ static int _hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stre continue; if (s->direction == direction && s->stream_tag == stream_tag) { + hext_stream_match = hext_stream; s->opened = false; found = true; s->curr_pos = 0; @@ -325,6 +328,20 @@ static int _hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stre } } + if (!sdev->dspless_mode_selected && !pair && hext_stream_match) { + /* + * Couple host and link DMA if link DMA is unused + * In case of channel pair the snd_hdac_ext_stream_release() will + * take care of coupling te HDA channel. + */ + if (!hext_stream_match->link_locked) { + u32 mask = BIT(hext_stream_match->hstream.index); + + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, + SOF_HDA_REG_PP_PPCTL, mask, 0); + } + } + spin_unlock_irq(&bus->reg_lock); /* Enable DMI L1 if permitted */ From 7a78c18bffc6e09016720681d791b799feb61783 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 9 Oct 2026 15:33:36 +0200 Subject: [PATCH 639/924] ALSA: hda: Disable unsol event handling at error and shutdown paths When the HD-audio controller driver probe fails, it still leaves the unsolicited event handling and jackpoll work active, hence if they are pending, they might fire up later after the resource gets released, which may lead to a UAF. A similar problem may be seen at shutdown, too. Add the recently added helper to disable unsol events and the cancel of jackpoll_work at the appropriate places. We need the workaround in the error path before driver->ops->remove, too. As unsol events and jack polling may have been already enabled during the probe callback, the driver->ops->remove call is done (more) conditionally now. Fixes: c3ec8ac82105 ("ASoC: hdac_hda: fix memleak on module unload") Reported-by: Sashiko Link: https://patch.msgid.link/20261009133341.440917-3-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/hda/common/bind.c | 8 ++++++-- sound/hda/common/codec.c | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c index 4772ca154a29..d12359970e74 100644 --- a/sound/hda/common/bind.c +++ b/sound/hda/common/bind.c @@ -89,6 +89,7 @@ static int hda_codec_driver_probe(struct device *dev) struct hda_codec *codec = dev_to_hda_codec(dev); struct module *owner = dev->driver->owner; struct hda_codec_driver *driver = hda_codec_to_driver(codec); + bool probed = false; int err; if (codec->bus->core.ext_ops) { @@ -122,7 +123,8 @@ static int hda_codec_driver_probe(struct device *dev) err = driver->ops->probe(codec, codec->preset); if (err < 0) - goto error_module_put; + goto error_module; + probed = true; err = snd_hda_codec_build_pcms(codec); if (err < 0) goto error_module; @@ -141,7 +143,9 @@ static int hda_codec_driver_probe(struct device *dev) return 0; error_module: - if (driver->ops->remove) + snd_hdac_device_disable_unsol(&codec->core); + cancel_delayed_work_sync(&codec->jackpoll_work); + if (probed && driver->ops->remove) driver->ops->remove(codec); error_module_put: module_put(owner); diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index c99fe61c29db..94da4f3a21ed 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -3039,6 +3039,7 @@ void snd_hda_codec_shutdown(struct hda_codec *codec) codec->jackpoll_interval = 0; /* don't poll any longer */ cancel_delayed_work_sync(&codec->jackpoll_work); + snd_hdac_device_disable_unsol(&codec->core); list_for_each_entry(cpcm, &codec->pcm_list_head, list) snd_pcm_suspend_all(cpcm->pcm); From 569c6b2714731df534ad367a3de99502accd6c4e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 9 Oct 2026 15:33:37 +0200 Subject: [PATCH 640/924] ALSA: hda: Add lock around codec->registered flag manipulations Currently the places setting or clearing codec->registered flag have no locking, while its reference in the unsol handling work snd_hdac_bus_process_unsol_events() takes the bus->reg_lock lock. Let's add the same bus->reg_lock at the places where codec->registered is set and cleared for avoiding the races. Link: https://patch.msgid.link/20261009133341.440917-4-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/hda/common/codec.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 94da4f3a21ed..18838a45e6f7 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -757,11 +757,15 @@ static void codec_release_pcms(struct hda_codec *codec) */ void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec) { + struct hda_bus *bus = codec->bus; + if (codec->core.registered) { /* pm_runtime_put() is called in snd_hdac_device_exit() */ pm_runtime_get_noresume(hda_codec_dev(codec)); pm_runtime_disable(hda_codec_dev(codec)); + spin_lock_irq(&bus->core.reg_lock); codec->core.registered = false; + spin_unlock_irq(&bus->core.reg_lock); } snd_hda_codec_disconnect_pcms(codec); @@ -808,6 +812,8 @@ void snd_hda_codec_display_power(struct hda_codec *codec, bool enable) */ void snd_hda_codec_register(struct hda_codec *codec) { + struct hda_bus *bus = codec->bus; + if (codec->core.registered) return; if (device_is_registered(hda_codec_dev(codec))) { @@ -815,7 +821,9 @@ void snd_hda_codec_register(struct hda_codec *codec) pm_runtime_enable(hda_codec_dev(codec)); /* it was powered up in snd_hda_codec_new(), now all done */ snd_hda_power_down(codec); + spin_lock_irq(&bus->core.reg_lock); codec->core.registered = true; + spin_unlock_irq(&bus->core.reg_lock); } } EXPORT_SYMBOL_GPL(snd_hda_codec_register); From 66530fbe5252eef5e5386a894804c62e10ecfdea Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 9 Oct 2026 15:33:38 +0200 Subject: [PATCH 641/924] ALSA: hda: Stop unsol event and jack polling at hda-ext device removal, too In the recent commit f3607b6952ca ("ALSA: hda: Stop unsol events and jack polling before codec unbind"), we applied the workaround to stop pending works at the hda codec driver removal, but it was applied after the bypass for hda-ext ops hdev_detach. Basically the same issue is seen for hda-ext stuff, so it's safer to apply the workaround at the beginning of hda_codec_driver_remove(). Along with it, the clearance of unsol_disabled flag is moved to the beginning of hda_codec_driver_probe(), too; otherwise hda-ext hdev_attach() might miss the unsol event handling when the code gets re-bound. Fixes: f3607b6952ca ("ALSA: hda: Stop unsol events and jack polling before codec unbind") Link: https://patch.msgid.link/20261009133341.440917-5-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/hda/common/bind.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c index d12359970e74..957feace0f58 100644 --- a/sound/hda/common/bind.c +++ b/sound/hda/common/bind.c @@ -92,6 +92,9 @@ static int hda_codec_driver_probe(struct device *dev) bool probed = false; int err; + /* unsol events are still blocked until registered */ + codec->core.unsol_disabled = false; + if (codec->bus->core.ext_ops) { if (WARN_ON(!codec->bus->core.ext_ops->hdev_attach)) return -EINVAL; @@ -101,9 +104,6 @@ static int hda_codec_driver_probe(struct device *dev) if (WARN_ON(!codec->preset)) return -EINVAL; - /* unsol events are still blocked until registered */ - codec->core.unsol_disabled = false; - err = snd_hda_codec_set_name(codec, codec->preset->name); if (err < 0) goto error; @@ -161,16 +161,16 @@ static int hda_codec_driver_remove(struct device *dev) struct hda_codec *codec = dev_to_hda_codec(dev); struct hda_codec_driver *driver = hda_codec_to_driver(codec); + /* stop asynchronous jack handling before freeing driver resources */ + snd_hdac_device_disable_unsol(&codec->core); + cancel_delayed_work_sync(&codec->jackpoll_work); + if (codec->bus->core.ext_ops) { if (WARN_ON(!codec->bus->core.ext_ops->hdev_detach)) return -EINVAL; return codec->bus->core.ext_ops->hdev_detach(&codec->core); } - /* stop asynchronous jack handling before freeing driver resources */ - snd_hdac_device_disable_unsol(&codec->core); - cancel_delayed_work_sync(&codec->jackpoll_work); - snd_hda_codec_disconnect_pcms(codec); snd_hda_jack_tbl_disconnect(codec); snd_refcount_sync(&codec->pcm_ref); From 8cbf75d115e97b7f5ba7859d30d0ce74d1af6c19 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 9 Oct 2026 15:33:39 +0200 Subject: [PATCH 642/924] ALSA: hda: Add NULL check for the driver pointer at unsol event work snd_hdac_bus_process_unsol_events() assumes that the codec driver is always set when it's accessible from the codec table. It's true in general, but in a sheer timing, it might have been already NULLified at the driver unbinding. Although the codec removal should have been done properly by the previous fixes, just for more safety, let's add a NULL check before dereferencing and calling the driver's unsol_event callback. Fixes: e7255c00b10e ("ALSA: hda: Skip event processing for unregistered codecs") Link: https://patch.msgid.link/20261009133341.440917-6-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/hda/core/bus.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sound/hda/core/bus.c b/sound/hda/core/bus.c index 8d4ed9834aaa..21af4b7c870d 100644 --- a/sound/hda/core/bus.c +++ b/sound/hda/core/bus.c @@ -182,10 +182,13 @@ static void snd_hdac_bus_process_unsol_events(struct work_struct *work) codec = bus->caddr_tbl[caddr & 0x0f]; if (!codec || !codec->registered || codec->unsol_disabled) continue; - spin_unlock_irq(&bus->reg_lock); + if (!codec->dev.driver) + continue; drv = drv_to_hdac_driver(codec->dev.driver); - if (drv->unsol_event) - drv->unsol_event(codec, res); + if (!drv->unsol_event) + continue; + spin_unlock_irq(&bus->reg_lock); + drv->unsol_event(codec, res); spin_lock_irq(&bus->reg_lock); } spin_unlock_irq(&bus->reg_lock); From f2293e7b2ddd803b1461e0490f9b2f32089c1dd0 Mon Sep 17 00:00:00 2001 From: Evgeny Akhtimirov Date: Fri, 9 Oct 2026 20:02:37 +0400 Subject: [PATCH 643/924] ALSA: hda/realtek: Enable all four speaker amps on Lenovo ThinkBook X G2 IAH The Lenovo ThinkBook X G2 IAH (MTM 21TU) has an ALC287 codec with four CS35L41 amplifiers on I2C (CSC3551). It reports codec SSID 17aa:3929, which it shares with the ThinkBook 13x Gen 5, and PCI SSID 17aa:3842. Since PCI SSID 17aa:3842 has no quirk entry, the codec SSID matches ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD. That fixup binds only two amps (cs35l41-hda.0 and .1), while the CS35L41 configuration table describes four for 17AA3929. Amps .2 and .3 probe and load firmware, but they are never bound to the codec, so half of the speakers stay silent. Add a ThinkPad-style fixup chain that binds four I2C amps, and match the laptop by its PCI SSID so the ThinkBook 13x Gen 5 is not affected. Tested with hda_model=1028:0c4d (alias to ALC287_FIXUP_CS35L41_I2C_4): all four amps bind, apply their calibration and are runtime-active during playback. Cc: stable@vger.kernel.org Signed-off-by: Evgeny Akhtimirov Link: https://patch.msgid.link/20261009-thinkbook-x-g2-iah-v1-1-56012188aee0@devduty.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index a68b6a9b71aa..217df4301a30 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4427,6 +4427,8 @@ enum { ALC245_FIXUP_HP_X360_MUTE_LEDS, ALC287_FIXUP_THINKPAD_I2S_SPK, ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD, + ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI, + ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD, ALC2XX_FIXUP_HEADSET_MIC, ALC289_FIXUP_DELL_CS35L41_SPI_2, ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, @@ -7017,6 +7019,18 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI, }, + [ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_four, + .chained = true, + .chain_id = ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK, + }, + [ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_tb_vmaster_led, + .chained = true, + .chain_id = ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI, + }, [ALC2XX_FIXUP_HEADSET_MIC] = { .type = HDA_FIXUP_FUNC, .v.func = alc2xx_fixup_headset_mic, @@ -8378,6 +8392,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3827, "Ideapad S740", ALC285_FIXUP_IDEAPAD_S740_COEF), SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3842, "ThinkBook X G2 IAH", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD), SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; * use codec SSID to distinguish them From 51ee6ce2418cb41698f1bfb9fc2f08d0e274c68e Mon Sep 17 00:00:00 2001 From: Bob Song Date: Sat, 10 Oct 2026 16:27:24 +0800 Subject: [PATCH 644/924] ALSA: hda: Reset response counter when a verb times out On some controllers the HDA link reports a verb timeout and the driver falls back to polling mode. After that, every subsequent verb sent to that codec address keeps timing out (roughly one second per verb), while the hardware itself looks perfectly healthy: the RIRB write pointer keeps advancing, interrupts are still delivered, and verbs that carry no reply payload -- notably writes -- still take effect on the codec. The reason is that bus->rirb.cmds[addr] is incremented for every verb and only decremented in snd_hdac_bus_update_rirb() when a matching RIRB response is read, without any rollback for a response that never arrives. A controller that cannot fall back to the single-command mode (chip->fallback_to_single_cmd == 0, e.g. the ACPI platform, Tegra and CIX controllers) returns an error right away and never performs the bus reset that would reinitialize CORB/RIRB and clear the counter, so a single lost response leaves bus->rirb.cmds[addr] non-zero forever. Each following verb then still gets one response that only brings the count back to the stale baseline of 1, and snd_hdac_bus_get_response() keeps timing out even though the codec answers normally. Handle it in the core helper snd_hdac_bus_exec_verb_unlocked(): whenever get_response() reports an error, reset the pending counter for the codec address, so a late response is simply treated as a spurious response and the following verbs recover. Doing it here covers all HD-audio controllers instead of relying on each controller's fallback setting. Add a small helper snd_hdac_bus_reset_response_counter() for this. Signed-off-by: Bob Song Link: https://patch.msgid.link/20261010082724.1653199-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/core/bus.c | 6 +++++- sound/hda/core/controller.c | 19 +++++++++++++++++++ sound/hda/core/local.h | 2 ++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/sound/hda/core/bus.c b/sound/hda/core/bus.c index 21af4b7c870d..02462bd035e7 100644 --- a/sound/hda/core/bus.c +++ b/sound/hda/core/bus.c @@ -121,11 +121,15 @@ int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr, break; /* process pending verbs */ err = bus->ops->get_response(bus, addr, &tmp); - if (err) + if (err) { + snd_hdac_bus_reset_response_counter(bus, addr); break; + } } if (!err && res) { err = bus->ops->get_response(bus, addr, res); + if (err) + snd_hdac_bus_reset_response_counter(bus, addr); trace_hda_get_response(bus, addr, *res); } return err; diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c index e8d40c24ed00..a7acd3418ae8 100644 --- a/sound/hda/core/controller.c +++ b/sound/hda/core/controller.c @@ -396,6 +396,25 @@ int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr, } EXPORT_SYMBOL_GPL(snd_hdac_bus_get_response); +/** + * snd_hdac_bus_reset_response_counter - reset the pending response count + * @bus: HD-audio core bus + * @addr: codec address + * + * Drop the number of commands that are still waiting for a response from + * the given codec address. This is called when a verb is known to be dead, + * e.g. after a response timeout, so that a lost response won't leave the + * counter non-zero forever and block all later verbs to the same codec. + */ +void snd_hdac_bus_reset_response_counter(struct hdac_bus *bus, unsigned int addr) +{ + if (addr >= HDA_MAX_CODECS) + return; + + guard(spinlock_irq)(&bus->reg_lock); + bus->rirb.cmds[addr] = 0; +} + #define HDAC_MAX_CAPS 10 /** * snd_hdac_bus_parse_capabilities - parse capability structure diff --git a/sound/hda/core/local.h b/sound/hda/core/local.h index 5f03b203c416..b9e58395decb 100644 --- a/sound/hda/core/local.h +++ b/sound/hda/core/local.h @@ -16,6 +16,8 @@ int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec); void snd_hdac_bus_remove_device(struct hdac_bus *bus, struct hdac_device *codec); void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex); +void snd_hdac_bus_reset_response_counter(struct hdac_bus *bus, + unsigned int addr); int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr, unsigned int cmd, unsigned int *res); From 8362d65141d275d2907f1610a3cd2becb7ae2c83 Mon Sep 17 00:00:00 2001 From: Yi Tao Date: Sat, 10 Oct 2026 11:40:55 +0200 Subject: [PATCH 645/924] ALSA: usb-audio: Enable native DSD for MOONDROP DAWN PRO2 The MOONDROP DAWN PRO2 (USB ID 35d8:011d) exposes a UAC2 RAW_DATA playback altsetting, but ALSA reports it as SPECIAL without the DSD_RAW quirk, preventing native DSD playback. Add the device to the raw DSD quirk table. With the quirk enabled, ALSA exposes DSD_U32_BE and MPD 0.24.4 outputs stereo DSD256 through USB altsetting 4 at 352800 frames/s, instead of converting to PCM. Tested using snd-usb-audio quirk_flags=35d8:011d:DSD_RAW on Linux 7.2.6+deb13-amd64 (Debian 13). Assisted-by: LLM Signed-off-by: Yi Tao Link: https://patch.msgid.link/20261010094055.49308-1-taoyi.sh.cn@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index e0de33aa3abd..f4d7f68f4ee9 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2809,6 +2809,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x3443, 0x930d, /* NexiGo N930W 60fps Webcam */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x35d8, 0x011d, /* MOONDROP DAWN PRO2 */ + QUIRK_FLAG_DSD_RAW), DEVICE_FLG(0x36f9, 0xc009, /* XIBERIA K03S */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x3703, 0x2000, /* NUX NAI-24 */ From 570373542e8fad56f9f7355a615bfdbb2dd24b56 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Sat, 12 Sep 2026 21:04:21 +0200 Subject: [PATCH 646/924] ALSA: hda/scodec: imply SERIAL_MULTI_INSTANTIATE for AW88399 The AW88399 HDA side codec driver relies on the serial-multi-instantiate driver to create I2C devices from the "AWDZ8399" ACPI HID entry. Add imply SERIAL_MULTI_INSTANTIATE to help kernel configuration tools suggest the correct dependency, matching the CS35L41 and CS35L56 HDA side codec entries. Signed-off-by: Marco Giunta Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/SN6PR19MB2303BF65050B51AE7217BB9AFCBD2@SN6PR19MB2303.namprd19.prod.outlook.com --- sound/hda/codecs/side-codecs/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig index f90f1dfcec68..b7e2a6fa4258 100644 --- a/sound/hda/codecs/side-codecs/Kconfig +++ b/sound/hda/codecs/side-codecs/Kconfig @@ -22,6 +22,7 @@ config SND_HDA_SCODEC_AW88399_I2C depends on I2C depends on ACPI depends on SND_SOC + imply SERIAL_MULTI_INSTANTIATE select SND_HDA_SCODEC_AW88399 select SND_SOC_AW88399_LIB help From 947037990e050a3cb116631ff29a9cb621068162 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Wed, 23 Sep 2026 19:42:32 +0200 Subject: [PATCH 647/924] MAINTAINERS: add entry for AW88399 HDA side codec driver Add a MAINTAINERS entry for the AW88399 HDA side codec driver, covering the I2C bus driver and core driver files under sound/hda/codecs/side-codecs/. Signed-off-by: Marco Giunta Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/SN6PR19MB23036B2EBA9AD55D12961FA0FC822@SN6PR19MB2303.namprd19.prod.outlook.com --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 85cc45b65cda..d72d0744b7a0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4426,6 +4426,12 @@ L: linux-media@vger.kernel.org S: Maintained F: drivers/media/pci/hws/ +AWINIC AW88399 HDA SIDE CODEC DRIVER +M: Marco Giunta +L: linux-sound@vger.kernel.org +S: Maintained +F: sound/hda/codecs/side-codecs/aw88399* + AWINIC AW99706 WLED BACKLIGHT DRIVER M: Junjie Cao S: Maintained From 7ca1731b8a129cafe258c578fa87b7c414c6d004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Ahmet=20Memi=C5=9F?= Date: Sun, 11 Oct 2026 01:25:40 +0300 Subject: [PATCH 648/924] ALSA: seq: Wait for one RCU grace period when deleting all ports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the port list was converted to RCU, snd_seq_delete_all_ports() calls synchronize_rcu() once per port under register_mutex. Closing a client with 254 ports now takes about 1.6s and every other client open or close waits for it. Unlink all ports first, wait for a single grace period and then free them. Fixes: 4c252fbc06d6 ("ALSA: seq: Use RCU for the client port list") Signed-off-by: Ali Ahmet MemiÅŸ Link: https://patch.msgid.link/20261010222543.1470723-1-aliamemis@disroot.org Signed-off-by: Takashi Iwai --- sound/core/seq/seq_ports.c | 41 +++++++++++++++++++++++++------------- sound/core/seq/seq_ports.h | 1 + 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index eb67eb0eeb14..b582d034437f 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -248,17 +248,9 @@ static void clear_subscriber_list(struct snd_seq_client *client, } /* delete port data */ -static int port_delete(struct snd_seq_client *client, - struct snd_seq_client_port *port) +static void port_free(struct snd_seq_client *client, + struct snd_seq_client_port *port) { - /* set closing flag and wait for all port access are gone */ - port->closing = 1; - /* the port has already been unlinked from the client's port list; - * wait for a grace period so that RCU readers still traversing the - * list can no longer take a new use_lock reference, then drain the - * outstanding references before freeing - */ - synchronize_rcu(); snd_use_lock_sync(&port->use_lock); /* clear subscribers info */ @@ -272,6 +264,20 @@ static int port_delete(struct snd_seq_client *client, snd_BUG_ON(port->c_dest.count != 0); kfree(port); +} + +static int port_delete(struct snd_seq_client *client, + struct snd_seq_client_port *port) +{ + /* set closing flag and wait for all port access are gone */ + port->closing = 1; + /* the port has already been unlinked from the client's port list; + * wait for a grace period so that RCU readers still traversing the + * list can no longer take a new use_lock reference, then drain the + * outstanding references before freeing + */ + synchronize_rcu(); + port_free(client, port); return 0; } @@ -302,18 +308,25 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) int snd_seq_delete_all_ports(struct snd_seq_client *client) { struct snd_seq_client_port *port, *tmp; + LIST_HEAD(deleted); - /* unlink and delete each port; port_delete() waits for an RCU grace - * period before draining the port, so concurrent lockless readers can - * no longer take a new use_lock reference on it + /* unlink all ports first and wait for a single RCU grace period + * before draining and freeing them, so concurrent lockless readers + * can no longer take a new use_lock reference on any of them */ guard(mutex)(&client->ports_mutex); list_for_each_entry_safe(port, tmp, &client->ports_list_head, list) { list_del_rcu(&port->list); client->num_ports--; + port->closing = 1; + list_add_tail(&port->delete_list, &deleted); snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); - port_delete(client, port); } + if (list_empty(&deleted)) + return 0; + synchronize_rcu(); + list_for_each_entry_safe(port, tmp, &deleted, delete_list) + port_free(client, port); return 0; } diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index 12ad86bf1489..ef1787d6c2dd 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -49,6 +49,7 @@ struct snd_seq_client_port { struct module *owner; /* owner of this port */ char name[64]; /* port name */ struct list_head list; /* port list */ + struct list_head delete_list; /* used by snd_seq_delete_all_ports() */ snd_use_lock_t use_lock; /* subscribers */ From dd83323e4d5e22bd04f7506a9ec33533465c8393 Mon Sep 17 00:00:00 2001 From: Maksim Krasutski Date: Sat, 10 Oct 2026 20:53:35 +0200 Subject: [PATCH 649/924] ALSA: hda/realtek: Fix mute LEDs on HP ENVY 17-cr1xxx The HP ENVY Laptop 17-cr1xxx with subsystem ID 103c:8be8 uses an ALC245 codec and two CS35L41 I2C speaker amplifiers. Its existing quirk configures the amplifiers but does not enable the speaker and microphone mute LED handlers. Select ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS to retain the amplifier setup and enable both mute indicators. The equivalent change was tested on an HP ENVY 17-cr1xxx running Kubuntu 26.04.1 with kernel 7.0.0-38-generic. Both keyboard mute indicators were confirmed working. Fixes: aa8e3ef4fe53 ("ALSA: hda/realtek: Add quirks for various HP ENVY models") Assisted-by: LLM Signed-off-by: Maksim Krasutski Link: https://patch.msgid.link/20261010185335.30949-1-krasutskimaksim@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 217df4301a30..0bfd1d0939eb 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7817,7 +7817,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8be5, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be6, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be7, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8be8, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8be8, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8be9, "HP Envy x360 2-in-1 Laptop 15-fh0xxx", ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX), SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8c15, "HP Spectre x360 2-in-1 Laptop 14-eu0xxx", ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX), From f21d232b2ba3997c1fd33cbe37ff47f72d2a9f2a Mon Sep 17 00:00:00 2001 From: Steven 'Steve' Kendall Date: Wed, 7 Oct 2026 00:31:24 +0000 Subject: [PATCH 650/924] ALSA: hda/cs8409: Fix for Dell Cirrus audio jack detect On some models like the Dell Inspiron 15 3520, jack detection does not work. This patch switches to delayed work to fix jack plug on Chrome OS for this model. Assisted-by: LLM Signed-off-by: Steven 'Steve' Kendall Link: https://patch.msgid.link/20261007-fix-headphone-plug-cirrus-dell-v5-1-c28146b18d5c@chromium.org Signed-off-by: Takashi Iwai --- sound/hda/codecs/cirrus/cs8409-tables.c | 9 ++++- sound/hda/codecs/cirrus/cs8409.c | 52 +++++++++++++++++++++++-- sound/hda/codecs/cirrus/cs8409.h | 4 ++ 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/sound/hda/codecs/cirrus/cs8409-tables.c b/sound/hda/codecs/cirrus/cs8409-tables.c index b9ec8fb8eab7..d0cef8492c99 100644 --- a/sound/hda/codecs/cirrus/cs8409-tables.c +++ b/sound/hda/codecs/cirrus/cs8409-tables.c @@ -594,7 +594,7 @@ const struct hda_quirk cs8409_fixup_tbl[] = { SND_PCI_QUIRK(0x1028, 0x0BA8, "Odin", CS8409_ODIN), SND_PCI_QUIRK(0x1028, 0x0BAA, "Odin", CS8409_ODIN), SND_PCI_QUIRK(0x1028, 0x0BAE, "Odin", CS8409_ODIN), - SND_PCI_QUIRK(0x1028, 0x0BB2, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0BB2, "Warlock MLK", CS8409_WARLOCK_MLK_DELAYED_JD), SND_PCI_QUIRK(0x1028, 0x0BB3, "Warlock MLK", CS8409_WARLOCK_MLK), SND_PCI_QUIRK(0x1028, 0x0BB4, "Warlock MLK", CS8409_WARLOCK_MLK), SND_PCI_QUIRK(0x1028, 0x0BB5, "Warlock N3 15 TGL-U Nuvoton EC", CS8409_WARLOCK), @@ -625,6 +625,7 @@ const struct hda_model_fixup cs8409_models[] = { { .id = CS8409_BULLSEYE, .name = "bullseye" }, { .id = CS8409_WARLOCK, .name = "warlock" }, { .id = CS8409_WARLOCK_MLK, .name = "warlock mlk" }, + { .id = CS8409_WARLOCK_MLK_DELAYED_JD, .name = "warlock mlk delayed jd" }, { .id = CS8409_WARLOCK_MLK_DUAL_MIC, .name = "warlock mlk dual mic" }, { .id = CS8409_CYBORG, .name = "cyborg" }, { .id = CS8409_DOLPHIN, .name = "dolphin" }, @@ -652,6 +653,12 @@ const struct hda_fixup cs8409_fixups[] = { .chained = true, .chain_id = CS8409_FIXUPS, }, + [CS8409_WARLOCK_MLK_DELAYED_JD] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, [CS8409_WARLOCK_MLK_DUAL_MIC] = { .type = HDA_FIXUP_PINS, .v.pins = cs8409_cs42l42_pincfgs, diff --git a/sound/hda/codecs/cirrus/cs8409.c b/sound/hda/codecs/cirrus/cs8409.c index c43ff3ef75b6..12ee71d7a35c 100644 --- a/sound/hda/codecs/cirrus/cs8409.c +++ b/sound/hda/codecs/cirrus/cs8409.c @@ -56,6 +56,9 @@ static int cs8409_parse_auto_config(struct hda_codec *codec) } static void cs8409_disable_i2c_clock_worker(struct work_struct *work); +static void cs8409_jack_detect_worker(struct work_struct *work); +static void cs42l42_run_jack_detect(struct sub_codec *cs42l42); +static int cs8409_i2c_read(struct sub_codec *scodec, unsigned int addr); static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec) { @@ -69,6 +72,7 @@ static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec) codec->power_save_node = 1; mutex_init(&spec->i2c_mux); INIT_DELAYED_WORK(&spec->i2c_clk_work, cs8409_disable_i2c_clock_worker); + INIT_DELAYED_WORK(&spec->jack_detect_work, cs8409_jack_detect_worker); snd_hda_gen_spec_init(&spec->gen); return spec; @@ -115,6 +119,28 @@ static void cs8409_disable_i2c_clock_worker(struct work_struct *work) cs8409_disable_i2c_clock(spec->codec); } +/* + * cs8409_jack_detect_worker - Perform initial jack detection once hardware is settled + * + * Defer initial jack detection. On ChromeOS and possibly other platforms this + * delay is required for jack detection to work as expected. + */ +static void cs8409_jack_detect_worker(struct work_struct *work) +{ + struct cs8409_spec *spec = container_of(work, struct cs8409_spec, jack_detect_work.work); + struct sub_codec *cs42l42 = spec->scodecs[CS8409_CODEC0]; + + if (spec->init_done && spec->build_ctrl_done && !cs42l42->hp_jack_in) { + int reg_ts_status = cs8409_i2c_read(cs42l42, CS42L42_TSRS_PLUG_STATUS); + + /* error case */ + if (reg_ts_status < 0) + return; + + cs42l42_run_jack_detect(cs42l42); + } +} + /* * cs8409_enable_i2c_clock - Enable I2C clocks * @codec: the codec instance @@ -951,6 +977,8 @@ static void cs8409_remove(struct hda_codec *codec) { struct cs8409_spec *spec = codec->spec; + /* Disable jack detect work */ + disable_delayed_work_sync(&spec->jack_detect_work); /* Cancel i2c clock disable timer, and disable clock if left enabled */ cancel_delayed_work_sync(&spec->i2c_clk_work); cs8409_disable_i2c_clock(codec); @@ -1020,6 +1048,9 @@ static int cs8409_cs42l42_suspend(struct hda_codec *codec) cs8409_enable_ur(codec, 0); + /* Cancel jack detect work first as it accesses CS42L42 over I2C */ + cancel_delayed_work_sync(&spec->jack_detect_work); + for (i = 0; i < spec->num_scodecs; i++) cs42l42_suspend(spec->scodecs[i]); @@ -1153,6 +1184,9 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_0DB; spec->speaker_pdn_gpio = CS8409_CYBORG_SPEAKER_PDN; break; + case CS8409_WARLOCK_MLK_DELAYED_JD: + spec->delay_jack_detect = 1; + fallthrough; case CS8409_WARLOCK_MLK: case CS8409_WARLOCK_MLK_DUAL_MIC: spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_0DB; @@ -1197,8 +1231,13 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, cs8409_cs42l42_hw_init(codec); spec->init_done = 1; if (spec->init_done && spec->build_ctrl_done - && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) - cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) { + if (spec->delay_jack_detect) + schedule_delayed_work(&spec->jack_detect_work, + msecs_to_jiffies(CS8409_JACK_DETECT_DELAY_MS)); + else + cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + } break; case HDA_FIXUP_ACT_BUILD: spec->build_ctrl_done = 1; @@ -1208,8 +1247,13 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, * Run immediately after init. */ if (spec->init_done && spec->build_ctrl_done - && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) - cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) { + if (spec->delay_jack_detect) + schedule_delayed_work(&spec->jack_detect_work, + msecs_to_jiffies(CS8409_JACK_DETECT_DELAY_MS)); + else + cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + } break; default: break; diff --git a/sound/hda/codecs/cirrus/cs8409.h b/sound/hda/codecs/cirrus/cs8409.h index be1714a84fff..dacc1083b9d4 100644 --- a/sound/hda/codecs/cirrus/cs8409.h +++ b/sound/hda/codecs/cirrus/cs8409.h @@ -247,6 +247,7 @@ enum cs8409_coefficient_index_registers { #define CS8409_CS42L42_AMIC_PIN_NID CS8409_PIN_ASP1_RECEIVER_A #define CS8409_CS42L42_DMIC_PIN_NID CS8409_PIN_DMIC1_IN #define CS8409_CS42L42_DMIC_ADC_PIN_NID CS8409_PIN_DMIC1 +#define CS8409_JACK_DETECT_DELAY_MS 100 /* Dolphin */ @@ -266,6 +267,7 @@ enum { CS8409_BULLSEYE, CS8409_WARLOCK, CS8409_WARLOCK_MLK, + CS8409_WARLOCK_MLK_DELAYED_JD, CS8409_WARLOCK_MLK_DUAL_MIC, CS8409_CYBORG, CS8409_FIXUPS, @@ -339,12 +341,14 @@ struct cs8409_spec { unsigned int i2c_clck_enabled; unsigned int dev_addr; struct delayed_work i2c_clk_work; + struct delayed_work jack_detect_work; unsigned int playback_started:1; unsigned int capture_started:1; unsigned int init_done:1; unsigned int build_ctrl_done:1; unsigned int speaker_muted:1; + unsigned int delay_jack_detect:1; /* verb exec op override */ int (*exec_verb)(struct hdac_device *dev, unsigned int cmd, unsigned int flags, From f9e882ac8257bc55a8c99399e504e434d9e72eb9 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 12 Oct 2026 00:02:50 +0800 Subject: [PATCH 651/924] ALSA: timer: Pin the module for userspace timer controllers The static snd_utimer_ids IDA must remain live until a userspace timer controller has returned its ID. However, snd_utimer_fops has no owner, so anon_inode_getfd() does not take a reference to snd-timer for the controller file. With CONFIG_SND_TIMER=m and CONFIG_SND_UTIMER=y, userspace can create a controller with SNDRV_TIMER_IOCTL_CREATE and close the native timer file. If no timer instances or dependent modules retain snd-timer, ordinary module unload can then overlap the controller's final release: Controller final release Module unload snd_utimer_release() snd_utimer_free() snd_timer_free(): unlink provider snd_utimer_put_id() enter ida_free() delete_module() alsa_timer_exit() free_module(): free MOD_DATA access snd_utimer_ids.xa The provider is already absent from snd_timer_free_all() when unload runs. Neither register_mutex nor the IDA's lock keeps module storage resident through the remaining IDA access. That access can therefore use freed module data and fault while acquiring the XArray lock, as shown in the Oops below. Set snd_utimer_fops.owner to THIS_MODULE so anonymous-file creation takes the existing module reference. The VFS drops it only after snd_utimer_release() returns, keeping the IDA resident through ida_free() and preventing ordinary unload during that interval. Oops report as below: BUG: unable to handle page fault for address: fffffbfff80480af #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page PGD 1a7ff6067 P4D 1a7ff6067 PUD 1a7ff2067 PMD 100ad6067 PTE 0 Oops: Oops: 0000 [#1] SMP KASAN NOPTI CPU: 0 UID: 0 PID: 485 Comm: alsa-utimer-fix Tainted: G O 7.2.0-rc6-pmb-bt-functional-v1+ #1 PREEMPT(lazy) Tainted: [O]=OOT_MODULE Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 RIP: 0010:kasan_byte_accessible+0x15/0x30 Code: 00 00 0f 1f 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 0f 1f 40 d6 48 b8 00 00 00 00 00 fc ff df 48 c1 ef 03 48 01 c7 <0f> b6 07 3c 07 0f 96 c0 e9 69 f1 2b ff 66 66 2e 0f 1f 84 00 00 00 RSP: 0018:ffff88811a457c48 EFLAGS: 00010086 RAX: dffffc0000000000 RBX: ffffffffc0240578 RCX: 0000000000000000 RDX: 0000000000000000 RSI: ffffffff861f111c RDI: fffffbfff80480af RBP: ffffffffc0240578 R08: 0000000000000001 R09: 0000000000000000 R10: ffffffff891cc057 R11: ffff88811531ba00 R12: ffffffff861f111c R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000 FS: 00007ff190ff7780(0000) GS:ffff8881fda2a000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: fffffbfff80480af CR3: 0000000116890003 CR4: 0000000000770ef0 PKRU: 55555554 Call Trace: __kasan_check_byte+0x13/0x50 lock_acquire+0x113/0x2f0 ? __pfx_pmbd_probe_hit_cookie+0x10/0x10 ? lock_release+0xc8/0x280 ? srso_alias_return_thunk+0x5/0xfbef5 _raw_spin_lock_irqsave+0x3c/0x60 ? ida_free+0xd4/0x2b0 ida_free+0xd4/0x2b0 ? __pfx_ida_free+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_locks_remove_file+0x10/0x10 ? __fput+0x39f/0xa60 ? fput_close_sync+0xff/0x200 ? __pfx_fput_close_sync+0x10/0x10 ? dnotify_flush+0x22/0x4b0 ? __x64_sys_close+0x8c/0xf0 ? do_syscall_64+0x115/0x6a0 ? entry_SYSCALL_64_after_hwframe+0x77/0x7f Modules linked in: snd(O) [last unloaded: snd_timer(O)] CR2: fffffbfff80480af ---[ end trace 0000000000000000 ]--- Fixes: 37745918e0e7 ("ALSA: timer: Introduce virtual userspace-driven timers") Assisted-by: LLM Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-98fb8f747fc7b8499fd4-v2-1c531f149a6d6c084f48@gmail.com Signed-off-by: Takashi Iwai --- sound/core/timer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/core/timer.c b/sound/core/timer.c index 69a59b25fe0a..905176bf6948 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -2173,6 +2173,7 @@ static long snd_utimer_ioctl(struct file *file, unsigned int ioctl, unsigned lon } static const struct file_operations snd_utimer_fops = { + .owner = THIS_MODULE, .llseek = noop_llseek, .release = snd_utimer_release, .unlocked_ioctl = snd_utimer_ioctl, From f0e9db0f84249b65a72b5fba43e91212281ad378 Mon Sep 17 00:00:00 2001 From: Ting-Han Hou Date: Sun, 11 Oct 2026 20:54:26 +0800 Subject: [PATCH 652/924] ALSA: virtio: reset I/O message fill level on prepare Since commit fe981e67568c ("ALSA: virtio: use ack callback"), an I/O message is only queued to the device once the application has filled the whole period: virtsnd_pcm_msg_send() accumulates the transferred bytes in msg->length and queues the message when it reaches period_bytes. msg->length is reset to 0 only when the device completes the message. If the substream is stopped (e.g. snd_pcm_drop()) while a period is only partially filled, the corresponding message is never queued and keeps its partial length. virtsnd_pcm_prepare() resets msg_count, hw_ptr and the indirect PCM state, but not msg->length. After the next start, the stale length is added to the new data, the sum never equals period_bytes, and the message is never sent again. Playback then stalls once hw_ptr reaches that period: writes fail with -EIO ("DMA or IRQ trouble") and drain fails with -EIO. Capture is affected the same way after a partial read. Clear the fill level of all messages in prepare. Outside of the resume path, virtsnd_pcm_prepare() already refuses to run while I/O messages are pending, so no message is owned by the device at this point and the reset does not race with message completion. Reproduced in QEMU (virtio-sound-pci, wav audiodev): prepare, write 1.5 periods, drop, prepare, write 16 full periods, drain. Without this patch WRITEI returns -EIO after 14 periods and DRAIN fails; with it all 16 periods are played and DRAIN succeeds. Period-aligned prefill works in both cases. The same sequence on capture (read 1.5 periods, drop, prepare, read 16 periods) fails with -EIO after 10 periods without this patch and reads all 16 with it. Found by code inspection, confirmed with the reproducer above. Fixes: fe981e67568c ("ALSA: virtio: use ack callback") Assisted-by: LLM Signed-off-by: Ting-Han Hou Link: https://patch.msgid.link/20261011125426.35745-1-ue081723@gmail.com Signed-off-by: Takashi Iwai --- sound/virtio/virtio_pcm.h | 2 ++ sound/virtio/virtio_pcm_msg.c | 19 +++++++++++++++++++ sound/virtio/virtio_pcm_ops.c | 1 + 3 files changed, 22 insertions(+) diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index 368dafa5df3f..8837dfc12828 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -118,6 +118,8 @@ int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *vss, void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss); +void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss); + int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss, unsigned long offset, unsigned long bytes); diff --git a/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c index a5c4e7027717..72103f606716 100644 --- a/sound/virtio/virtio_pcm_msg.c +++ b/sound/virtio/virtio_pcm_msg.c @@ -182,6 +182,25 @@ void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss) vss->nmsgs = 0; } +/** + * virtsnd_pcm_msg_reset() - Reset the fill level of all I/O messages. + * @vss: VirtIO PCM substream. + * + * Messages that were only partially filled by the application when the + * substream was stopped are never sent to the device, so their length must be + * cleared before the substream is started again. + * + * Context: Any context. Takes and releases the VirtIO substream spinlock. + */ +void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss) +{ + unsigned int i; + + guard(spinlock_irqsave)(&vss->lock); + for (i = 0; vss->msgs && i < vss->nmsgs; ++i) + vss->msgs[i]->length = 0; +} + /** * virtsnd_pcm_msg_send() - Send asynchronous I/O messages. * @vss: VirtIO PCM substream. diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c index 1105e7ff3523..1091100a4c98 100644 --- a/sound/virtio/virtio_pcm_ops.c +++ b/sound/virtio/virtio_pcm_ops.c @@ -299,6 +299,7 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) vss->xfer_xrun = false; vss->suspended = false; vss->msg_count = 0; + virtsnd_pcm_msg_reset(vss); memset(&vss->pcm_indirect, 0, sizeof(vss->pcm_indirect)); vss->pcm_indirect.sw_buffer_size = From 8af4d760f61fefa60d9e397d596f09a7d2c96be4 Mon Sep 17 00:00:00 2001 From: Lianqin Hu Date: Mon, 12 Oct 2026 06:55:03 +0000 Subject: [PATCH 653/924] ALSA: usb-audio: Add iface reset and delay quirk for rongyuan GS20 Setting up the interface when suspended/resuming fails on this card. Adding a reset and delay quirk will eliminate this problem. usb 1-1: New USB device found, idVendor=1237, idProduct=17b8 usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 1-1: Product: GS20 usb 1-1: Manufacturer: rongyuan usb 1-1: SerialNumber: 20250228 Signed-off-by: Lianqin Hu Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/TYUPR06MB62171DFA33D7E17FD3F73D24D29F2@TYUPR06MB6217.apcprd06.prod.outlook.com --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index f4d7f68f4ee9..0999b79ca075 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2659,6 +2659,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1237, 0x17b8, /* rongyuan GS20 */ + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE | QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), From 682d6bb5f783adea7fa2a5b05166bff01b338081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Ahmet=20Memi=C5=9F?= Date: Sun, 11 Oct 2026 21:06:17 +0300 Subject: [PATCH 654/924] ALSA: control_led: Fix deadlock between sysfs writes and card disconnect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_ctl_led_disconnect() removes the ctl-led sysfs files while holding card->controls_rwsem. device_unregister() waits for running sysfs callbacks which can deadlock when a concurrent write causes the attach or detach callback to wait for controls_rwsem. Break kernfs active protection in the attach, detach and list callbacks and hold a module reference instead. Fixes: a135dfb5de15 ("ALSA: led control - add sysfs kcontrol LED marking layer") Signed-off-by: Ali Ahmet MemiÅŸ Link: https://patch.msgid.link/20261011180627.1594051-1-aliamemis@disroot.org Signed-off-by: Takashi Iwai --- sound/core/control_led.c | 62 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/sound/core/control_led.c b/sound/core/control_led.c index ec0e6c7ad657..501387287ae6 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -563,12 +563,43 @@ static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, si return count; } +/* + * snd_ctl_led_disconnect() removes these files with card->controls_rwsem + * held. so don't let the removal wait for a callback that takes it. + */ +static struct kernfs_node *snd_ctl_led_sysfs_enter(struct device *dev, + struct device_attribute *attr) +{ + struct kernfs_node *kn; + + if (!try_module_get(THIS_MODULE)) + return NULL; + kn = sysfs_break_active_protection(&dev->kobj, &attr->attr); + if (!kn) + module_put(THIS_MODULE); + return kn; +} + +static void snd_ctl_led_sysfs_exit(struct kernfs_node *kn) +{ + sysfs_unbreak_active_protection(kn); + module_put(THIS_MODULE); +} + static ssize_t attach_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); - return set_led_id(led_card, buf, count, true); + struct kernfs_node *kn; + ssize_t ret; + + kn = snd_ctl_led_sysfs_enter(dev, attr); + if (!kn) + return -ENODEV; + ret = set_led_id(led_card, buf, count, true); + snd_ctl_led_sysfs_exit(kn); + return ret; } static ssize_t detach_store(struct device *dev, @@ -576,7 +607,15 @@ static ssize_t detach_store(struct device *dev, const char *buf, size_t count) { struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); - return set_led_id(led_card, buf, count, false); + struct kernfs_node *kn; + ssize_t ret; + + kn = snd_ctl_led_sysfs_enter(dev, attr); + if (!kn) + return -ENODEV; + ret = set_led_id(led_card, buf, count, false); + snd_ctl_led_sysfs_exit(kn); + return ret; } static ssize_t reset_store(struct device *dev, @@ -594,10 +633,8 @@ static ssize_t reset_store(struct device *dev, return count; } -static ssize_t list_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t snd_ctl_led_list(struct snd_ctl_led_card *led_card, char *buf) { - struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); struct snd_ctl_led_ctl *lctl; size_t l = 0; struct snd_card *card __free(snd_card_unref) = @@ -620,6 +657,21 @@ static ssize_t list_show(struct device *dev, return l; } +static ssize_t list_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); + struct kernfs_node *kn; + ssize_t ret; + + kn = snd_ctl_led_sysfs_enter(dev, attr); + if (!kn) + return -ENODEV; + ret = snd_ctl_led_list(led_card, buf); + snd_ctl_led_sysfs_exit(kn); + return ret; +} + static DEVICE_ATTR_WO(attach); static DEVICE_ATTR_WO(detach); static DEVICE_ATTR_WO(reset); From 293821f3c53eb89d1ee6a92925750164d04826be Mon Sep 17 00:00:00 2001 From: Sandro Pischinger Date: Mon, 12 Oct 2026 14:42:55 +0200 Subject: [PATCH 655/924] ALSA: usb-audio: Add iface reset quirk for TASCAM US-16x08 When suspending and resuming the playback stream, audible glitching/chirping appears on the audio interface's output for several seconds. On playback stream resumption, I observed an increase in feedback rate up to 48,038Hz. This stabilized back to 48,000Hz after ~20s. This is mitigated by applying an interface reset on stream resumption, as introduced by John Keeping on the TASCAM Model 12. Link: https://lore.kernel.org/20221129130100.1257904-1-john@metanate.com Signed-off-by: Sandro Pischinger Link: https://patch.msgid.link/20261012124255.10485-1-kernel@sandropischinger.de Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 7cd806e9611b..8393a0af7b40 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2578,6 +2578,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x0644, 0x8044, /* Esoteric D-05X */ QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY | QUIRK_FLAG_FORCE_IFACE_RESET), + DEVICE_FLG(0x0644, 0x8047, /* TASCAM US-16x08 */ + QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY | + QUIRK_FLAG_FORCE_IFACE_RESET), DEVICE_FLG(0x0644, 0x804a, /* TEAC UD-301 */ QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY), From dbfd3fb74ce4adb7a87da975ba0250b8aa6c571e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Miranda?= Date: Mon, 12 Oct 2026 12:00:24 +0100 Subject: [PATCH 656/924] ALSA: hda/conexant: Add quirk for Acer Aspire Go 16 mic mute LED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Acer Aspire Go 16 (AG16-71P) uses a Conexant SN6180 codec (subsystem ID 0x1025:0x1937) with the microphone mute LED wired to GPIO4. Add a quirk entry so the kernel drives the LED in sync with the microphone mute state. The fixup reuses the existing cxt_setup_mute_led() helper with mask 0x10, matching the behavior verified on the hardware. Signed-off-by: Gonçalo Miranda Link: https://patch.msgid.link/20261012110024.25310-1-me@goncalom.dev Signed-off-by: Takashi Iwai --- sound/hda/codecs/conexant.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index 91cbd728372f..a141c94eee13 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -302,6 +302,7 @@ enum { CXT_FIXUP_HP_SPECTRE, CXT_FIXUP_HP_GATE_MIC, CXT_FIXUP_MUTE_LED_GPIO, + CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED, CXT_FIXUP_HP_ELITEONE_OUT_DIS, CXT_FIXUP_HP_ZBOOK_MUTE_LED, CXT_FIXUP_HEADSET_MIC, @@ -809,6 +810,13 @@ static void cxt_fixup_hp_zbook_mute_led(struct hda_codec *codec, cxt_setup_mute_led(codec, 0x10, 0x20); } +static void cxt_fixup_acer_aspire_go_mute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + cxt_setup_mute_led(codec, 0, 0x10); +} + static void cxt_fixup_hp_a_u(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -1069,6 +1077,10 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_mute_led_gpio, }, + [CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_acer_aspire_go_mute_led, + }, [CXT_FIXUP_HP_ELITEONE_OUT_DIS] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_update_pinctl, @@ -1178,6 +1190,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x1025, 0x054c, "Acer Aspire 3830TG", CXT_FIXUP_ASPIRE_DMIC), SND_PCI_QUIRK(0x1025, 0x054f, "Acer Aspire 4830T", CXT_FIXUP_ASPIRE_DMIC), SND_PCI_QUIRK(0x1025, 0x136d, "Acer Swift SF314", CXT_FIXUP_ACER_SWIFT_HP), + SND_PCI_QUIRK(0x1025, 0x1937, "Acer Aspire Go 16", CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x807C, "HP EliteBook 820 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x80FD, "HP ProBook 640 G2", CXT_FIXUP_HP_DOCK), From 8d70e364e866bdfca81eee24d0688fac9472cfb9 Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Mon, 12 Oct 2026 16:41:52 +0800 Subject: [PATCH 657/924] ALSA: hda: cs35l41: Fix ACPI device reference leak on probe failure cs35l41_hda_probe() takes an acpi_device through cs35l41_hda_read_acpi() and stores it in cs35l41->dacpi. Only the failures that happen later reach the err: label, which is where that reference and cs35l41->acpi_subsystem_id are released, so the early return taken when cs35l41_hda_read_acpi() fails leaves both behind. Keep the ownership in the caller instead of dropping the reference inside cs35l41_hda_read_acpi(). That removes the need to work out whether a given failure path has already released it, and with it the double put that cs35l41_hda_parse_acpi() would otherwise cause when it fails after having released the device itself. cs35l41_hda_parse_acpi() and the !physdev path no longer put the reference, and the early failure enters the existing err: cleanup tail through a new err_acpi label. The label sits between the reset of the codec and the gpio cleanup, so an early failure also releases the reset and chip-select descriptors that cs35l41_hda_parse_acpi() may already have taken, while still skipping cs35l41_safe_reset() on a codec that was never brought up. cs35l41->dacpi comes from devm_kzalloc() and is only assigned once the ACPI device has been found, so the put is a no-op when the lookup itself failed. Found by code review. Fixes: 447106e92a0c ("ALSA: hda: cs35l41: Support mute notifications for CS35L41 HDA") Cc: stable@vger.kernel.org Signed-off-by: Ma Ke Link: https://patch.msgid.link/20261012084152.3740340-1-make_ruc2021@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/cs35l41_hda.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c index 62296be45c55..d50c3d8f5303 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c @@ -1907,7 +1907,6 @@ int cs35l41_hda_parse_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, hw_cfg->valid = false; hw_cfg->gpio1.valid = false; hw_cfg->gpio2.valid = false; - acpi_dev_put(cs35l41->dacpi); return ret; } @@ -1928,10 +1927,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i cs35l41->dacpi = adev; struct device *physdev __free(put_device) = get_device(acpi_get_first_physical_node(adev)); - if (!physdev) { - acpi_dev_put(adev); + if (!physdev) return -ENODEV; - } sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); if (IS_ERR(sub)) @@ -1986,8 +1983,10 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i dev_set_drvdata(dev, cs35l41); ret = cs35l41_hda_read_acpi(cs35l41, device_name, id); - if (ret) - return dev_err_probe(cs35l41->dev, ret, "Platform not supported\n"); + if (ret) { + dev_err_probe(cs35l41->dev, ret, "Platform not supported\n"); + goto err_acpi; + } if (IS_ERR(cs35l41->reset_gpio)) { ret = PTR_ERR(cs35l41->reset_gpio); @@ -2074,6 +2073,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i err: if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type)) gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); +err_acpi: gpiod_put(cs35l41->reset_gpio); gpiod_put(cs35l41->cs_gpio); acpi_dev_put(cs35l41->dacpi); From 50fb7f7fc7e4341bd5779905972fa94539ea6a49 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 9 Oct 2026 18:22:03 +0700 Subject: [PATCH 658/924] ASoC: codecs: rt5645: Simplify optional GPIO error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gpiod_not_found() converts -ENOENT to NULL before devm_gpiod_get_optional() returns, so there is no need to handle -ENOENT explicitly. Use dev_err_probe() to simplify error reporting and handle -EPROBE_DEFER. Signed-off-by: bui duc phuc Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/20261009112204.33199-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5645.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 15d21097ed0c..63e3719059ea 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -4045,26 +4045,16 @@ static int rt5645_i2c_probe(struct i2c_client *i2c) rt5645->gpiod_hp_det = devm_gpiod_get_optional(&i2c->dev, "hp-detect", GPIOD_IN); - if (IS_ERR(rt5645->gpiod_hp_det)) { - dev_info(&i2c->dev, "failed to initialize gpiod\n"); - ret = PTR_ERR(rt5645->gpiod_hp_det); - /* - * Continue if optional gpiod is missing, bail for all other - * errors, including -EPROBE_DEFER - */ - if (ret != -ENOENT) - return ret; - } + if (IS_ERR(rt5645->gpiod_hp_det)) + return dev_err_probe(&i2c->dev, PTR_ERR(rt5645->gpiod_hp_det), + "failed to get hp-detect gpio\n"); rt5645->gpiod_cbj_sleeve = devm_gpiod_get_optional(&i2c->dev, "cbj-sleeve", GPIOD_OUT_LOW); - if (IS_ERR(rt5645->gpiod_cbj_sleeve)) { - ret = PTR_ERR(rt5645->gpiod_cbj_sleeve); - dev_info(&i2c->dev, "failed to initialize gpiod, ret=%d\n", ret); - if (ret != -ENOENT) - return ret; - } + if (IS_ERR(rt5645->gpiod_cbj_sleeve)) + return dev_err_probe(&i2c->dev, PTR_ERR(rt5645->gpiod_cbj_sleeve), + "failed to get cbj-sleeve gpio\n"); for (i = 0; i < ARRAY_SIZE(rt5645->supplies); i++) rt5645->supplies[i].supply = rt5645_supply_names[i]; From c1e321bd5ce6ae3702db65837cfe0c792634d603 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 9 Oct 2026 18:22:04 +0700 Subject: [PATCH 659/924] ASoC: codecs: rt5659: propagate GPIO errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit devm_gpiod_get_optional() may return an error pointer, including -EPROBE_DEFER, but rt5659_i2c_probe() does not handle these errors for the ldo1-en GPIO and does not check for errors for the reset GPIO. Check the return values and propagate GPIO lookup errors. Fixes: d3cb2de2479b ("ASoC: rt5659: add rt5659 codec driver") Signed-off-by: bui duc phuc Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/20261009112204.33199-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5659.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt5659.c b/sound/soc/codecs/rt5659.c index 389be7cbfe05..af8051fa69f8 100644 --- a/sound/soc/codecs/rt5659.c +++ b/sound/soc/codecs/rt5659.c @@ -4121,10 +4121,14 @@ static int rt5659_i2c_probe(struct i2c_client *i2c) rt5659->gpiod_ldo1_en = devm_gpiod_get_optional(&i2c->dev, "ldo1-en", GPIOD_OUT_HIGH); if (IS_ERR(rt5659->gpiod_ldo1_en)) - dev_warn(&i2c->dev, "Request ldo1-en GPIO failed\n"); + return dev_err_probe(&i2c->dev, PTR_ERR(rt5659->gpiod_ldo1_en), + "failed to get ldo1-en GPIO\n"); rt5659->gpiod_reset = devm_gpiod_get_optional(&i2c->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(rt5659->gpiod_reset)) + return dev_err_probe(&i2c->dev, PTR_ERR(rt5659->gpiod_reset), + "failed to get reset GPIO\n"); /* Sleep for 300 ms minimum */ msleep(300); From 0f2bec6921f0971c2fc2f8bb5a93229b6546dded Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 7 Oct 2026 15:33:47 +0200 Subject: [PATCH 660/924] ASoC: core: Add component constructor There is a gap between snd_soc_component_alloc() and snd_soc_register_component(). A driver that obtains a component object through snd_soc_component_alloc() receives an object in uninitialized state. soc_component_initialize() is the function responsible for bringing it into a sane one but it is not accessible by the framework users. The framework shall prevent manipulation of uninitialized components. Introduce snd_soc_component_new() which acts as a constructor: allocates and initializes the component object before returning it to the caller. Cc: Kuninori Morimoto Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20261007133353.455185-2-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 2 ++ sound/soc/soc-core.c | 44 +++++++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 6c1acc984ecb..d51f71e2f334 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -292,6 +292,8 @@ static inline int snd_soc_component_cache_sync( return regcache_sync(component->regmap); } +struct snd_soc_component *snd_soc_component_new(struct device *dev, const char *name, + const struct snd_soc_component_driver *driver); struct snd_soc_component *snd_soc_component_alloc(struct device *dev); void snd_soc_component_set_name(struct snd_soc_component *component, const char *name); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 06167a198591..c4de336ab408 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2589,6 +2589,42 @@ static int soc_component_initialize(struct snd_soc_component *component, return 0; } +struct snd_soc_component *snd_soc_component_new(struct device *dev, const char *name, + const struct snd_soc_component_driver *driver) +{ + struct snd_soc_component *component; + + component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); + if (!component) + return ERR_PTR(-ENOMEM); + + component->dapm = snd_soc_dapm_alloc(dev); + if (!component->dapm) + return ERR_PTR(-ENOMEM); + + if (!name) { + name = snd_soc_fmt_single_name(dev, NULL); + if (!name) { + dev_err(dev, "ASoC: Failed to allocate name\n"); + return ERR_PTR(-ENOMEM); + } + } + + component->dev = dev; + component->name = name; + component->driver = driver; + + INIT_LIST_HEAD(&component->dai_list); + INIT_LIST_HEAD(&component->dobj_list); + INIT_LIST_HEAD(&component->card_list); + INIT_LIST_HEAD(&component->list); + INIT_LIST_HEAD(&component->card_aux_list); + mutex_init(&component->io_mutex); + + return component; +} +EXPORT_SYMBOL_GPL(snd_soc_component_new); + static int soc_component_add(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, int num_dai) @@ -2653,11 +2689,11 @@ int snd_soc_register_component_d(struct device *dev, { struct snd_soc_component *component; - component = snd_soc_component_alloc(dev); - if (!component) - return -ENOMEM; + component = snd_soc_component_new(dev, NULL, component_driver); + if (IS_ERR(component)) + return PTR_ERR(component); - return snd_soc_register_component_c(component, component_driver, dai_drv, num_dai); + return soc_component_add(component, dai_drv, num_dai); } EXPORT_SYMBOL_GPL(snd_soc_register_component_d); From 4e2db62172b5e260550202f2cc6b97db6de98477 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 7 Oct 2026 15:33:48 +0200 Subject: [PATCH 661/924] ASoC: core: Rename and export soc_component_add() To keep the register/unregister pattern intact, rename to the function before exporting it. This concludes the registration update, leaving the following two options available to the callers: 1) snd_soc_component_register(component..) 2) snd_soc_register_component(dev..) A driver uses 1) when attempting to register a pre-constructed component object explicitly and 2) when they want the ASoC framework to do the entire job for them. Cc: Kuninori Morimoto Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20261007133353.455185-3-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 3 +++ sound/soc/soc-core.c | 11 ++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index d51f71e2f334..f4d4f836269e 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -294,6 +294,9 @@ static inline int snd_soc_component_cache_sync( struct snd_soc_component *snd_soc_component_new(struct device *dev, const char *name, const struct snd_soc_component_driver *driver); +int snd_soc_component_register(struct snd_soc_component *component, + struct snd_soc_dai_driver *dai_drv, + int num_dai); struct snd_soc_component *snd_soc_component_alloc(struct device *dev); void snd_soc_component_set_name(struct snd_soc_component *component, const char *name); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index c4de336ab408..93bc7fa32444 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2625,9 +2625,9 @@ struct snd_soc_component *snd_soc_component_new(struct device *dev, const char * } EXPORT_SYMBOL_GPL(snd_soc_component_new); -static int soc_component_add(struct snd_soc_component *component, - struct snd_soc_dai_driver *dai_drv, - int num_dai) +int snd_soc_component_register(struct snd_soc_component *component, + struct snd_soc_dai_driver *dai_drv, + int num_dai) { struct snd_soc_card *card, *c; int ret; @@ -2666,6 +2666,7 @@ static int soc_component_add(struct snd_soc_component *component, return ret; } +EXPORT_SYMBOL_GPL(snd_soc_component_register); int snd_soc_register_component_c(struct snd_soc_component *component, const struct snd_soc_component_driver *component_driver, @@ -2678,7 +2679,7 @@ int snd_soc_register_component_c(struct snd_soc_component *component, if (ret < 0) return ret; - return soc_component_add(component, dai_drv, num_dai); + return snd_soc_component_register(component, dai_drv, num_dai); } EXPORT_SYMBOL_GPL(snd_soc_register_component_c); @@ -2693,7 +2694,7 @@ int snd_soc_register_component_d(struct device *dev, if (IS_ERR(component)) return PTR_ERR(component); - return soc_component_add(component, dai_drv, num_dai); + return snd_soc_component_register(component, dai_drv, num_dai); } EXPORT_SYMBOL_GPL(snd_soc_register_component_d); From 857469990ee1a69bec3fb60e757c7a62582a5f44 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 7 Oct 2026 15:33:49 +0200 Subject: [PATCH 662/924] ASoC: dmaengine: Switch to snd_snd_component_new() snd_soc_component_alloc() leaves the component object in uninitialized state. Switch to the constructor function to retrieve and operate on object in sane state. Cc: Kuninori Morimoto Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20261007133353.455185-4-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/soc-generic-dmaengine-pcm.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index f6206ecaf26f..d750dc44981d 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -454,10 +454,6 @@ int snd_dmaengine_pcm_register(struct device *dev, struct dmaengine_pcm *pcm; int ret; - component = snd_soc_component_alloc(dev); - if (!component) - return -ENOMEM; - pcm = kzalloc_obj(*pcm); if (!pcm) return -ENOMEM; @@ -467,10 +463,6 @@ int snd_dmaengine_pcm_register(struct device *dev, pcm->config = config; pcm->flags = flags; - if (config->name) - snd_soc_component_set_name(component, config->name); - snd_soc_component_set_priv(component, pcm); - ret = dmaengine_pcm_request_chan_of(pcm, dev, config); if (ret) goto err_free_dma; @@ -480,7 +472,15 @@ int snd_dmaengine_pcm_register(struct device *dev, else driver = &dmaengine_pcm_component; - ret = snd_soc_register_component(component, driver, NULL, 0); + component = snd_soc_component_new(dev, config->name, driver); + if (IS_ERR(component)) { + ret = PTR_ERR(component); + goto err_free_dma; + } + + snd_soc_component_set_priv(component, pcm); + + ret = snd_soc_component_register(component, NULL, 0); if (ret) goto err_free_dma; From c0e1ffec60ce10101643f8bf7e45f42b0fdaa002 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 7 Oct 2026 15:33:50 +0200 Subject: [PATCH 663/924] ASoC: topology-test: Switch to snd_snd_component_new() snd_soc_component_alloc() leaves the component object in uninitialized state. Switch to the constructor function to retrieve and operate on object in sane state. Cc: Kuninori Morimoto Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20261007133353.455185-5-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/soc-topology-test.c | 66 +++++++++++++++++------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/sound/soc/soc-topology-test.c b/sound/soc/soc-topology-test.c index 346a52d3006a..bcfb52061133 100644 --- a/sound/soc/soc-topology-test.c +++ b/sound/soc/soc-topology-test.c @@ -246,8 +246,8 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component_null_comp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -256,7 +256,7 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component_null_comp, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -291,8 +291,8 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -301,7 +301,7 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -355,8 +355,8 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component_null_fw); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -365,7 +365,7 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component_null_fw, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -406,8 +406,8 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -416,7 +416,7 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -464,8 +464,8 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -474,7 +474,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -522,8 +522,8 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -532,7 +532,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -580,8 +580,8 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -590,7 +590,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -639,8 +639,8 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -649,7 +649,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -690,8 +690,8 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -700,7 +700,7 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); snd_soc_unregister_component(test_dev); @@ -743,8 +743,8 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -755,7 +755,7 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) for (i = 0; i < 100; i++) { - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); snd_soc_unregister_component(test_dev); @@ -799,13 +799,13 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); /* run test */ - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); for (i = 0; i < 100; i++) { From 6cb74a0741dac2a242eefb00f7052cc85b7cffc8 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 7 Oct 2026 15:33:51 +0200 Subject: [PATCH 664/924] ASoC: Intel: catpt: Switch to snd_snd_component_new() snd_soc_component_alloc() leaves the component object in uninitialized state. Switch to the constructor function to retrieve and operate on object in sane state. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20261007133353.455185-6-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/catpt/pcm.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c index bbba0d959ea2..ca42bc531c6b 100644 --- a/sound/soc/intel/catpt/pcm.c +++ b/sound/soc/intel/catpt/pcm.c @@ -1073,13 +1073,9 @@ int catpt_register_plat_component(struct catpt_dev *cdev) { struct snd_soc_component *component; - component = snd_soc_component_alloc(cdev->dev); - if (!component) - return -ENOMEM; + component = snd_soc_component_new(cdev->dev, catpt_comp_driver.name, &catpt_comp_driver); + if (IS_ERR(component)) + return PTR_ERR(component); - snd_soc_component_set_name(component, catpt_comp_driver.name); - - return snd_soc_register_component(component, - &catpt_comp_driver, - dai_drivers, ARRAY_SIZE(dai_drivers)); + return snd_soc_component_register(component, dai_drivers, ARRAY_SIZE(dai_drivers)); } From d7065004a33aedc864fa49b3a59c7f7cd79be900 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 7 Oct 2026 15:33:52 +0200 Subject: [PATCH 665/924] ASoC: Intel: avs: Switch to snd_snd_component_new() snd_soc_component_alloc() leaves the component object in uninitialized state. Switch to the constructor function to retrieve and operate on object in sane state. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20261007133353.455185-7-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/pcm.c | 11 +++++------ sound/soc/intel/avs/probes.c | 12 ++++-------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c index 2d7ced953fde..6dd2b938baaa 100644 --- a/sound/soc/intel/avs/pcm.c +++ b/sound/soc/intel/avs/pcm.c @@ -1399,10 +1399,6 @@ int avs_register_component(struct device *dev, const char *name, if (!acomp) return -ENOMEM; - acomp->base = snd_soc_component_alloc(dev); - if (!acomp->base) - return -ENOMEM; - comp_name = devm_kstrdup(dev, name, GFP_KERNEL); if (!comp_name) return -ENOMEM; @@ -1411,10 +1407,13 @@ int avs_register_component(struct device *dev, const char *name, drv->use_dai_pcm_id = !obsolete_card_names; - snd_soc_component_set_name(acomp->base, comp_name); + acomp->base = snd_soc_component_new(dev, comp_name, drv); + if (IS_ERR(acomp->base)) + return PTR_ERR(acomp->base); + snd_soc_component_set_priv(acomp->base, acomp); - return snd_soc_register_component(acomp->base, drv, cpu_dais, num_cpu_dais); + return snd_soc_component_register(acomp->base, cpu_dais, num_cpu_dais); } static struct snd_soc_dai_driver dmic_cpu_dais[] = { diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c index 2e4063b743ba..483e151dfd66 100644 --- a/sound/soc/intel/avs/probes.c +++ b/sound/soc/intel/avs/probes.c @@ -298,17 +298,13 @@ int avs_register_probe_component(struct avs_dev *adev, const char *name) struct snd_soc_component *component; const char *comp_name; - component = snd_soc_component_alloc(adev->dev); - if (!component) - return -ENOMEM; - comp_name = devm_kstrdup(adev->dev, name, GFP_KERNEL); if (!comp_name) return -ENOMEM; - snd_soc_component_set_name(component, comp_name); + component = snd_soc_component_new(adev->dev, comp_name, &avs_probe_component_driver); + if (IS_ERR(component)) + return PTR_ERR(component); - return snd_soc_register_component(component, - &avs_probe_component_driver, - probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); + return snd_soc_component_register(component, probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); } From 146caaa16f85b895cd2a6370092961da784e16b7 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 7 Oct 2026 15:33:53 +0200 Subject: [PATCH 666/924] ASoC: core: Remove unused component-register members With the new component constructor, functions snd_soc_component_alloc(), snd_soc_component_set_name() and soc_component_initialize() become unused. Remove them and drop the generic macro as there is only one snd_soc_register_component() function. Cc: Kuninori Morimoto Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20261007133353.455185-8-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 2 -- include/sound/soc.h | 8 +----- sound/soc/soc-component.c | 19 ------------ sound/soc/soc-core.c | 54 ++++------------------------------- 4 files changed, 6 insertions(+), 77 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index f4d4f836269e..ac5e9412582e 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -297,9 +297,7 @@ struct snd_soc_component *snd_soc_component_new(struct device *dev, const char * int snd_soc_component_register(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, int num_dai); -struct snd_soc_component *snd_soc_component_alloc(struct device *dev); -void snd_soc_component_set_name(struct snd_soc_component *component, const char *name); const char *snd_soc_component_name(struct snd_soc_component *component); void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv); diff --git a/include/sound/soc.h b/include/sound/soc.h index 553bb7e2be98..08fdee15dba7 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -447,15 +447,9 @@ static inline int snd_soc_resume(struct device *dev) } #endif int snd_soc_poweroff(struct device *dev); -int snd_soc_register_component_c(struct snd_soc_component *component, - const struct snd_soc_component_driver *component_driver, - struct snd_soc_dai_driver *dai_drv, int num_dai); -int snd_soc_register_component_d(struct device *dev, +int snd_soc_register_component(struct device *dev, const struct snd_soc_component_driver *component_driver, struct snd_soc_dai_driver *dai_drv, int num_dai); -#define snd_soc_register_component(x, ...) _Generic((x), \ -struct device * : snd_soc_register_component_d, \ -struct snd_soc_component * : snd_soc_register_component_c)(x, __VA_ARGS__) int devm_snd_soc_register_component(struct device *dev, const struct snd_soc_component_driver *component_driver, diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 463ea227cac8..34efb270ce29 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -29,25 +29,6 @@ static inline int _soc_component_ret_reg_rw(struct snd_soc_component *component, func, component->name, reg); } -struct snd_soc_component *snd_soc_component_alloc(struct device *dev) -{ - struct snd_soc_component *component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); - - if (!component) - return NULL; - - component->dev = dev; - - return component; -} -EXPORT_SYMBOL_GPL(snd_soc_component_alloc); - -void snd_soc_component_set_name(struct snd_soc_component *component, const char *name) -{ - component->name = name; -} -EXPORT_SYMBOL_GPL(snd_soc_component_set_name); - const char *snd_soc_component_name(struct snd_soc_component *component) { return component->name; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 93bc7fa32444..69181cc0258b 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2560,35 +2560,6 @@ static void snd_soc_del_component_unlocked(struct snd_soc_component *component) list_del(&component->list); } -static int soc_component_initialize(struct snd_soc_component *component, - const struct snd_soc_component_driver *driver) -{ - struct device *dev = component->dev; - - component->dapm = snd_soc_dapm_alloc(dev); - if (!component->dapm) - return -ENOMEM; - - INIT_LIST_HEAD(&component->dai_list); - INIT_LIST_HEAD(&component->dobj_list); - INIT_LIST_HEAD(&component->card_list); - INIT_LIST_HEAD(&component->list); - INIT_LIST_HEAD(&component->card_aux_list); - mutex_init(&component->io_mutex); - - if (!component->name) { - component->name = snd_soc_fmt_single_name(dev, NULL); - if (!component->name) { - dev_err(dev, "ASoC: Failed to allocate name\n"); - return -ENOMEM; - } - } - - component->driver = driver; - - return 0; -} - struct snd_soc_component *snd_soc_component_new(struct device *dev, const char *name, const struct snd_soc_component_driver *driver) { @@ -2668,25 +2639,10 @@ int snd_soc_component_register(struct snd_soc_component *component, } EXPORT_SYMBOL_GPL(snd_soc_component_register); -int snd_soc_register_component_c(struct snd_soc_component *component, - const struct snd_soc_component_driver *component_driver, - struct snd_soc_dai_driver *dai_drv, - int num_dai) -{ - int ret; - - ret = soc_component_initialize(component, component_driver); - if (ret < 0) - return ret; - - return snd_soc_component_register(component, dai_drv, num_dai); -} -EXPORT_SYMBOL_GPL(snd_soc_register_component_c); - -int snd_soc_register_component_d(struct device *dev, - const struct snd_soc_component_driver *component_driver, - struct snd_soc_dai_driver *dai_drv, - int num_dai) +int snd_soc_register_component(struct device *dev, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, + int num_dai) { struct snd_soc_component *component; @@ -2696,7 +2652,7 @@ int snd_soc_register_component_d(struct device *dev, return snd_soc_component_register(component, dai_drv, num_dai); } -EXPORT_SYMBOL_GPL(snd_soc_register_component_d); +EXPORT_SYMBOL_GPL(snd_soc_register_component); /** * snd_soc_unregister_component_by_driver - Unregister component using a given driver From e7860dc5d8d45f6a0e98bfe542c7b776c5ede2a4 Mon Sep 17 00:00:00 2001 From: Joonhoe Kim <26rote@gmail.com> Date: Wed, 7 Oct 2026 21:39:54 +0900 Subject: [PATCH 667/924] ASoC: codecs: wcd939x: read the SoundWire port switches per channel The "HPHL/HPHR", "ADCn" and "DMICn" switches are channels of shared SoundWire ports (ADC1-4 are channels 0-3 of the ADC_1_4 port), but wcd939x_get_swr_port() reports a per-port flag. Once one channel of a port is switched on, every switch on that port reads back as on: amixer cset name='ADC1 Switch' on -> ADC1..ADC4 Switch all read "on" Setting one of the others then looks like a no-op to userspace, and "alsactl store" saves all of them as on, so a later "alsactl restore" really enables the unused channels of the port. Read the channel's own bit in the port's channel mask instead, drop the now unused port_enable[] and return 0 from the put handler when the switch does not change, as ALSA expects. Fixes: 10f514bd172a ("ASoC: codecs: Add WCD939x Codec driver") Link: https://lore.kernel.org/all/20251124064850.15419-6-jonathan@marek.ca/ Assisted-by: LLM Signed-off-by: Joonhoe Kim <26rote@gmail.com> Link: https://patch.msgid.link/20261007123954.1709-1-26rote@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wcd939x.c | 10 +++++++--- sound/soc/codecs/wcd939x.h | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/wcd939x.c b/sound/soc/codecs/wcd939x.c index 26fe0b6313cd..2d463f062e6d 100644 --- a/sound/soc/codecs/wcd939x.c +++ b/sound/soc/codecs/wcd939x.c @@ -1794,8 +1794,9 @@ static int wcd939x_get_swr_port(struct snd_kcontrol *kcontrol, struct wcd939x_priv *wcd939x = snd_soc_component_get_drvdata(comp); struct wcd939x_sdw_priv *wcd = wcd939x->sdw_priv[mixer->shift]; unsigned int portidx = wcd->ch_info[mixer->reg].port_num; + u8 ch_mask = wcd->ch_info[mixer->reg].ch_mask; - ucontrol->value.integer.value[0] = wcd->port_enable[portidx] ? 1 : 0; + ucontrol->value.integer.value[0] = !!(wcd->port_config[portidx - 1].ch_mask & ch_mask); return 0; } @@ -1821,10 +1822,13 @@ static int wcd939x_set_swr_port(struct snd_kcontrol *kcontrol, struct wcd939x_priv *wcd939x = snd_soc_component_get_drvdata(comp); struct wcd939x_sdw_priv *wcd = wcd939x->sdw_priv[mixer->shift]; unsigned int portidx = wcd->ch_info[mixer->reg].port_num; + u8 ch_mask = wcd->ch_info[mixer->reg].ch_mask; + bool enable = !!ucontrol->value.integer.value[0]; - wcd->port_enable[portidx] = !!ucontrol->value.integer.value[0]; + if (enable == !!(wcd->port_config[portidx - 1].ch_mask & ch_mask)) + return 0; - wcd939x_connect_port(wcd, portidx, mixer->reg, wcd->port_enable[portidx]); + wcd939x_connect_port(wcd, portidx, mixer->reg, enable); return 1; } diff --git a/sound/soc/codecs/wcd939x.h b/sound/soc/codecs/wcd939x.h index 6bd2366587a8..dab7ef108b48 100644 --- a/sound/soc/codecs/wcd939x.h +++ b/sound/soc/codecs/wcd939x.h @@ -899,7 +899,6 @@ struct wcd939x_sdw_priv { struct sdw_stream_runtime *sruntime; struct sdw_port_config port_config[WCD939X_MAX_SWR_PORTS]; const struct wcd_sdw_ch_info *ch_info; - bool port_enable[WCD939X_MAX_SWR_CH_IDS]; int active_ports; bool is_tx; struct wcd939x_priv *wcd939x; From d3b59d50e1ff206aa519ed5c13bf6907f97dd6a6 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sun, 11 Oct 2026 23:55:56 +0800 Subject: [PATCH 668/924] ALSA: jack: Remove debugfs before freeing unregistered jacks Jack debugfs files store a raw snd_jack_kctl pointer, so their callbacks must be drained before the attachment is freed. snd_jack_dev_free() unlinks and frees the attachments before calling snd_jack_dev_disconnect(). Its late snd_jack_remove_debugfs() call then has no entries left to traverse. With CONFIG_SND_JACK_INJECTION_DEBUG and input support enabled, an HDA codec rebind on a live card can publish a non-phantom jack's type file while its component is still in SNDRV_DEV_BUILD. A later jack-control allocation failure permits the following order: 1. hda_codec_driver_probe() creates a jack and publishes its debugfs files before reaching snd_card_register(). 2. A later control-build failure calls snd_hda_codec_cleanup_for_unbind(), then snd_hda_jack_tbl_clear() and snd_device_free(). __snd_device_disconnect() skips BUILD components. snd_jack_dev_free() frees the attachment before its late disconnect, leaving the debugfs files published. 3. After cleanup, a reader opens the surviving type file and jack_type_read() dereferences the freed attachment's jack pointer. The card's debugfs root remains live throughout this sequence. The result is a use-after-free even without an overlapping read. An already admitted callback is also not drained by this cleanup. Call snd_jack_remove_debugfs() before unlinking any controls in the free callback. The existing debugfs_remove() rundown blocks new accesses and waits for active callbacks while the attachment, control and jack remain valid. Roots cleared by an earlier disconnect are NULL, so repeating the removal is harmless. KASAN report as below: ================================================================== BUG: KASAN: slab-use-after-free in jack_type_read+0x1da/0x1f0 Read of size 8 at addr ffff8881085f9da0 by task python3/429 CPU: 2 UID: 0 PID: 429 Comm: python3 Not tainted 7.2.0-rc6-pmb-alsa-functional-v2+ #1 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 ? jack_type_read+0x1da/0x1f0 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x20d/0x410 ? jack_type_read+0x1da/0x1f0 kasan_report+0xe0/0x110 ? jack_type_read+0x1da/0x1f0 jack_type_read+0x1da/0x1f0 ? __pfx_jack_type_read+0x10/0x10 ? __pfx___debugfs_file_get+0x10/0x10 ? __pfx_cp_new_stat+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? srso_alias_return_thunk+0x5/0xfbef5 ? selinux_file_permission+0x122/0x570 full_proxy_read+0x11f/0x180 ? __pfx_full_proxy_read+0x10/0x10 vfs_read+0x1a0/0xc20 ? __pfx_vfs_read+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __do_sys_newfstat+0xa6/0x100 ? find_held_lock+0x2b/0x80 ? __pfx___do_sys_newfstat+0x10/0x10 ksys_read+0x111/0x200 ? __pfx_ksys_read+0x10/0x10 ? full_proxy_llseek+0x11e/0x170 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f7e392e7687 Code: 48 89 fa 4c 89 df e8 58 b3 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff RSP: 002b:00007fff5f7b9180 EFLAGS: 00000202 ORIG_RAX: 0000000000000000 RAX: ffffffffffffffda RBX: 00007f7e39253780 RCX: 00007f7e392e7687 RDX: 0000000000002000 RSI: 000000003d2e9b00 RDI: 0000000000000003 RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000202 R12: 00007f7e39253700 R13: 0000000000002000 R14: 00007f7e38d7e2c0 R15: 0000000000a55b98 Allocated by task 425: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_cache_noprof+0x251/0x610 snd_jack_kctl_new+0x80/0x180 snd_jack_new+0x4b/0x490 snd_hda_jack_add_kctl_mst+0x19b/0x610 add_jack_kctl+0x204/0x320 snd_hda_jack_add_kctls+0x212/0x910 snd_hda_gen_build_controls+0x233/0x600 snd_hda_codec_build_controls+0x2e4/0x9f0 hda_codec_driver_probe+0x26e/0x590 really_probe+0x21f/0xa90 __driver_probe_device+0x1ec/0x490 device_driver_attach+0xdc/0x210 bind_store+0xec/0x180 drv_attr_store+0x79/0xb0 sysfs_kf_write+0xed/0x140 kernfs_fop_write_iter+0x34b/0x500 vfs_write+0x637/0x1010 ksys_write+0x111/0x200 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 425: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x307/0x580 snd_jack_kctl_private_free+0x153/0x200 __snd_ctl_remove+0x29f/0x3b0 snd_ctl_remove+0x46/0x70 snd_jack_dev_free+0x1b1/0x370 __snd_device_free+0x193/0x3f0 snd_device_free+0xcf/0x190 snd_hda_jack_tbl_clear+0x159/0x2e0 snd_hda_codec_cleanup_for_unbind+0x37e/0x9c0 hda_codec_driver_probe+0x3ae/0x590 really_probe+0x21f/0xa90 __driver_probe_device+0x1ec/0x490 device_driver_attach+0xdc/0x210 bind_store+0xec/0x180 drv_attr_store+0x79/0xb0 sysfs_kf_write+0xed/0x140 kernfs_fop_write_iter+0x34b/0x500 vfs_write+0x637/0x1010 ksys_write+0x111/0x200 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff8881085f9d80 which belongs to the cache kmalloc-64 of size 64 The buggy address is located 32 bytes inside of freed 64-byte region [ffff8881085f9d80, ffff8881085f9dc0) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x1085f9 flags: 0x200000000000000(node=0|zone=2) page_type: f5(slab) raw: 0200000000000000 ffff8881000428c0 dead000000000100 dead000000000122 raw: 0000000000000000 0000000000200020 00000000f5000000 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff8881085f9c80: 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc ffff8881085f9d00: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc >ffff8881085f9d80: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ^ ffff8881085f9e00: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ffff8881085f9e80: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ================================================================== Fixes: 495000a38634 ("ALSA: core: Remove debugfs at disconnection") Assisted-by: LLM Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-e1e7490bbe7ba1b3c44a-v2-3e3b6315a18c02ced60d@gmail.com Signed-off-by: Takashi Iwai --- sound/core/jack.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/core/jack.c b/sound/core/jack.c index 0f04018181ac..5cc9c42de981 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -68,6 +68,9 @@ static int snd_jack_dev_free(struct snd_device *device) struct snd_card *card = device->card; struct snd_jack_kctl *jack_kctl, *tmp_jack_kctl; + /* Remove debugfs while the controls are still linked. */ + snd_jack_remove_debugfs(jack); + list_for_each_entry_safe(jack_kctl, tmp_jack_kctl, &jack->kctl_list, list) { list_del_init(&jack_kctl->list); snd_ctl_remove(card, jack_kctl->kctl); From fbaccb1b19c79e79220e3bcc5bb18ca54ffb017b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Ahmet=20Memi=C5=9F?= Date: Mon, 12 Oct 2026 19:28:16 +0300 Subject: [PATCH 669/924] ALSA: hda: intel: Don't free card in the probe work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the delayed probe fails, azx_probe_work() frees the card and clears the drvdata. azx_remove() reads the card before it waits for the probe work with cancel_delayed_work_sync() so when the device is removed while the probe work is running, it frees the card again after the work has already freed it. Only disconnect the card in the work so that its slot is released right away and leave freeing it to azx_remove(). Fixes: 39173303c838 ("ALSA: hda: Free card instance properly at probe errors") Reported-by: syzbot+40b06a86af225236fdb4@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=40b06a86af225236fdb4 Signed-off-by: Ali Ahmet MemiÅŸ Link: https://patch.msgid.link/20261012162841.1879278-1-aliamemis@disroot.org Signed-off-by: Takashi Iwai --- sound/hda/controllers/intel.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 2b5b5d555f2c..6b80b59b8743 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -1737,7 +1737,12 @@ static void azx_check_snoop_available(struct azx *chip) static void azx_probe_work(struct work_struct *work) { struct hda_intel *hda = container_of(work, struct hda_intel, probe_work.work); - azx_probe_continue(&hda->chip, true); + + if (azx_probe_continue(&hda->chip, false) < 0) { + hda->init_failed = 1; + complete_all(&hda->probe_wait); + snd_card_disconnect(hda->chip.card); + } } static int default_bdl_pos_adj(struct azx *chip) From aa118c8f61d59387961d58ffae831c5259a9f222 Mon Sep 17 00:00:00 2001 From: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Date: Tue, 13 Oct 2026 14:35:54 +0800 Subject: [PATCH 670/924] ALSA: line6: Clear prev_fbuf on capture buffer release A KASAN slab-use-after-free read was reported in submit_audio_out_urb() when generating an impulse test signal: BUG: KASAN: slab-use-after-free in create_impulse_test_signal sound/usb/line6/playback.c:76 [inline] BUG: KASAN: slab-use-after-free in submit_audio_out_urb+0x1848/0x1b20 sound/usb/line6/playback.c:249 Read of size 2 at addr ffff888008d74870 by task syz.2.3214/7785 Allocated by task 11: kmem_cache_alloc_noprof+0x2f0/0x390 mm/slub.c:4144 line6_buffer_acquire+0x1a1/0x220 sound/usb/line6/pcm.c:158 line6_pcm_acquire+0xb6/0x230 sound/usb/line6/pcm.c:329 line6_startup_work+0x3c/0x50 sound/usb/line6/driver.c:728 Freed by task 7785: kfree+0xf8/0x390 mm/slub.c:6566 line6_buffer_release sound/usb/line6/pcm.c:176 [inline] line6_buffer_release+0xa5/0xf0 sound/usb/line6/pcm.c:170 __line6_pcm_release sound/usb/line6/pcm.c:305 [inline] line6_pcm_release+0x5a/0x80 sound/usb/line6/pcm.c:313 snd_toneport_monitor_put+0x11b/0x190 sound/usb/line6/toneport.c:183 line6pcm->prev_fbuf points into the capture PCM stream buffer (line6pcm->in.buffer), which is updated during audio_in_callback() to feed rate synchronization, software monitoring, and impulse test signal generation on playback side. When all capture streams are closed, line6_buffer_release() frees pstr->buffer with kfree(). However, it does not clear line6pcm->prev_fbuf. If playback is subsequently started (e.g. via impulse test signal volume control snd_line6_impulse_volume_put()), submit_audio_out_urb() dereferences the stale pointer line6pcm->prev_fbuf under in.lock, resulting in a slab-use-after-free read. Clear prev_fbuf and prev_fsize in line6_buffer_release() when releasing the capture stream buffer under in.lock, ensuring that no stale buffer pointer can be accessed by submit_audio_out_urb(). Fixes: 63e20df1e5b2 ("ALSA: line6: Reorganize PCM stream handling") Cc: stable@vger.kernel.org Co-developed-by: Bin Yu Signed-off-by: Bin Yu Co-developed-by: Cheng Wen Signed-off-by: Cheng Wen Signed-off-by: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Link: https://patch.msgid.link/20261013063554.571-1-25031212351@stu.xidian.edu.cn Signed-off-by: Takashi Iwai --- sound/usb/line6/pcm.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 998869dc4613..f30d82cc0fc1 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -173,6 +173,14 @@ static void line6_buffer_release(struct snd_line6_pcm *line6pcm, clear_bit(type, &pstr->opened); if (!pstr->opened) { line6_wait_clear_audio_urbs(line6pcm, pstr); + if (pstr == &line6pcm->in) { + /* prev_fbuf points into the capture buffer we are about + * to free; drop it while no capture URB is in flight. + */ + guard(spinlock_irqsave)(&line6pcm->in.lock); + line6pcm->prev_fbuf = NULL; + line6pcm->prev_fsize = 0; + } kfree(pstr->buffer); pstr->buffer = NULL; } From a83a0a662313f85411f6c01d5d117df924817a0c Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Tue, 13 Oct 2026 15:12:55 +0800 Subject: [PATCH 671/924] ALSA: hda/realtek: Add GPIO2 low quirk for ASUS E5402WHA The internal speaker amplifier on the ASUS ExpertCenter AIO E5402WHA (ALC256, SSID 1043:3500) is enabled via GPIO2 and is active-low. Driving GPIO2 high, as ALC256_FIXUP_ASUS_AIO_GPIO2 does on the ZN270IE, keeps the speakers muted. Add a dedicated fixup that drives GPIO2 low so the internal speakers work. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221938 Tested-by: Johan den Hollander Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20261013071255.270131-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 9eeb3a66b2b9..92686e71dad1 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -2809,6 +2809,19 @@ static void alc275_fixup_gpio4_off(struct hda_codec *codec, } } +static void alc256_fixup_asus_aio_gpio2_low(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gpio_mask |= 0x04; + spec->gpio_dir |= 0x04; + spec->gpio_data &= ~0x04; + } +} + /* Quirk for Thinkpad X1 7th and 8th Gen * The following fixed routing needed * DAC1 (NID 0x02) -> Speaker (NID 0x14); some eq applied secretly @@ -4250,6 +4263,7 @@ enum { ALC256_FIXUP_ASUS_HEADSET_MODE, ALC256_FIXUP_ASUS_MIC, ALC256_FIXUP_ASUS_AIO_GPIO2, + ALC256_FIXUP_ASUS_AIO_GPIO2_LOW, ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE, ALC233_FIXUP_LENOVO_MULTI_CODECS, @@ -5584,6 +5598,10 @@ static const struct hda_fixup alc269_fixups[] = { /* Set up GPIO2 for the speaker amp */ .v.func = alc_fixup_gpio4, }, + [ALC256_FIXUP_ASUS_AIO_GPIO2_LOW] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_asus_aio_gpio2_low, + }, [ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -8124,6 +8142,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x31e1, "ASUS B5605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x31f1, "ASUS B3605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x3391, "ASUS PM3606CKA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x3500, "ASUS E5402WHA", ALC256_FIXUP_ASUS_AIO_GPIO2_LOW), SND_PCI_QUIRK(0x1043, 0x3501, "ASUS PM3606CHA", ALC256_FIXUP_ASUS_SPEAKER_COEFS), SND_PCI_QUIRK(0x1043, 0x3601, "ASUS PM5406CGA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x3611, "ASUS PM5606CGA", ALC287_FIXUP_CS35L41_I2C_2), From 8c3529f0a8faaac7d73333486f69da1617ecc579 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Tue, 13 Oct 2026 15:54:35 +0800 Subject: [PATCH 672/924] ALSA: rawmidi: Drain input work before freeing the runtime The input runtime embeds event_work, so the work must be drained before close_substream() frees the runtime. The cancellation in snd_rawmidi_input_trigger() runs before close clears substream->opened, leaving a window for snd_rawmidi_receive() to enqueue more work. With a sequencer subscription to a USB MIDI input port, an in-flight completion can overlap the last subscription's midisynth_unsubscribe(): 1. snd_usbmidi_input_data() observes input_triggered and is delayed before calling snd_rawmidi_receive(). 2. Unsubscribe enters close_substream(), clears input_triggered through the provider trigger callback and returns from cancel_work_sync(). The substream is still opened. 3. The completion resumes. snd_rawmidi_receive() takes substream->lock, sees opened set and queues runtime->event_work before dropping the lock. 4. Close clears opened under that lock, closes the provider and frees the runtime. Stopping USB URBs does not drain the raw-MIDI work. 5. The queued work runs and snd_rawmidi_input_event_work() reads event or substream from the freed runtime, causing a use-after-free. Cancel input event_work again after clearing opened, before closing the provider or releasing the runtime's private data. Receive holds substream->lock through its opened check and work enqueue, so once close has cleared opened under that lock, no further receive can queue work. The final cancellation therefore drains any late work before the runtime is freed. Keep it outside the spinlock so a running callback can finish. Fixes: b3c705aa9e91 ("ALSA: rawmidi - Use workq for event handling") Assisted-by: LLM Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-98489078ab56528d8e4a-v2-9408ce8c8f3c70b5c10d@gmail.com Signed-off-by: Takashi Iwai --- sound/core/rawmidi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index c43cedb5c121..630d9fc8e9da 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -607,6 +607,9 @@ static void close_substream(struct snd_rawmidi *rmidi, substream->opened = 0; substream->append = 0; } + /* Drain input work queued after the earlier cancellation. */ + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) + cancel_work_sync(&substream->runtime->event_work); substream->ops->close(substream); if (substream->runtime->private_free) substream->runtime->private_free(substream); From 50f413b83b31ea8d8ee4276165ab95ed5ab0e10f Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Tue, 13 Oct 2026 16:01:06 +0800 Subject: [PATCH 673/924] ALSA: pcm: Protect delay DMA sync against concurrent HW_FREE A PCM DMA buffer must remain allocated until explicit DMA synchronization finishes. snd_pcm_delay() calls snd_pcm_dma_buffer_sync() after dropping the stream lock, without participating in the buffer-access exclusion used by HW_FREE. With a managed NONCONTIG buffer requiring explicit sync, HW_PARAMS can allocate a separate runtime descriptor when the requested ring exceeds the preallocation. In PREPARED state with mmap_count zero, concurrent HWSYNC and HW_FREE calls on the same open PCM file can interleave: 1. HWSYNC checks the state in snd_pcm_delay() under the stream lock and releases that lock. 2. The sync helper fetches runtime->dma_buffer_p and enters snd_dma_noncontig_sync() with that descriptor. 3. HW_FREE takes the negative buffer-access count, checks PREPARED under the stream lock, releases the lock, and frees the DMA allocation and dynamic descriptor through snd_pcm_lib_free_pages(). 4. The sync callback resumes and reads the freed descriptor. The stream lock does not cover the allocation's release, and the sync operation has no positive buffer-access count to prevent it. The stale read causes a use-after-free in snd_dma_noncontig_sync(). DELAY uses the same snd_pcm_delay() path. Take a positive buffer-access count before checking the state and keep it until the DMA sync finishes, as the transfer and mmap paths already do. HW_FREE then returns -EBUSY while this access is active. If a buffer writer has already entered, snd_pcm_delay() returns -EBUSY before using the buffer. Keep the sync outside the stream lock and balance the count on the state-error path as well. KASAN report as below: BUG: KASAN: slab-use-after-free in snd_dma_noncontig_sync+0x295/0x310 [snd_pcm] Read of size 4 at addr ffff888108f58584 by task pcm-uaf-fixture/487 CPU: 1 UID: 0 PID: 487 Comm: pcm-uaf-fixture Not tainted 7.2.0-rc6-pmb-alsa-functional-v2+ #1 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 ? snd_dma_noncontig_sync+0x295/0x310 [snd_pcm] ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x20d/0x410 ? snd_dma_noncontig_sync+0x295/0x310 [snd_pcm] kasan_report+0xe0/0x110 ? snd_dma_noncontig_sync+0x295/0x310 [snd_pcm] ? __pfx_snd_dma_noncontig_sync+0x10/0x10 [snd_pcm] snd_dma_noncontig_sync+0x295/0x310 [snd_pcm] ? __pfx_snd_dma_noncontig_sync+0x10/0x10 [snd_pcm] snd_dma_buffer_sync+0xc9/0x110 [snd_pcm] snd_pcm_delay+0x3bd/0x550 [snd_pcm] snd_pcm_common_ioctl+0x1e7/0x3aa0 [snd_pcm] ? srso_alias_return_thunk+0x5/0xfbef5 ? do_vfs_ioctl+0x1f5/0x1280 ? __pfx_do_vfs_ioctl+0x10/0x10 ? ioctl_has_perm.constprop.0.isra.0+0x34f/0x500 ? __pfx_snd_pcm_common_ioctl+0x10/0x10 [snd_pcm] ? srso_alias_return_thunk+0x5/0xfbef5 snd_pcm_ioctl+0x76/0xb0 [snd_pcm] ? __pfx_snd_pcm_ioctl+0x10/0x10 [snd_pcm] __x64_sys_ioctl+0x163/0x1d0 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f1cae7ab91b Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00 48 89 44 24 08 48 8d 44 24 20 48 89 44 24 10 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1c 48 8b 44 24 18 64 48 2b 04 25 28 00 00 RSP: 002b:00007ffe12d544f0 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 0000000000000004 RCX: 00007f1cae7ab91b RDX: 0000000000000000 RSI: 0000000000004122 RDI: 0000000000000003 RBP: 00000000000001ee R08: 0000000000000073 R09: 00000000ffffffff R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000003 R13: 0000000000000001 R14: 00005584f32b1398 R15: 00005584f32b2ce8 Allocated by task 487: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_cache_noprof+0x251/0x610 snd_pcm_lib_malloc_pages+0x27c/0x9f0 [snd_pcm] snd_pcm_hw_params+0x171c/0x1d10 [snd_pcm] snd_pcm_common_ioctl+0x1f62/0x3aa0 [snd_pcm] snd_pcm_ioctl+0x76/0xb0 [snd_pcm] __x64_sys_ioctl+0x163/0x1d0 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 499: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x307/0x580 snd_pcm_lib_free_pages+0x1c9/0x420 [snd_pcm] snd_pcm_common_ioctl+0x2bd3/0x3aa0 [snd_pcm] snd_pcm_ioctl+0x76/0xb0 [snd_pcm] __x64_sys_ioctl+0x163/0x1d0 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff888108f58580 which belongs to the cache kmalloc-64 of size 64 The buggy address is located 4 bytes inside of freed 64-byte region [ffff888108f58580, ffff888108f585c0) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x108f58 flags: 0x200000000000000(node=0|zone=2) page_type: f5(slab) raw: 0200000000000000 ffff8881000428c0 dead000000000100 dead000000000122 raw: 0000000000000000 0000000000200020 00000000f5000000 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff888108f58480: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ffff888108f58500: 00 00 00 00 00 06 fc fc fc fc fc fc fc fc fc fc >ffff888108f58580: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ^ ffff888108f58600: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ffff888108f58680: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ================================================================== Disabling lock debugging due to kernel taint ------------[ cut here ]------------ kernel BUG at kernel/dma/mapping.c:417! Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI CPU: 1 UID: 0 PID: 487 Comm: pcm-uaf-fixture Tainted: G B 7.2.0-rc6-pmb-alsa-functional-v2+ #1 PREEMPT(lazy) Tainted: [B]=BAD_PAGE Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 RIP: 0010:__dma_sync_sg_for_cpu+0x230/0x360 Code: df 48 c1 e8 03 80 3c 10 00 0f 85 fa 00 00 00 4c 8b 35 d4 6b 7a 09 e8 7f e2 17 00 83 fd 02 0f 86 39 fe ff ff e8 71 e2 17 00 90 <0f> 0b e8 69 e2 17 00 be 08 00 00 00 4c 89 ff e8 bc 5f 77 00 4c 89 RSP: 0018:ffff888111f3fc50 EFLAGS: 00010293 RAX: 0000000000000000 RBX: ffffffffc0250d10 RCX: ffffffff816d395f RDX: ffff88810836ba00 RSI: ffff888108f58580 RDI: ffffffffc02510d8 RBP: 00000000ffff8881 R08: 0000000000000000 R09: fffffbfff804a239 R10: ffffffffc02511cf R11: 6e696c6261736944 R12: ffff888108f58580 R13: 0000000002ce00e5 R14: ffffffffc0258720 R15: 0000000000000000 FS: 00007f1cae69c780(0000) GS:ffff8881fd861000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 000055d1affa2480 CR3: 0000000108fc0003 CR4: 0000000000770ef0 PKRU: 55555554 Call Trace: snd_dma_noncontig_sync+0x15a/0x310 [snd_pcm] ? __pfx_snd_dma_noncontig_sync+0x10/0x10 [snd_pcm] snd_dma_buffer_sync+0xc9/0x110 [snd_pcm] snd_pcm_delay+0x3bd/0x550 [snd_pcm] snd_pcm_common_ioctl+0x1e7/0x3aa0 [snd_pcm] ? srso_alias_return_thunk+0x5/0xfbef5 ? do_vfs_ioctl+0x1f5/0x1280 ? __pfx_do_vfs_ioctl+0x10/0x10 ? ioctl_has_perm.constprop.0.isra.0+0x34f/0x500 ? __pfx_snd_pcm_common_ioctl+0x10/0x10 [snd_pcm] ? srso_alias_return_thunk+0x5/0xfbef5 snd_pcm_ioctl+0x76/0xb0 [snd_pcm] ? __pfx_snd_pcm_ioctl+0x10/0x10 [snd_pcm] __x64_sys_ioctl+0x163/0x1d0 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f1cae7ab91b Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00 48 89 44 24 08 48 8d 44 24 20 48 89 44 24 10 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1c 48 8b 44 24 18 64 48 2b 04 25 28 00 00 RSP: 002b:00007ffe12d544f0 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 0000000000000004 RCX: 00007f1cae7ab91b RDX: 0000000000000000 RSI: 0000000000004122 RDI: 0000000000000003 RBP: 00000000000001ee R08: 0000000000000073 R09: 00000000ffffffff R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000003 R13: 0000000000000001 R14: 00005584f32b1398 R15: 00005584f32b2ce8 Modules linked in: snd_pcmtest snd_pcm ---[ end trace 0000000000000000 ]--- RIP: 0010:__dma_sync_sg_for_cpu+0x230/0x360 Code: df 48 c1 e8 03 80 3c 10 00 0f 85 fa 00 00 00 4c 8b 35 d4 6b 7a 09 e8 7f e2 17 00 83 fd 02 0f 86 39 fe ff ff e8 71 e2 17 00 90 <0f> 0b e8 69 e2 17 00 be 08 00 00 00 4c 89 ff e8 bc 5f 77 00 4c 89 RSP: 0018:ffff888111f3fc50 EFLAGS: 00010293 RAX: 0000000000000000 RBX: ffffffffc0250d10 RCX: ffffffff816d395f RDX: ffff88810836ba00 RSI: ffff888108f58580 RDI: ffffffffc02510d8 RBP: 00000000ffff8881 R08: 0000000000000000 R09: fffffbfff804a239 R10: ffffffffc02511cf R11: 6e696c6261736944 R12: ffff888108f58580 R13: 0000000002ce00e5 R14: ffffffffc0258720 R15: 0000000000000000 FS: 00007f1cae69c780(0000) GS:ffff8881fd861000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 000055d1affa2480 CR3: 0000000108fc0003 CR4: 0000000000770ef0 PKRU: 55555554 Kernel panic - not syncing: Fatal exception Kernel Offset: disabled Rebooting in 1 seconds.. Fixes: a25684a95646 ("ALSA: memalloc: Support for non-contiguous page allocation") Assisted-by: LLM Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-d74585d187e0b1810c08-v2-25432d0951f67fdaa1a9@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index defbb2977efe..f774358629bb 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -3116,14 +3116,19 @@ static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, static int snd_pcm_delay(struct snd_pcm_substream *substream, snd_pcm_sframes_t *delay) { + struct snd_pcm_runtime *runtime = substream->runtime; int err; + /* Keep the DMA buffer alive through the sync below. */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; scoped_guard(pcm_stream_lock_irq, substream) { err = do_pcm_hwsync(substream); if (delay && !err) *delay = snd_pcm_calc_delay(substream); } snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU); + atomic_dec(&runtime->buffer_accessing); return err; } From ad6be54d13496a0b5c6d1edeea99504e438cdb7a Mon Sep 17 00:00:00 2001 From: Mingxuan Guo Date: Tue, 13 Oct 2026 18:23:58 +0800 Subject: [PATCH 674/924] firmware: cs_dsp: clear dsp->running on the cs_dsp_run() error path cs_dsp_run() sets dsp->running = true once the core has been started, but the error path below it only stops and disables the core, leaving the flag set. The first thing that can fail after that point is the post_run client hook, and it is reachable in mainline: wm_adsp registers wm_adsp_event_post_run(), which can fail in wm_adsp_buffer_init(). The core is then stopped and disabled, but dsp->running still reads true, and that stale state is acted on elsewhere: cs_dsp_coeff_write_ctrl() and cs_dsp_coeff_read_ctrl() talk to the DSP when ctl->dsp->running is set, and client drivers use the flag to decide whether the firmware is up (cs35l41 checks cs_dsp.running before its early events and on hibernate/resume). The flag cannot simply be moved below the hook: post_run hooks such as wm_adsp_event_post_run() read controls while they run, and cs_dsp_coeff_read_ctrl() only goes to the DSP when the flag is set. Clear the flag on the error path, before the core is stopped - the order cs_dsp_stop() already uses. Fixes: f6bc909e7673 ("firmware: cs_dsp: add driver to support firmware loading on Cirrus Logic DSPs") Signed-off-by: Mingxuan Guo Reviewed-by: Richard Fitzgerald Link: https://patch.msgid.link/tencent_B839C5A92D762B5717B9637A5CFA0841C608@qq.com Signed-off-by: Mark Brown --- drivers/firmware/cirrus/cs_dsp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index 04015280ac5d..844c68036975 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -2960,6 +2960,8 @@ int cs_dsp_run(struct cs_dsp *dsp) return 0; err: + dsp->running = false; + if (dsp->ops->stop_core) dsp->ops->stop_core(dsp); if (dsp->ops->disable_core) From 80b3f283e9616821ba2f121ed5e4b2e6af93d771 Mon Sep 17 00:00:00 2001 From: Mingxuan Guo Date: Tue, 13 Oct 2026 18:23:59 +0800 Subject: [PATCH 675/924] firmware: cs_dsp: add a KUnit test for the cs_dsp_run() error path Cover the case where the post_run client hook fails: cs_dsp_run() must return the error and the DSP must not be left marked as running, and a subsequent run must be able to start it again. The test fails without the previous patch and passes with it. Full suite run (UML, KUnit) on top of that patch: before: Ran 5829 tests: passed: 5825, failed: 4 (cs_dsp_test_run_error_clears_running in each of the four cs_dsp_callbacks_* suites) after: Ran 5829 tests: passed: 5829 Signed-off-by: Mingxuan Guo Reviewed-by: Richard Fitzgerald Link: https://patch.msgid.link/tencent_113ABD187DD2A0C99CB7D4003B81AB94DC07@qq.com Signed-off-by: Mark Brown --- .../cirrus/test/cs_dsp_test_callbacks.c | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c b/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c index e5a389808e5f..b6e141ec409f 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c @@ -36,6 +36,8 @@ struct cs_dsp_test_local { int num_post_stop; int num_watchdog_expired; + bool fail_post_run; + struct cs_dsp_coeff_ctl *passed_ctl[16]; struct cs_dsp *passed_dsp; }; @@ -106,6 +108,9 @@ static int cs_dsp_test_post_run_callback(struct cs_dsp *dsp) local->passed_dsp = dsp; local->num_post_run++; + if (local->fail_post_run) + return -EIO; + return 0; } @@ -198,6 +203,33 @@ static void cs_dsp_test_run_stop_callbacks(struct kunit *test) local->passed_dsp = NULL; } +static void cs_dsp_test_run_error_clears_running(struct kunit *test) +{ + struct cs_dsp_test *priv = test->priv; + struct cs_dsp_test_local *local = priv->local; + struct firmware *wmfw; + + wmfw = cs_dsp_mock_wmfw_get_firmware(local->wmfw_builder); + + KUNIT_EXPECT_EQ(test, + cs_dsp_power_up(priv->dsp, wmfw, "wmfw", NULL, NULL, "misc"), + 0); + + /* + * By the time the post_run hook runs, the core has been started. A + * failure there stops and disables the core again, so the DSP must not + * be left looking like it is running. + */ + local->fail_post_run = true; + KUNIT_EXPECT_EQ(test, cs_dsp_run(priv->dsp), -EIO); + KUNIT_EXPECT_FALSE(test, priv->dsp->running); + + /* and the next run must be able to start it again */ + local->fail_post_run = false; + KUNIT_EXPECT_EQ(test, cs_dsp_run(priv->dsp), 0); + KUNIT_EXPECT_TRUE(test, priv->dsp->running); +} + static void cs_dsp_test_ctl_v1_callbacks(struct kunit *test) { struct cs_dsp_test *priv = test->priv; @@ -609,6 +641,7 @@ KUNIT_ARRAY_PARAM(cs_dsp_no_callbacks, static struct kunit_case cs_dsp_callbacks_adsp2_wmfwv1_test_cases[] = { KUNIT_CASE_PARAM(cs_dsp_test_run_stop_callbacks, cs_dsp_callbacks_ops_gen_params), + KUNIT_CASE_PARAM(cs_dsp_test_run_error_clears_running, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_ctl_v1_callbacks, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_no_callbacks, cs_dsp_no_callbacks_gen_params), @@ -617,6 +650,7 @@ static struct kunit_case cs_dsp_callbacks_adsp2_wmfwv1_test_cases[] = { static struct kunit_case cs_dsp_callbacks_adsp2_wmfwv2_test_cases[] = { KUNIT_CASE_PARAM(cs_dsp_test_run_stop_callbacks, cs_dsp_callbacks_ops_gen_params), + KUNIT_CASE_PARAM(cs_dsp_test_run_error_clears_running, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_ctl_v2_callbacks, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_no_callbacks, cs_dsp_no_callbacks_gen_params), @@ -625,6 +659,7 @@ static struct kunit_case cs_dsp_callbacks_adsp2_wmfwv2_test_cases[] = { static struct kunit_case cs_dsp_callbacks_halo_test_cases[] = { KUNIT_CASE_PARAM(cs_dsp_test_run_stop_callbacks, cs_dsp_callbacks_ops_gen_params), + KUNIT_CASE_PARAM(cs_dsp_test_run_error_clears_running, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_ctl_v2_callbacks, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_no_callbacks, cs_dsp_no_callbacks_gen_params), From fe9dcf5f65751445aa1137e81fb6e2c614c2e97e Mon Sep 17 00:00:00 2001 From: Federico Amedeo Izzo Date: Sat, 10 Oct 2026 12:24:43 +0200 Subject: [PATCH 676/924] ASoC: codecs: aw88261: expose hardware mute control When the aw88261 speaker volume is set to 0, it becomes very quiet but still audible. This causes echo during phone calls on motorola-dubai, as the bottom speaker output is getting picked up by the bottom microphone. Expose the aw88261 hardware mute functionality as a Playback Switch control. The new control can be used in a UCM profile to disable the bottom speaker during phone calls and greatly reduce the echo. Signed-off-by: Federico Amedeo Izzo Link: https://patch.msgid.link/20261010-aw88261-hardware-mute-v1-1-d0befedd3b4c@izzo.pro Signed-off-by: Mark Brown --- sound/soc/codecs/aw88261.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c index 8671069f4c18..7303dc9eb4e0 100644 --- a/sound/soc/codecs/aw88261.c +++ b/sound/soc/codecs/aw88261.c @@ -1014,6 +1014,33 @@ static int aw88261_volume_set(struct snd_kcontrol *kcontrol, return 0; } +static int aw88261_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); + + ucontrol->value.integer.value[0] = !aw88261->mute_st; + return 0; +} + +static int aw88261_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); + bool mute = !ucontrol->value.integer.value[0]; + + if (aw88261->mute_st == mute) + return 0; + + aw88261->mute_st = mute; + if (aw88261->aw_pa->status == AW88261_DEV_PW_ON) + aw88261_dev_mute(aw88261->aw_pa, mute); + + return 1; +} + /* * The field contains 4 bits in units of 6dB + 6 bits in units of 0.125dB * which is too precise for TLV (!) so we have to multiply the scale by 2. @@ -1026,6 +1053,8 @@ static const struct snd_kcontrol_new aw88261_controls[] = { SOC_SINGLE_EXT_TLV("PCM Playback Volume", AW88261_SYSCTRL2_REG, 6, AW88261_CTL_MAX_VOL, 1, aw88261_volume_get, aw88261_volume_set, volume_tlv), + SOC_SINGLE_EXT("Playback Switch", 0, 0, 1, 0, + aw88261_mute_get, aw88261_mute_put), AW88261_PROFILE_EXT("Profile Set", aw88261_profile_info, aw88261_profile_get, aw88261_profile_set), }; From 2c3400aa9f397da48a6760f5e84438973e01a00c Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:08 +0700 Subject: [PATCH 677/924] ASoC: amd: acp: Fix .close() being skipped on rollback snd_pcm_hw_constraint_integer() may return 1, which is then returned from acp_dma_open(). ASoC only marks the component as opened when .open() returns 0, so .close() can be skipped on rollback. Return 0 explicitly on success. Fixes: 623621a9f9e1 ("ASoC: amd: Add common framework to support I2S on ACP SOC") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp-platform.c b/sound/soc/amd/acp/acp-platform.c index 6b1e18b31c1c..ba6dfe8fb6b7 100644 --- a/sound/soc/amd/acp/acp-platform.c +++ b/sound/soc/amd/acp/acp-platform.c @@ -248,7 +248,7 @@ static int acp_dma_open(struct snd_soc_component *component, struct snd_pcm_subs list_add_tail(&stream->list, &chip->stream_list); spin_unlock_irq(&chip->acp_lock); - return ret; + return 0; } static int acp_dma_hw_params(struct snd_soc_component *component, From 172888114dda96492d40e4a6e0ed16146cb76e2b Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:09 +0700 Subject: [PATCH 678/924] ASoC: amd: ps: Fix acp63_pdm_dma_close() being skipped on rollback snd_pcm_hw_constraint_integer() may return 1, which is then returned from acp63_pdm_dma_open(). ASoC only marks the component as opened when .open() returns 0, so .close() can be skipped on rollback. Return 0 explicitly on success. Fixes: 5137305662ef ("ASoC: amd: add acp6.2 pdm driver dma ops") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/ps/ps-pdm-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/ps/ps-pdm-dma.c b/sound/soc/amd/ps/ps-pdm-dma.c index 04c014349347..28e0686e9373 100644 --- a/sound/soc/amd/ps/ps-pdm-dma.c +++ b/sound/soc/amd/ps/ps-pdm-dma.c @@ -211,7 +211,7 @@ static int acp63_pdm_dma_open(struct snd_soc_component *component, pdm_data->acp63_base = adata->acp63_base; runtime->private_data = pdm_data; - return ret; + return 0; } static int acp63_pdm_dma_hw_params(struct snd_soc_component *component, From 45dd2b22ea8934ed69dd05e6ecbf3c66ac3baebf Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:10 +0700 Subject: [PATCH 679/924] ASoC: amd: ps: Fix acp63_sdw_dma_close() being skipped on rollback snd_pcm_hw_constraint_integer() may return 1, which is then returned from acp63_sdw_dma_open(). ASoC only marks the component as opened when .open() returns 0, so .close() can be skipped on rollback. Return 0 explicitly on success. Fixes: f722917350ee ("ASoC: amd: ps: add SoundWire dma driver dma ops") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/ps/ps-sdw-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c index f27ebbd21379..553fe11a2e50 100644 --- a/sound/soc/amd/ps/ps-sdw-dma.c +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -337,7 +337,7 @@ static int acp63_sdw_dma_open(struct snd_soc_component *component, stream->stream_id = cpu_dai->id; stream->instance = amd_manager->instance; runtime->private_data = stream; - return ret; + return 0; } static int acp63_sdw_dma_hw_params(struct snd_soc_component *component, From 37ceef26df57c4a86a5e984b1951995aa96d4342 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:11 +0700 Subject: [PATCH 680/924] ASoC: amd: raven: Fix acp3x_dma_close() being skipped on rollback snd_pcm_hw_constraint_integer() may return 1, which is then returned from acp3x_dma_open(). ASoC only marks the component as opened when .open() returns 0, so .close() can be skipped on rollback. Return 0 explicitly on success. Fixes: 703a6e22888b ("ASoC: amd: Enabling I2S instance in DMA and DAI") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/raven/acp3x-pcm-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/raven/acp3x-pcm-dma.c b/sound/soc/amd/raven/acp3x-pcm-dma.c index 37ea5c572eb9..072f3dc35d40 100644 --- a/sound/soc/amd/raven/acp3x-pcm-dma.c +++ b/sound/soc/amd/raven/acp3x-pcm-dma.c @@ -237,7 +237,7 @@ static int acp3x_dma_open(struct snd_soc_component *component, i2s_data->acp3x_base = adata->acp3x_base; runtime->private_data = i2s_data; - return ret; + return 0; } From a92571113ef297840270e4d8d4263a842302c9c4 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:12 +0700 Subject: [PATCH 681/924] ASoC: amd: renoir: Fix acp_pdm_dma_close() being skipped on rollback snd_pcm_hw_constraint_integer() may return 1, which is then returned from acp_pdm_dma_open(). ASoC only marks the component as opened when .open() returns 0, so .close() can be skipped on rollback. Return 0 explicitly on success. Fixes: 4a767b1d039a ("ASoC: amd: add acp3x pdm driver dma ops") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/renoir/acp3x-pdm-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/renoir/acp3x-pdm-dma.c b/sound/soc/amd/renoir/acp3x-pdm-dma.c index 3f59b753243d..c0481a9ba858 100644 --- a/sound/soc/amd/renoir/acp3x-pdm-dma.c +++ b/sound/soc/amd/renoir/acp3x-pdm-dma.c @@ -233,7 +233,7 @@ static int acp_pdm_dma_open(struct snd_soc_component *component, pdm_data->acp_base = adata->acp_base; runtime->private_data = pdm_data; - return ret; + return 0; } static int acp_pdm_dma_hw_params(struct snd_soc_component *component, From 38559fcd1c518a33055ef0a2cf7c04b57bfa9215 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:13 +0700 Subject: [PATCH 682/924] ASoC: amd: vangogh: Fix acp5x_dma_close() being skipped on rollback snd_pcm_hw_constraint_integer() may return 1, which is then returned from acp5x_dma_open(). ASoC only marks the component as opened when .open() returns 0, so .close() can be skipped on rollback. Return 0 explicitly on success. Fixes: cab396d8b22c ("ASoC: amd: add ACP5x pcm dma driver ops") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/vangogh/acp5x-pcm-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/vangogh/acp5x-pcm-dma.c b/sound/soc/amd/vangogh/acp5x-pcm-dma.c index 831e30e9b042..33d59d992ee0 100644 --- a/sound/soc/amd/vangogh/acp5x-pcm-dma.c +++ b/sound/soc/amd/vangogh/acp5x-pcm-dma.c @@ -231,7 +231,7 @@ static int acp5x_dma_open(struct snd_soc_component *component, } i2s_data->acp5x_base = adata->acp5x_base; runtime->private_data = i2s_data; - return ret; + return 0; } static int acp5x_dma_hw_params(struct snd_soc_component *component, From 06654a3ef5137719be880a6cd12bd453050eafc5 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:14 +0700 Subject: [PATCH 683/924] ASoC: amd: yc: Fix acp6x_pdm_dma_close() being skipped on rollback snd_pcm_hw_constraint_integer() may return 1, which is then returned from acp6x_pdm_dma_open(). ASoC only marks the component as opened when .open() returns 0, so .close() can be skipped on rollback. Return 0 explicitly on success. Fixes: ceb4fcc13ae5 ("ASoC: amd: add acp6x pdm driver dma ops") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-pdm-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/yc/acp6x-pdm-dma.c b/sound/soc/amd/yc/acp6x-pdm-dma.c index 40c4d833f4ed..afa9f66533e3 100644 --- a/sound/soc/amd/yc/acp6x-pdm-dma.c +++ b/sound/soc/amd/yc/acp6x-pdm-dma.c @@ -209,7 +209,7 @@ static int acp6x_pdm_dma_open(struct snd_soc_component *component, pdm_data->acp6x_base = adata->acp6x_base; runtime->private_data = pdm_data; - return ret; + return 0; } static int acp6x_pdm_dma_hw_params(struct snd_soc_component *component, From 4d23ef7515dd996237a68796cea52bc1cee0b63e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:15 +0700 Subject: [PATCH 684/924] ASoC: apple: mca: Ensure the DAI is marked as started on success snd_pcm_hw_constraint_minmax() may return 1, which is then returned from mca_fe_startup(). ASoC only marks the DAI as started when .startup() returns 0. This currently has no functional impact, as mca_fe_ops has no .shutdown() callback. Still, return 0 explicitly on success to match what ASoC expects and ensure the DAI is correctly marked as started. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-9-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/apple/mca.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index be702b2942a5..3585432ad49b 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -516,6 +516,7 @@ static int mca_fe_startup(struct snd_pcm_substream *substream, { struct mca_cluster *cl = mca_dai_to_cluster(dai); unsigned int mask, nchannels; + int ret; if (cl->tdm_slots) { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) @@ -528,9 +529,13 @@ static int mca_fe_startup(struct snd_pcm_substream *substream, nchannels = 2; } - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_CHANNELS, - 1, nchannels); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 1, nchannels); + if (ret < 0) + return ret; + + return 0; } static int mca_fe_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, From 50aaca643038774b6779c911c4f96bc7e0d607e9 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:16 +0700 Subject: [PATCH 685/924] ASoC: atmel: atmel-pcm-pdc: Fix atmel_pcm_close() being skipped on rollback snd_pcm_hw_constraint_integer() may return 1, which is then returned from atmel_pcm_open(). ASoC only marks the component as opened when .open() returns 0, so .close() can be skipped on rollback. Return 0 explicitly on success. Fixes: 92dfa6198623 ("ASoC: atmel-pcm: split into two file") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-10-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/atmel/atmel-pcm-pdc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/atmel/atmel-pcm-pdc.c b/sound/soc/atmel/atmel-pcm-pdc.c index 374f93fa6e0b..552bfa30d4ba 100644 --- a/sound/soc/atmel/atmel-pcm-pdc.c +++ b/sound/soc/atmel/atmel-pcm-pdc.c @@ -295,6 +295,8 @@ static int atmel_pcm_open(struct snd_soc_component *component, } runtime->private_data = prtd; + return 0; + out: return ret; } From c2a3d0b2be0cdd379471acc10aea6e3e8e54f4e9 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:17 +0700 Subject: [PATCH 686/924] ASoC: codecs: cs42l42: Ensure the DAI is marked as started on success snd_pcm_hw_constraint_minmax() may return 1, which is then returned from cs42l42_dai_startup(). ASoC only marks the DAI as started when .startup() returns 0. This currently has no functional impact, as cs42l42_ops has no .shutdown() callback. Still, return 0 explicitly on success to match what ASoC expects and ensure the DAI is correctly marked as started. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-11-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l42.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index b5d69d5516a6..f6e4bf6206b7 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -890,6 +890,7 @@ static int cs42l42_dai_startup(struct snd_pcm_substream *substream, struct snd_s { struct snd_soc_component *component = dai->component; struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + int ret; /* * Sample rates < 44.1 kHz would produce an out-of-range SCLK with @@ -900,9 +901,13 @@ static int cs42l42_dai_startup(struct snd_pcm_substream *substream, struct snd_s return 0; /* Machine driver has not set a SCLK, limit bottom end to 44.1 kHz */ - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 44100, 96000); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 44100, 96000); + if (ret < 0) + return ret; + + return 0; } static int cs42l42_pcm_hw_params(struct snd_pcm_substream *substream, From 0634e7b247278f6be00bbd37d1a02a3ec5c2ca30 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:18 +0700 Subject: [PATCH 687/924] ASoC: codecs: nau8325: Ensure the DAI is marked as started on success snd_pcm_hw_constraint_minmax() may return 1, which is then returned from nau8325_dai_startup(). ASoC only marks the DAI as started when .startup() returns 0. This currently has no functional impact, as nau8325_dai_ops has no .shutdown() callback. Still, return 0 explicitly on success to match what ASoC expects and ensure the DAI is correctly marked as started. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-12-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8325.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/nau8325.c b/sound/soc/codecs/nau8325.c index 2c95ed00a681..0902a7d7730d 100644 --- a/sound/soc/codecs/nau8325.c +++ b/sound/soc/codecs/nau8325.c @@ -489,14 +489,19 @@ static int nau8325_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8325 *nau8325 = snd_soc_component_get_drvdata(component); const struct nau8325_osr_attr *osr; + int ret; osr = nau8325_get_osr(nau8325); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_DA_AD_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_DA_AD_MAX / osr->osr); + if (ret < 0) + return ret; + + return 0; } static int nau8325_hw_params(struct snd_pcm_substream *substream, From 1de1fd7c69a3efb7549afb53b8508757877495ed Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:19 +0700 Subject: [PATCH 688/924] ASoC: codecs: nau8540: Ensure the DAI is marked as started on success snd_pcm_hw_constraint_minmax() may return 1, which is then returned from nau8540_dai_startup(). ASoC only marks the DAI as started when .startup() returns 0. This currently has no functional impact, as nau8540_dai_ops has no .shutdown() callback. Still, return 0 explicitly on success to match what ASoC expects and ensure the DAI is correctly marked as started. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-13-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8540.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/nau8540.c b/sound/soc/codecs/nau8540.c index d4211a76b096..9484388ab262 100644 --- a/sound/soc/codecs/nau8540.c +++ b/sound/soc/codecs/nau8540.c @@ -419,14 +419,19 @@ static int nau8540_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); const struct nau8540_osr_attr *osr; + int ret; osr = nau8540_get_osr(nau8540); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_ADC_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_ADC_MAX / osr->osr); + if (ret < 0) + return ret; + + return 0; } static int nau8540_hw_params(struct snd_pcm_substream *substream, From fc5da89a0a8a0a15258a0e8c2c3fc5395191a13d Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:20 +0700 Subject: [PATCH 689/924] ASoC: codecs: nau8821: Ensure the DAI is marked as started on success snd_pcm_hw_constraint_minmax() may return 1, which is then returned from nau8821_dai_startup(). ASoC only marks the DAI as started when .startup() returns 0. This currently has no functional impact, as nau8821_dai_ops has no .shutdown() callback. Still, return 0 explicitly on success to match what ASoC expects and ensure the DAI is correctly marked as started. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-14-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8821.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/nau8821.c b/sound/soc/codecs/nau8821.c index ecadbaee1037..935df063822c 100644 --- a/sound/soc/codecs/nau8821.c +++ b/sound/soc/codecs/nau8821.c @@ -829,14 +829,19 @@ static int nau8821_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); const struct nau8821_osr_attr *osr; + int ret; osr = nau8821_get_osr(nau8821, substream->stream); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_DA_AD_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_DA_AD_MAX / osr->osr); + if (ret < 0) + return ret; + + return 0; } static int nau8821_hw_params(struct snd_pcm_substream *substream, From e66285731262379ddda0d2c821461c260a9b4839 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:21 +0700 Subject: [PATCH 690/924] ASoC: codecs: nau8824: Ensure the DAI is marked as started on success snd_pcm_hw_constraint_minmax() may return 1, which is then returned from nau8824_dai_startup(). ASoC only marks the DAI as started when .startup() returns 0. This currently has no functional impact, as nau8824_dai_ops has no .shutdown() callback. Still, return 0 explicitly on success to match what ASoC expects and ensure the DAI is correctly marked as started. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-15-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8824.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/nau8824.c b/sound/soc/codecs/nau8824.c index 853eb9472af9..c89889aa420e 100644 --- a/sound/soc/codecs/nau8824.c +++ b/sound/soc/codecs/nau8824.c @@ -1061,14 +1061,19 @@ static int nau8824_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); const struct nau8824_osr_attr *osr; + int ret; osr = nau8824_get_osr(nau8824, substream->stream); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_DA_AD_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_DA_AD_MAX / osr->osr); + if (ret < 0) + return ret; + + return 0; } static int nau8824_hw_params(struct snd_pcm_substream *substream, From 552944e3b027e2bfeae9d58ff0824fbd67bee128 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:22 +0700 Subject: [PATCH 691/924] ASoC: codecs: nau8825: Ensure the DAI is marked as started on success snd_pcm_hw_constraint_minmax() may return 1, which is then returned from nau8825_dai_startup(). ASoC only marks the DAI as started when .startup() returns 0. This currently has no functional impact, as nau8825_dai_ops has no .shutdown() callback. Still, return 0 explicitly on success to match what ASoC expects and ensure the DAI is correctly marked as started. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-16-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8825.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index 63d927f31409..10bbfbb460b1 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -1261,14 +1261,19 @@ static int nau8825_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); const struct nau8825_osr_attr *osr; + int ret; osr = nau8825_get_osr(nau8825, substream->stream); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_DA_AD_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_DA_AD_MAX / osr->osr); + if (ret < 0) + return ret; + + return 0; } static int nau8825_hw_params(struct snd_pcm_substream *substream, From b2c81f056c53b425fffb062c0220510ced2654e9 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:23 +0700 Subject: [PATCH 692/924] ASoC: codecs: wm8580: Ensure the DAI is marked as started on success snd_pcm_hw_constraint_minmax() may return 1, which is then returned from wm8580_playback_startup(). ASoC only marks the DAI as started when .startup() returns 0. This currently has no functional impact, as wm8580_dai_ops_playback has no .shutdown() callback. Still, return 0 explicitly on success to match what ASoC expects and ensure the DAI is correctly marked as started. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-17-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8580.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 9bc5d1e795dc..5343d445c322 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -852,9 +852,15 @@ static int wm8580_playback_startup(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct wm8580_priv *wm8580 = snd_soc_component_get_drvdata(component); + int ret; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_CHANNELS, 1, wm8580->drvdata->num_dacs * 2); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 1, + wm8580->drvdata->num_dacs * 2); + if (ret < 0) + return ret; + + return 0; } #define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ From 29f52545ada79a427659ea27c13cb369bc2ad5f2 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:24 +0700 Subject: [PATCH 693/924] ASoC: codecs: wm8782: Ensure the DAI is marked as started on success snd_pcm_hw_constraint_minmax() may return 1, which is then returned from wm8782_dai_startup(). ASoC only marks the DAI as started when .startup() returns 0. This currently has no functional impact, as wm8782_dai_ops has no .shutdown() callback. Still, return 0 explicitly on success to match what ASoC expects and ensure the DAI is correctly marked as started. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-18-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8782.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8782.c b/sound/soc/codecs/wm8782.c index 3a2acdfa9b85..767251ac4f89 100644 --- a/sound/soc/codecs/wm8782.c +++ b/sound/soc/codecs/wm8782.c @@ -39,9 +39,14 @@ static int wm8782_dai_startup(struct snd_pcm_substream *sub, struct snd_soc_dai struct snd_pcm_runtime *runtime = sub->runtime; struct wm8782_priv *priv = snd_soc_component_get_drvdata(dai->component); + int ret; - return snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, 8000, priv->max_rate); + if (ret < 0) + return ret; + + return 0; } static const struct snd_soc_dapm_widget wm8782_dapm_widgets[] = { From 85e56c3bbb7e8ea4626f8653afaef0d79d9cb7ba Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:25 +0700 Subject: [PATCH 694/924] ASoC: imx-pcm-rpmsg: Fix imx_rpmsg_pcm_close() being skipped on rollback snd_pcm_hw_constraint_integer() may return 1, which is then returned from imx_rpmsg_pcm_open(). ASoC only marks the component as opened when .open() returns 0, so .close() can be skipped on rollback. Return 0 explicitly on success. Fixes: 3c00eceb2a53 ("ASoC: imx-pcm-rpmsg: Add platform driver for audio base on rpmsg") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-19-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/imx-pcm-rpmsg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c index 7086fb7fc1eb..30e75fee5175 100644 --- a/sound/soc/fsl/imx-pcm-rpmsg.c +++ b/sound/soc/fsl/imx-pcm-rpmsg.c @@ -269,7 +269,7 @@ static int imx_rpmsg_pcm_open(struct snd_soc_component *component, info->stream_timer[substream->stream].substream = substream; timer_setup(&info->stream_timer[substream->stream].timer, imx_rpmsg_timer_callback, 0); - return ret; + return 0; } static int imx_rpmsg_pcm_close(struct snd_soc_component *component, From e3bb575811042ca30ac9b7b4dacc74aab8efa512 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:26 +0700 Subject: [PATCH 695/924] ASoC: intel: atom: Fix .shutdown() being skipped on rollback snd_pcm_hw_constraint_integer() may return 1, which is then returned from sst_media_open(). ASoC only marks the DAI as started when .startup() returns 0, so .shutdown() can be skipped on rollback. Return 0 explicitly on success. Fixes: 4889a8a73f65 ("ASoC: intel: atom: Use __free(kfree) for stream pointer") Fixes: 6cc0f4e63994 ("ASoC: Intel: mfld_pcm: move stream handling to dai_ops") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-20-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/intel/atom/sst-mfld-platform-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c index a4c8cbfba096..db1fb3b26909 100644 --- a/sound/soc/intel/atom/sst-mfld-platform-pcm.c +++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c @@ -341,7 +341,7 @@ static int sst_media_open(struct snd_pcm_substream *substream, stream = NULL; - return ret_val; + return 0; } static void sst_media_close(struct snd_pcm_substream *substream, From 6bba4e5a171cda96872a2ed12a6ba55c58c032a0 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:27 +0700 Subject: [PATCH 696/924] ASoC: renesas: fsi: Ensure the component is marked as opened on success snd_pcm_hw_constraint_integer() may return 1, which is then returned from fsi_pcm_open(). ASoC only marks the component as opened when .open() returns 0. This currently has no functional impact, as fsi_soc_component has no .close() callback. Still, return 0 explicitly on success to match what ASoC expects and ensure the component is correctly marked as opened. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-21-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/renesas/fsi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/soc/renesas/fsi.c b/sound/soc/renesas/fsi.c index 23b7e63596d3..88618e4b052d 100644 --- a/sound/soc/renesas/fsi.c +++ b/sound/soc/renesas/fsi.c @@ -1811,14 +1811,16 @@ static int fsi_pcm_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - int ret = 0; + int ret; snd_soc_set_runtime_hwparams(substream, &fsi_pcm_hardware); ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; - return ret; + return 0; } static snd_pcm_uframes_t fsi_pointer(struct snd_soc_component *component, From b7c58a55fd9b71c0b11b0365fdf53f49702c39b5 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:28 +0700 Subject: [PATCH 697/924] ASoC: renesas: msiof: Fix msiof_close() being skipped on rollback snd_pcm_hw_constraint_integer() may return 1, which is then returned from msiof_open(). ASoC only marks the component as opened when .open() returns 0, so .close() can be skipped on rollback. Return 0 explicitly on success. Fixes: c61caec22820 ("ASoC: renesas: add MSIOF sound support") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-22-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/renesas/rcar/msiof.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/soc/renesas/rcar/msiof.c b/sound/soc/renesas/rcar/msiof.c index 128543fc4fc9..9fead9a22cdc 100644 --- a/sound/soc/renesas/rcar/msiof.c +++ b/sound/soc/renesas/rcar/msiof.c @@ -427,10 +427,12 @@ static int msiof_open(struct snd_soc_component *component, ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); open_err_dma: - if (ret < 0) + if (ret < 0) { dma_release_channel(chan); + return ret; + } - return ret; + return 0; } static int msiof_close(struct snd_soc_component *component, From fd5c45166d6501aaa6f34e1ebae70ae412b97961 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:29 +0700 Subject: [PATCH 698/924] ASoC: renesas: rz-ssi: Ensure the component is marked as opened on success snd_pcm_hw_constraint_integer() may return 1, which is then returned from rz_ssi_pcm_open_xx(). ASoC only marks the component as opened when .open() returns 0. This currently has no functional impact, as the component driver has no .close() callback. Still, return 0 explicitly on success to match what ASoC expects and ensure the component is correctly marked as opened. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-23-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/renesas/rz-ssi.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c index b1587f499cb8..2e297f721bb6 100644 --- a/sound/soc/renesas/rz-ssi.c +++ b/sound/soc/renesas/rz-ssi.c @@ -937,17 +937,29 @@ static const struct snd_pcm_hardware rz_ssi_pcm_hardware = { static int rz_ssi_pcm_open_pio(struct snd_soc_component *component, struct snd_pcm_substream *substream) { + int ret; + snd_soc_set_runtime_hwparams(substream, &rz_ssi_pcm_hardware); - return snd_pcm_hw_constraint_integer(substream->runtime, + ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + return 0; } static int rz_ssi_pcm_open_dma(struct snd_soc_component *component, struct snd_pcm_substream *substream) { - return snd_pcm_hw_constraint_integer(substream->runtime, - SNDRV_PCM_HW_PARAM_PERIODS); + int ret; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + return 0; } static snd_pcm_uframes_t rz_ssi_pcm_pointer(struct snd_soc_component *component, From 8a43611c834fbb082572e42249ac152f2096163e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:30 +0700 Subject: [PATCH 699/924] ASoC: rockchip: rk3399_gru_sound: Mark the link as started on success snd_pcm_hw_constraint_minmax() may return 1, which is then returned from rockchip_sound_startup(). ASoC only marks the DAI link as started when .startup() returns 0. This currently has no functional impact, as there is no .shutdown() callback in the relevant snd_soc_ops. Still, return 0 explicitly on success to match what ASoC expects and ensure the DAI link is correctly marked as started. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-24-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rk3399_gru_sound.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sound/soc/rockchip/rk3399_gru_sound.c b/sound/soc/rockchip/rk3399_gru_sound.c index 04e7bed8275d..0ec80a4b8c59 100644 --- a/sound/soc/rockchip/rk3399_gru_sound.c +++ b/sound/soc/rockchip/rk3399_gru_sound.c @@ -258,10 +258,15 @@ static int rockchip_sound_dmic_hw_params(struct snd_pcm_substream *substream, static int rockchip_sound_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; + int ret; runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; - return snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, - 8000, 96000); + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, + 8000, 96000); + if (ret < 0) + return ret; + + return 0; } static const struct snd_soc_ops rockchip_sound_max98357a_ops = { From 303961c7daf1ce87848fa17260cd307577850461 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 8 Oct 2026 17:38:31 +0700 Subject: [PATCH 700/924] ASoC: rockchip: rockchip_max98090: Mark the link as started on success snd_pcm_hw_constraint_minmax() may return 1, which is then returned from rk_aif1_startup(). ASoC only marks the DAI link as started when .startup() returns 0. This currently has no functional impact, as there is no .shutdown() callback in the relevant snd_soc_ops. Still, return 0 explicitly on success to match what ASoC expects and ensure the DAI link is correctly marked as started. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261008103831.542533-25-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rockchip_max98090.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c index 426506a8e18e..d54c36682415 100644 --- a/sound/soc/rockchip/rockchip_max98090.c +++ b/sound/soc/rockchip/rockchip_max98090.c @@ -191,12 +191,18 @@ static int rk_aif1_hw_params(struct snd_pcm_substream *substream, static int rk_aif1_startup(struct snd_pcm_substream *substream) { + int ret; /* * Set period size to 240 because pl330 has issue * dealing with larger period in stress testing. */ - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 240, 240); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + 240, 240); + if (ret < 0) + return ret; + + return 0; } static const struct snd_soc_ops rk_aif1_ops = { From 2e2da92de9bffd2cdbb9a2558fe60de7f1bb09e3 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 2 Oct 2026 11:38:52 +0700 Subject: [PATCH 701/924] ASoC: atmel-i2s: fix MCK generator error handling Disable the gclk and reset the MCK configuration when an error occurs while enabling the MCK generator. This prevents the gclk from remaining enabled after a failed clock configuration. Fixes: 73ad0df57290 ("ASoC: atmel-i2s: Remove unnecessary audio PLL clock (aclk)") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261002043856.97295-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/atmel/atmel-i2s.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c index d9ad262ec44d..f28808d980ce 100644 --- a/sound/soc/atmel/atmel-i2s.c +++ b/sound/soc/atmel/atmel-i2s.c @@ -466,11 +466,21 @@ static int atmel_i2s_switch_mck_generator(struct atmel_i2s_dev *dev, mr |= ATMEL_I2SC_MR_IMCKMODE_I2SMCK; ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, mr); if (ret) - return ret; + goto err_disable_gclk; /* Finally enable the I2S Master Clock generator. */ - return regmap_write(dev->regmap, ATMEL_I2SC_CR, - ATMEL_I2SC_CR_CKEN); + ret = regmap_write(dev->regmap, ATMEL_I2SC_CR, ATMEL_I2SC_CR_CKEN); + if (ret) + goto err_reset_mr; + + return 0; + +err_reset_mr: + regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, 0); +err_disable_gclk: + clk_disable_unprepare(dev->gclk); + + return ret; } static int atmel_i2s_trigger(struct snd_pcm_substream *substream, int cmd, From be952abd69c1f2c845e42e72147751e4c910cd81 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 2 Oct 2026 11:38:53 +0700 Subject: [PATCH 702/924] ASoC: atmel-i2s: use optional API for muxclk The muxclk is an optional clock according to the device tree binding. Use devm_clk_get_optional() to handle the optional muxclk instead of handling the missing clock explicitly. When muxclk is not available, devm_clk_get_optional() returns NULL. clk_set_parent() returns 0 when passed NULL, preserving the existing behavior. Unlike the existing code, errors other than -EPROBE_DEFER are now returned instead of being silently ignored. Fixes: b543e467d1a9 ("ASoC: atmel-i2s: add driver for the new Atmel I2S controller") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261002043856.97295-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/atmel/atmel-i2s.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c index f28808d980ce..c52d048449f8 100644 --- a/sound/soc/atmel/atmel-i2s.c +++ b/sound/soc/atmel/atmel-i2s.c @@ -589,21 +589,15 @@ static int atmel_i2s_sama5d2_mck_init(struct atmel_i2s_dev *dev, struct device_node *np) { struct clk *muxclk; - int err; if (!dev->gclk) return 0; - /* muxclk is optional, so we return error for probe defer only */ - muxclk = devm_clk_get(dev->dev, "muxclk"); - if (IS_ERR(muxclk)) { - err = PTR_ERR(muxclk); - if (err == -EPROBE_DEFER) - return -EPROBE_DEFER; - dev_dbg(dev->dev, - "failed to get the I2S clock control: %d\n", err); - return 0; - } + /* muxclk is optional */ + muxclk = devm_clk_get_optional(dev->dev, "muxclk"); + if (IS_ERR(muxclk)) + return dev_err_probe(dev->dev, PTR_ERR(muxclk), + "failed to get the I2S clock control\n"); return clk_set_parent(muxclk, dev->gclk); } From b431a055fb7688c0d3725fa3c55aeef852991178 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 2 Oct 2026 11:38:54 +0700 Subject: [PATCH 703/924] ASoC: atmel-i2s: use optional API for gclk The gclk is an optional clock according to the device tree binding. Use devm_clk_get_optional() to handle the optional gclk directly. Unlike the existing code, errors other than -EPROBE_DEFER are now returned instead of being silently ignored. Fixes: 73ad0df57290 ("ASoC: atmel-i2s: Remove unnecessary audio PLL clock (aclk)") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261002043856.97295-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/atmel/atmel-i2s.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c index c52d048449f8..6ca230f8adc8 100644 --- a/sound/soc/atmel/atmel-i2s.c +++ b/sound/soc/atmel/atmel-i2s.c @@ -670,13 +670,10 @@ static int atmel_i2s_probe(struct platform_device *pdev) } /* Get audio clock to generate the I2S Master Clock (I2S_MCK) */ - dev->gclk = devm_clk_get(&pdev->dev, "gclk"); - if (IS_ERR(dev->gclk)) { - if (PTR_ERR(dev->gclk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - /* Master Mode not supported */ - dev->gclk = NULL; - } + dev->gclk = devm_clk_get_optional(&pdev->dev, "gclk"); + if (IS_ERR(dev->gclk)) + return PTR_ERR(dev->gclk); + dev->dev = &pdev->dev; dev->regmap = regmap; platform_set_drvdata(pdev, dev); From 5421a0a39d438ff74c0ce6c7d3a062455d61120e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 2 Oct 2026 11:38:55 +0700 Subject: [PATCH 704/924] ASoC: atmel-i2s: use dev_err_probe() for pclk errors Use dev_err_probe() when obtaining clocks to avoid redundant error messages, particularly for probe deferral. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261002043856.97295-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/atmel/atmel-i2s.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c index 6ca230f8adc8..9b651eb4f0a4 100644 --- a/sound/soc/atmel/atmel-i2s.c +++ b/sound/soc/atmel/atmel-i2s.c @@ -662,12 +662,9 @@ static int atmel_i2s_probe(struct platform_device *pdev) /* Get the peripheral clock. */ dev->pclk = devm_clk_get(&pdev->dev, "pclk"); - if (IS_ERR(dev->pclk)) { - err = PTR_ERR(dev->pclk); - dev_err(&pdev->dev, - "failed to get the peripheral clock: %d\n", err); - return err; - } + if (IS_ERR(dev->pclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(dev->pclk), + "failed to get the peripheral clock\n"); /* Get audio clock to generate the I2S Master Clock (I2S_MCK) */ dev->gclk = devm_clk_get_optional(&pdev->dev, "gclk"); From c501f9873aca4d3c6bc6b8bfe9885dfe787557c4 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 2 Oct 2026 11:38:56 +0700 Subject: [PATCH 705/924] ASoC: atmel-i2s: remove redundant error message The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() call is redundant and can be removed. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261002043856.97295-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/atmel/atmel-i2s.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c index 9b651eb4f0a4..e1d8af056234 100644 --- a/sound/soc/atmel/atmel-i2s.c +++ b/sound/soc/atmel/atmel-i2s.c @@ -699,7 +699,6 @@ static int atmel_i2s_probe(struct platform_device *pdev) &atmel_i2s_component, &atmel_i2s_dai, 1); if (err) { - dev_err(&pdev->dev, "failed to register DAI: %d\n", err); clk_disable_unprepare(dev->pclk); return err; } @@ -714,7 +713,6 @@ static int atmel_i2s_probe(struct platform_device *pdev) pcm_flags |= SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX; err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, pcm_flags); if (err) { - dev_err(&pdev->dev, "failed to register PCM: %d\n", err); clk_disable_unprepare(dev->pclk); return err; } From 5a0caca149f2938cd82b96b631c5a144329bd30e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 29 Sep 2026 13:11:12 +0700 Subject: [PATCH 706/924] ASoC: mediatek: mt8196: fix APLL mux error handling Propagate errors from clock operations in the enable path and clean up previously enabled clocks when a later operation fails. For the disable path, continue disabling all clocks even if a clock operation fails. Do not propagate the errors so that the disable path can perform as much cleanup as possible. Fixes: 7384ecab34eb ("ASoC: mediatek: mt8196: support audio clock control") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260929061121.124001-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8196/mt8196-afe-clk.c | 56 +++++++++++++--------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c index 286e39f53ae0..91ae44024949 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c @@ -220,33 +220,39 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1], MT8196_AUD_ENG1_CLK); if (ret) - return ret; + goto err_disable_eng1; ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); if (ret) - return ret; + goto err_restore_eng1; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], apll_rate); if (ret) - return ret; + goto err_disable_audio_h; } else { - ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1], - MT8196_AFE_26M); - if (ret) - return ret; + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1], + MT8196_AFE_26M); mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1]); - ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], - MT8196_AFE_26M); - if (ret) - return ret; + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], + MT8196_AFE_26M); mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); } return 0; + +err_disable_audio_h: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); +err_restore_eng1: + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1], + MT8196_AFE_26M); +err_disable_eng1: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1]); + + return ret; } static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) @@ -268,33 +274,39 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2], MT8196_AUD_ENG2_CLK); if (ret) - return ret; + goto err_disable_eng2; ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); if (ret) - return ret; + goto err_restore_eng2; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], apll_rate); if (ret) - return ret; + goto err_disable_audio_h; } else { - ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2], - MT8196_AFE_26M); - if (ret) - return ret; + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2], + MT8196_AFE_26M); mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2]); - ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], - MT8196_AFE_26M); - if (ret) - return ret; + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], + MT8196_AFE_26M); mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); } return 0; + +err_disable_audio_h: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); +err_restore_eng2: + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2], + MT8196_AFE_26M); +err_disable_eng2: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2]); + + return ret; } int mt8196_apll1_enable(struct mtk_base_afe *afe) From 23239cabb6d130c81d2c5389ed2dbdcfc239a939 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 29 Sep 2026 13:11:13 +0700 Subject: [PATCH 707/924] ASoC: mediatek: mt8196: fix APLL enable error handling Propagate errors from the APLL mux and clock enable operations instead of ignoring them or returning success on failure. Clean up previously enabled clocks and disable the APLL tuner when a later clock operation fails, restoring the APLL mux state before returning the error. Fixes: 7384ecab34eb ("ASoC: mediatek: mt8196: support audio clock control") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260929061121.124001-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8196/mt8196-afe-clk.c | 47 ++++++++++++++++------ 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c index 91ae44024949..1dc4210e58c7 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c @@ -314,15 +314,17 @@ int mt8196_apll1_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll1_mux_setting(afe, true); + ret = apll1_mux_setting(afe, true); + if (ret) + return ret; ret = mt8196_afe_enable_top_cg(afe, MT8196_CG_APLL1_CK); if (ret) - goto err_clk_apll1; + goto err_disable_mux; ret = mt8196_afe_enable_top_cg(afe, MT8196_PDN_APLL_TUNER1); if (ret) - goto err_clk_apll1_tuner; + goto err_disable_apll1_ck; /* sel 44.1kHz:1, apll_div:7, upper bound:3 */ regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, @@ -336,14 +338,22 @@ int mt8196_apll1_enable(struct mtk_base_afe *afe) 0x1 << FREQ_TUNER_EN_SFT); /* audio apll1 on */ - mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL1_EN_ON); + ret = mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL1_EN_ON); + if (ret) + goto err_disable_tuner; return 0; -err_clk_apll1_tuner: +err_disable_tuner: + regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x0); mt8196_afe_disable_top_cg(afe, MT8196_PDN_APLL_TUNER1); -err_clk_apll1: +err_disable_apll1_ck: mt8196_afe_disable_top_cg(afe, MT8196_CG_APLL1_CK); +err_disable_mux: + apll1_mux_setting(afe, false); + return ret; } @@ -367,15 +377,17 @@ int mt8196_apll2_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll2_mux_setting(afe, true); + ret = apll2_mux_setting(afe, true); + if (ret) + return ret; ret = mt8196_afe_enable_top_cg(afe, MT8196_CG_APLL2_CK); if (ret) - goto err_clk_apll2; + goto err_disable_mux; ret = mt8196_afe_enable_top_cg(afe, MT8196_PDN_APLL_TUNER2); if (ret) - goto err_clk_apll2_tuner; + goto err_disable_apll2_ck; /* sel 48kHz: 2, apll_div: 7, upper bound: 3*/ regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, @@ -389,14 +401,23 @@ int mt8196_apll2_enable(struct mtk_base_afe *afe) 0x1 << FREQ_TUNER_EN_SFT); /* audio apll2 on */ - mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL2_EN_ON); + ret = mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL2_EN_ON); + if (ret) + goto err_disable_tuner2; + return 0; -err_clk_apll2_tuner: +err_disable_tuner2: + regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x0); mt8196_afe_disable_top_cg(afe, MT8196_PDN_APLL_TUNER2); -err_clk_apll2: +err_disable_apll2_ck: mt8196_afe_disable_top_cg(afe, MT8196_CG_APLL2_CK); - return 0; +err_disable_mux: + apll2_mux_setting(afe, false); + + return ret; } void mt8196_apll2_disable(struct mtk_base_afe *afe) From bc0cd0eb030389a78885561dc92f087e7595458a Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 29 Sep 2026 13:11:14 +0700 Subject: [PATCH 708/924] ASoC: mediatek: mt8196: fix MCK error handling Disable the clock if setting its rate fails after the clock has been enabled, preventing it from being left enabled on the error path. Fixes: 7384ecab34eb ("ASoC: mediatek: mt8196: support audio clock control") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260929061121.124001-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8196/mt8196-afe-clk.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c index 1dc4210e58c7..1d107b85d03c 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c @@ -502,8 +502,10 @@ int mt8196_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) return ret; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[div_clk_id], rate); - if (ret) + if (ret) { + mt8196_afe_disable_clk(afe, afe_priv->clk[div_clk_id]); return ret; + } return 0; } From 2844ba593741a9c9061b8c99790a47ffeed4114d Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 29 Sep 2026 13:11:15 +0700 Subject: [PATCH 709/924] ASoC: mediatek: mt8196: fix register access clock error handling Propagate errors from clock operations and disable previously enabled clocks when a later operation fails. This ensures that the register access clocks are properly cleaned up on the error path. Fixes: 7384ecab34eb ("ASoC: mediatek: mt8196: support audio clock control") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260929061121.124001-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8196/mt8196-afe-clk.c | 25 +++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c index 1d107b85d03c..6198711e01b5 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c @@ -546,26 +546,41 @@ int mt8196_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) int ret; /* bus clock for AFE external access, like DRAM */ - mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_TOP_ADSP_SEL]); + ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_TOP_ADSP_SEL]); + if (ret) + return ret; /* bus clock for AFE internal access, like AFE SRAM */ - mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS]); + ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS]); + if (ret) + goto err_disable_adsp_sel; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS], MT8196_AFE_26M); if (ret) - return ret; + goto err_disable_audiointbus; /* enable audio h clock */ - mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); + ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); + if (ret) + goto err_disable_audiointbus; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], MT8196_AFE_26M); if (ret) - return ret; + goto err_disable_audio_h; /* AFE hw clock */ /* IPM2.0: USE HOPPING & 26M */ /* set in the regmap_register_patch */ return 0; + +err_disable_audio_h: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); +err_disable_audiointbus: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS]); +err_disable_adsp_sel: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_TOP_ADSP_SEL]); + + return ret; } int mt8196_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) From c33bc2dd217d249de7b1ddffb80b8e4a0c597cf3 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 29 Sep 2026 13:11:16 +0700 Subject: [PATCH 710/924] ASoC: mediatek: mt8196: propagate main clock errors Propagate errors from the main clock enable and disable operations instead of always returning success. Fixes: 7384ecab34eb ("ASoC: mediatek: mt8196: support audio clock control") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260929061121.124001-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8196/mt8196-afe-clk.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c index 6198711e01b5..dbfd02d20338 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c @@ -598,14 +598,12 @@ int mt8196_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) int mt8196_afe_enable_main_clock(struct mtk_base_afe *afe) { - mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_26M_EN_ON); - return 0; + return mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_26M_EN_ON); } int mt8196_afe_disable_main_clock(struct mtk_base_afe *afe) { - mt8196_afe_disable_top_cg(afe, MT8196_AUDIO_26M_EN_ON); - return 0; + return mt8196_afe_disable_top_cg(afe, MT8196_AUDIO_26M_EN_ON); } int mt8196_init_clock(struct mtk_base_afe *afe) From b6f0d0e800b58a144d6edc5bba1be792e9daa06e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 29 Sep 2026 13:11:17 +0700 Subject: [PATCH 711/924] ASoC: mediatek: mt8196: use dev_err_probe() in mt8196_init_clock() Use dev_err_probe() when obtaining clocks to avoid redundant error messages, particularly for probe deferral. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260929061121.124001-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8196/mt8196-afe-clk.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c index dbfd02d20338..91a05ea6b8b6 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c @@ -618,10 +618,9 @@ int mt8196_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT8196_CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "devm_clk_get %s fail\n", aud_clks[i]); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } return 0; From 3a9a93cafb376541fecac10c5bb734cd2f26bfc8 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 29 Sep 2026 13:11:18 +0700 Subject: [PATCH 712/924] ASoC: mediatek: mt8196: fix runtime resume error handling Propagate errors from regcache_sync() and main clock enable operations instead of ignoring them. On failure, restore cache-only mode, disable register access clocks, and clear the 26M clock request as needed to clean up the runtime resume state. Fixes: 7384ecab34eb ("ASoC: mediatek: mt8196: support audio clock control") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260929061121.124001-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8196/mt8196-afe-pcm.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c index dcee037991aa..9888812cd993 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c @@ -2211,7 +2211,7 @@ static int mt8196_afe_runtime_suspend(struct device *dev) static int mt8196_afe_runtime_resume(struct device *dev) { struct mtk_base_afe *afe = dev_get_drvdata(dev); - int ret = 0; + int ret; ret = mt8196_afe_enable_reg_rw_clk(afe); if (ret) @@ -2222,7 +2222,9 @@ static int mt8196_afe_runtime_resume(struct device *dev) goto skip_regmap; } regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) + goto err_set_cache_only; /* set audio 26M request */ regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x1); @@ -2233,10 +2235,20 @@ static int mt8196_afe_runtime_resume(struct device *dev) CPU_HD_ALIGN_MASK_SFT, 0 << CPU_HD_ALIGN_SFT); /* enable AFE */ - mt8196_afe_enable_main_clock(afe); + ret = mt8196_afe_enable_main_clock(afe); + if (ret) + goto err_reset_26m_req; skip_regmap: return 0; + +err_reset_26m_req: + regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x0); +err_set_cache_only: + regcache_cache_only(afe->regmap, true); + mt8196_afe_disable_reg_rw_clk(afe); + + return ret; } static int mt8196_afe_component_probe(struct snd_soc_component *component) From 35a7f9f4cd4d49a1db80233248000a2dc4d80c43 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 29 Sep 2026 13:11:19 +0700 Subject: [PATCH 713/924] ASoC: mediatek: mt8196: remove redundant error message The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_warn() and dev_err_probe() call is redundant and can be removed. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260929061121.124001-9-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8196/mt8196-afe-pcm.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c index 9888812cd993..fbeba01b086c 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c @@ -2360,7 +2360,7 @@ static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) /* init audio related clock */ ret = mt8196_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "init clock error.\n"); + return ret; /* init memif */ /* IPM2.0 no need banding */ @@ -2394,13 +2394,13 @@ static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); if (irq_id < 0) - return dev_err_probe(dev, irq_id, "no irq found"); + return irq_id; ret = devm_request_irq(dev, irq_id, mt8196_afe_irq_handler, IRQF_TRIGGER_NONE, "Afe_ISR_Handle", afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); @@ -2469,10 +2469,8 @@ static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) &mt8196_afe_component, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_err(dev, "afe component err\n"); + if (ret) return ret; - } return 0; From 6c14a59bc503fc9aa599f1ebc50777460145e8b4 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 29 Sep 2026 13:11:20 +0700 Subject: [PATCH 714/924] ASoC: mediatek: mt8196: propagate I2S clock errors Propagate errors from APLL and MCK clock operations in the DAPM event callbacks instead of always returning success. Fixes: 65a195132669 ("ASoC: mediatek: mt8196: support I2S in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260929061121.124001-10-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8196/mt8196-dai-i2s.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8196/mt8196-dai-i2s.c b/sound/soc/mediatek/mt8196/mt8196-dai-i2s.c index ef5cde0ba829..adebf75629be 100644 --- a/sound/soc/mediatek/mt8196/mt8196-dai-i2s.c +++ b/sound/soc/mediatek/mt8196/mt8196-dai-i2s.c @@ -985,27 +985,30 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x\n", w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: if (strcmp(w->name, APLL1_W_NAME) == 0) - mt8196_apll1_enable(afe); + ret = mt8196_apll1_enable(afe); else - mt8196_apll2_enable(afe); + ret = mt8196_apll2_enable(afe); break; case SND_SOC_DAPM_POST_PMD: if (strcmp(w->name, APLL1_W_NAME) == 0) mt8196_apll1_disable(afe); else mt8196_apll2_disable(afe); + ret = 0; break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, @@ -1015,6 +1018,7 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mtk_afe_i2s_priv *i2s_priv; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x\n", w->name, event); @@ -1025,17 +1029,18 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8196_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); + ret = mt8196_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); break; case SND_SOC_DAPM_POST_PMD: i2s_priv->mclk_rate = 0; - mt8196_mck_disable(afe, i2s_priv->mclk_id); + ret = mt8196_mck_disable(afe, i2s_priv->mclk_id); break; default: + ret = 0; break; } - return 0; + return ret; } static const struct snd_soc_dapm_widget mtk_dai_i2s_widgets[] = { From d09412f79d70402c44bb92d19eff5b9aea3087e6 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 29 Sep 2026 13:11:21 +0700 Subject: [PATCH 715/924] ASoC: mediatek: mt8196: propagate TDM clock errors Propagate errors from BCK and MCK clock operations in the DAPM event callbacks instead of always returning success. Fixes: ede4d841111a ("ASoC: mediatek: mt8196: support TDM in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260929061121.124001-11-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8196/mt8196-dai-tdm.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c b/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c index b7aeee939d88..44bd08f5c1fb 100644 --- a/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c +++ b/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c @@ -256,22 +256,24 @@ static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, struct mt8196_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d\n", w->name, event, dai_id); switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8196_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); + ret = mt8196_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); break; case SND_SOC_DAPM_POST_PMD: - mt8196_mck_disable(afe, tdm_priv->bck_id); + ret = mt8196_mck_disable(afe, tdm_priv->bck_id); break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, @@ -283,23 +285,25 @@ static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, struct mt8196_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d\n", w->name, event, dai_id); switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8196_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); + ret = mt8196_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); break; case SND_SOC_DAPM_POST_PMD: tdm_priv->mclk_rate = 0; - mt8196_mck_disable(afe, tdm_priv->mclk_id); + ret = mt8196_mck_disable(afe, tdm_priv->mclk_id); break; default: + ret = 0; break; } - return 0; + return ret; } static const struct snd_soc_dapm_widget mtk_dai_tdm_widgets[] = { From a17b02fa1d670d68952d3ffc1e82a6a4c73ac57f Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 6 Oct 2026 11:07:15 +0530 Subject: [PATCH 716/924] ASoC: codecs: wsa885x: Reset registers on teardown for B2B playback During back-to-back speaker playback the PA sometimes fails to enable because a few I2S/TDM and clock source registers retain their previous state and are not reprogrammed on the next stream. Reset the relevant I2S/TDM, clock source and bandgap override registers in hw_free() so the codec is torn down cleanly and the speaker amplifier comes up reliably on back-to-back use cases. Move the bandgap override reset out of set_sysclk() into the teardown path where it belongs. Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20261006053717.3455143-2-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/wsa885x.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/wsa885x.c b/sound/soc/codecs/wsa885x.c index 1faa8541a872..5ffbf9e89fd9 100644 --- a/sound/soc/codecs/wsa885x.c +++ b/sound/soc/codecs/wsa885x.c @@ -896,7 +896,6 @@ static int wsa885x_codec_set_sysclk(struct snd_soc_dai *dai, int clk_id, regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00); regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x01); - regmap_write(wsa885x->regmap, WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00); return 0; } @@ -1006,6 +1005,16 @@ static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { static const struct reg_sequence regs[] = { + { WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_CTL0, 0x06 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, 0x08 }, + { WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 }, + { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 }, { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, }; struct wsa885x_priv *wsa885x; From 46f16a28bae80ef3220f16060c0b587cc3873ca3 Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 6 Oct 2026 11:07:16 +0530 Subject: [PATCH 717/924] ASoC: qcom: sc8280xp: add Maili sound card configuration Maili ships its own firmware, so its topology file has to live in a separate maili/ firmware directory. The topology load path is derived from the sound card driver_name, so reusing the Hawi data would look up the topology under hawi/ instead. Add a dedicated maili_priv_data with driver_name "maili" for the qcom,maili-sndcard compatible so the topology is loaded from the maili/ directory. It keeps the same DAPM widgets and enables codec sysclk, MI2S BCLK and WCD jack detection for the Maili audio pipeline. Signed-off-by: Prasad Kumpatla Reviewed-by: Ajay Kumar Nandam Link: https://patch.msgid.link/20261006053717.3455143-3-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/sc8280xp.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index b5d367486d56..b97cafeb8e78 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -552,6 +552,15 @@ static const struct qcom_snd_soc_common kaanapali_priv_data = { .wcd_jack = true, }; +static const struct qcom_snd_soc_common maili_priv_data = { + .driver_name = "maili", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .codec_sysclk_set = true, + .mi2s_bclk_enable = true, + .wcd_jack = true, +}; + static const struct qcom_snd_soc_common qcs9100_priv_data = { .driver_name = "sa8775p", .dapm_widgets = sc8280xp_dapm_widgets, @@ -676,7 +685,7 @@ static const struct of_device_id snd_sc8280xp_dt_match[] = { { .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data }, { .compatible = "qcom,hawi-sndcard", .data = &hawi_priv_data }, { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data }, - { .compatible = "qcom,maili-sndcard", .data = &hawi_priv_data }, + { .compatible = "qcom,maili-sndcard", .data = &maili_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 }, From 6d222e9806de4bdb68d6624727ba120c4a2c3c08 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 13 Oct 2026 21:02:52 +0300 Subject: [PATCH 718/924] ASoC: nvidia,tegra-audio-common: Document nvidia,aux-devs property Add nvidia,aux-devs which can contain a list of phandles pointing to auxiliary devices, such as amplifiers, sound-muxes or dsp, to be added to the sound card. Signed-off-by: Svyatoslav Ryhel Link: https://patch.msgid.link/20261013180253.477605-2-clamor95@gmail.com Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/nvidia,tegra-audio-common.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml index 2588589ad62d..316806fc7346 100644 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml @@ -48,6 +48,11 @@ properties: $ref: /schemas/types.yaml#/definitions/phandle description: The phandle of audio codec + nvidia,aux-devs: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: List of phandles pointing to auxiliary devices, + such as amplifiers or dsp, to be added to the sound card. + nvidia,spkr-en-gpios: maxItems: 1 description: The GPIO that enables the speakers From 3892339e4fedc2d8545fe2b3e8df600179b1a40c Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Tue, 13 Oct 2026 21:02:53 +0300 Subject: [PATCH 719/924] ASoC: tegra: Support auxiliary devices Add support for auxiliary devices, such as amplifiers, sound-muxes or dsp, to be added to the sound card. Signed-off-by: Svyatoslav Ryhel Link: https://patch.msgid.link/20261013180253.477605-3-clamor95@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra_asoc_machine.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/soc/tegra/tegra_asoc_machine.c b/sound/soc/tegra/tegra_asoc_machine.c index 50b23bbb637b..e4af049fd7e8 100644 --- a/sound/soc/tegra/tegra_asoc_machine.c +++ b/sound/soc/tegra/tegra_asoc_machine.c @@ -550,6 +550,10 @@ int tegra_asoc_machine_probe(struct platform_device *pdev) if (asoc->add_common_snd_ops) card->dai_link->ops = &tegra_machine_snd_ops; + err = snd_soc_of_parse_aux_devs(card, "nvidia,aux-devs"); + if (err) + return err; + if (!card->owner) card->owner = THIS_MODULE; if (!card->driver_name) From 929e433b8cbc1f3c0fe90847124fe1eced5ef4d4 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:33 +0530 Subject: [PATCH 720/924] ASoC: SOF: amd: rename acp7x machine table and descriptor to acp7f The existing acp7x machine table, chip descriptor, firmware, and topology files all target ACP7.F hardware. Rename them to acp7f to reflect the actual hardware variant. No functional change. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-2-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 10 +++++----- sound/soc/amd/mach-config.h | 2 +- sound/soc/sof/amd/pci-acp7x.c | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index 3d48bb0f100f..d40600e12d4f 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -428,17 +428,17 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[] = { }; EXPORT_SYMBOL(snd_soc_acpi_amd_acp70_sof_machines); -struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[] = { +struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[] = { { .id = "AMDI1010", - .drv_name = "acp7x-dsp", + .drv_name = "acp7f-dsp", .pdata = &acp_quirk_data, - .fw_filename = "sof-acp7x.ri", - .sof_tplg_filename = "sof-acp7x.tplg", + .fw_filename = "sof-acp7f.ri", + .sof_tplg_filename = "sof-acp7f.tplg", }, {}, }; -EXPORT_SYMBOL(snd_soc_acpi_amd_acp7x_sof_machines); +EXPORT_SYMBOL(snd_soc_acpi_amd_acp7f_sof_machines); MODULE_DESCRIPTION("AMD ACP Machine Configuration Module"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h index b602a983feb7..2dafd742dc01 100644 --- a/sound/soc/amd/mach-config.h +++ b/sound/soc/amd/mach-config.h @@ -28,7 +28,7 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; -extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[]; struct config_entry { u32 flags; diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 0594c1f3deb6..5949aefe26bc 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -26,8 +26,8 @@ #define ACP7X_REG_START 0x1240000 #define ACP7X_REG_END 0x125C000 -static const struct sof_amd_acp_desc acp7x_chip_info = { - .name = "acp7x", +static const struct sof_amd_acp_desc acp7f_chip_info = { + .name = "acp7f", .pgfsm_base = ACP7X_PGFSM_BASE, .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, @@ -45,13 +45,13 @@ static const struct sof_amd_acp_desc acp7x_chip_info = { .reg_end_addr = ACP7X_REG_END, }; -static const struct sof_dev_desc acp7x_desc = { - .machines = snd_soc_acpi_amd_acp7x_sof_machines, +static const struct sof_dev_desc acp7f_desc = { + .machines = snd_soc_acpi_amd_acp7f_sof_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .chip_info = &acp7x_chip_info, + .chip_info = &acp7f_chip_info, .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), .ipc_default = SOF_IPC_TYPE_3, .default_fw_path = { @@ -61,7 +61,7 @@ static const struct sof_dev_desc acp7x_desc = { [SOF_IPC_TYPE_3] = "amd/sof-tplg", }, .default_fw_filename = { - [SOF_IPC_TYPE_3] = "sof-acp7x.ri", + [SOF_IPC_TYPE_3] = "sof-acp7f.ri", }, .nocodec_tplg_filename = "sof-acp.tplg", .ops = &sof_acp7x_ops, @@ -95,7 +95,7 @@ static void acp7x_pci_remove(struct pci_dev *pci) /* PCI IDs */ static const struct pci_device_id acp7x_pci_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), - .driver_data = (unsigned long)&acp7x_desc}, + .driver_data = (unsigned long)&acp7f_desc}, { 0, } }; MODULE_DEVICE_TABLE(pci, acp7x_pci_ids); From f84363861e442581ecfb9d0fc54c7f2330f3b41b Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:34 +0530 Subject: [PATCH 721/924] ASoC: SOF: amd: select descriptor per PCI revision in acp7x probe Add a static acp7f_pci_id entry carrying the acp7f_desc descriptor. Update acp7x_pci_probe() to check the config flag before the revision switch and return the per-revision pci_device_id to sof_pci_probe() rather than passing the table entry. Remove driver_data from acp7x_pci_ids[] since the descriptor is now selected in the probe callback. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-3-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/pci-acp7x.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 5949aefe26bc..7261377c615b 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -68,23 +68,25 @@ static const struct sof_dev_desc acp7f_desc = { .ops_init = sof_acp7x_ops_init, }; +static const struct pci_device_id acp7f_pci_id = { + PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), + .driver_data = (unsigned long)&acp7f_desc, +}; + static int acp7x_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { unsigned int flag; - switch (pci->revision) { - case ACP7B_PCI_ID: - case ACP7F_PCI_ID: - break; - default: - return -ENODEV; - } - flag = snd_amd_acp_find_config(pci); if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) return -ENODEV; - return sof_pci_probe(pci, pci_id); + switch (pci->revision) { + case ACP7F_PCI_ID: + return sof_pci_probe(pci, &acp7f_pci_id); + default: + return -ENODEV; + } } static void acp7x_pci_remove(struct pci_dev *pci) @@ -94,8 +96,7 @@ static void acp7x_pci_remove(struct pci_dev *pci) /* PCI IDs */ static const struct pci_device_id acp7x_pci_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), - .driver_data = (unsigned long)&acp7f_desc}, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID) }, { 0, } }; MODULE_DEVICE_TABLE(pci, acp7x_pci_ids); From 9246ac5b5c99bac520ae130d21716cde350a8753 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:35 +0530 Subject: [PATCH 722/924] ASoC: amd: add I2S machine table for ACP7.B Add snd_soc_acpi_amd_acp7b_sof_machines with the ACP7.B-specific driver name, firmware image, and topology file. Declare it in mach-config.h alongside the existing acp7f entry. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-4-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 12 ++++++++++++ sound/soc/amd/mach-config.h | 1 + 2 files changed, 13 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index d40600e12d4f..30e01bc47ee0 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -440,5 +440,17 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[] = { }; EXPORT_SYMBOL(snd_soc_acpi_amd_acp7f_sof_machines); +struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_machines[] = { + { + .id = "AMDI1010", + .drv_name = "acp7b-dsp", + .pdata = &acp_quirk_data, + .fw_filename = "sof-acp7b.ri", + .sof_tplg_filename = "sof-acp7b.tplg", + }, + {}, +}; +EXPORT_SYMBOL(snd_soc_acpi_amd_acp7b_sof_machines); + MODULE_DESCRIPTION("AMD ACP Machine Configuration Module"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h index 2dafd742dc01..02ce044bcbf7 100644 --- a/sound/soc/amd/mach-config.h +++ b/sound/soc/amd/mach-config.h @@ -28,6 +28,7 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[]; struct config_entry { From 44f8d28191f13c56b453ca6ea0c1cfe195a58d9c Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:36 +0530 Subject: [PATCH 723/924] ASoC: SOF: amd: add ACP7.B chip descriptor and wire per-revision probe Add acp7b_chip_info, acp7b_desc and acp7b_pci_id for ACP7.B platforms. Extend acp7x_pci_probe() to select acp7b_desc for ACP7B_PCI_ID revisions alongside the existing acp7f_desc path. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-5-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/pci-acp7x.c | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 7261377c615b..5a21ab8ebb4b 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -26,6 +26,48 @@ #define ACP7X_REG_START 0x1240000 #define ACP7X_REG_END 0x125C000 +static const struct sof_amd_acp_desc acp7b_chip_info = { + .name = "acp7b", + .pgfsm_base = ACP7X_PGFSM_BASE, + .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, + .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, + .ext_intr_stat = ACP7X_EXT_INTR_STAT, + .ext_intr_stat1 = ACP7X_EXT_INTR_STAT1, + .dsp_intr_base = ACP7X_DSP_SW_INTR_BASE, + .acp_error_stat = ACP7X_ERROR_STATUS, + .sram_pte_offset = ACP7X_SRAM_PTE_OFFSET, + .hw_semaphore_offset = ACP7X_AXI2DAGB_SEM_0, + .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, + .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, + .reg_start_addr = ACP7X_REG_START, + .sdw_max_link_count = ACP7X_SDW_MAX_MANAGER_COUNT, + .sdw_acpi_dev_addr = SDW_ACPI_ADDR_ACP7X, + .reg_end_addr = ACP7X_REG_END, +}; + +static const struct sof_dev_desc acp7b_desc = { + .machines = snd_soc_acpi_amd_acp7b_sof_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .chip_info = &acp7b_chip_info, + .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), + .ipc_default = SOF_IPC_TYPE_3, + .default_fw_path = { + [SOF_IPC_TYPE_3] = "amd/sof", + }, + .default_tplg_path = { + [SOF_IPC_TYPE_3] = "amd/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC_TYPE_3] = "sof-acp7b.ri", + }, + .nocodec_tplg_filename = "sof-acp.tplg", + .ops = &sof_acp7x_ops, + .ops_init = sof_acp7x_ops_init, +}; + static const struct sof_amd_acp_desc acp7f_chip_info = { .name = "acp7f", .pgfsm_base = ACP7X_PGFSM_BASE, @@ -68,6 +110,11 @@ static const struct sof_dev_desc acp7f_desc = { .ops_init = sof_acp7x_ops_init, }; +static const struct pci_device_id acp7b_pci_id = { + PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), + .driver_data = (unsigned long)&acp7b_desc, +}; + static const struct pci_device_id acp7f_pci_id = { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), .driver_data = (unsigned long)&acp7f_desc, @@ -82,6 +129,8 @@ static int acp7x_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_ return -ENODEV; switch (pci->revision) { + case ACP7B_PCI_ID: + return sof_pci_probe(pci, &acp7b_pci_id); case ACP7F_PCI_ID: return sof_pci_probe(pci, &acp7f_pci_id); default: From b2dc10ee0b51f52a5b9c3dceec848c619ec63082 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:37 +0530 Subject: [PATCH 724/924] ASoC: SOF: amd: Propagate PCI subsystem Vendor and Device IDs Extend the AMD SOF machine driver to propagate the PCI subsystem Vendor and Device IDs so that they may be subsequently used as an SSID. Store the subsystem IDs in acp_dev_data during probe (both amd_sof_acp_probe and amd_sof_acp7x_probe), and propagate them to mach_params in the SoundWire machine select path. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-6-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp-common.c | 6 ++++++ sound/soc/sof/amd/acp.c | 4 ++++ sound/soc/sof/amd/acp.h | 2 ++ 3 files changed, 12 insertions(+) diff --git a/sound/soc/sof/amd/acp-common.c b/sound/soc/sof/amd/acp-common.c index 33540f7c421b..60e5c851c874 100644 --- a/sound/soc/sof/amd/acp-common.c +++ b/sound/soc/sof/amd/acp-common.c @@ -155,9 +155,15 @@ static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev * } if (mach && mach->link_mask) { mach->mach_params.subsystem_rev = acp_data->pci_rev; + mach->mach_params.subsystem_vendor = acp_data->subsystem_vendor; + mach->mach_params.subsystem_device = acp_data->subsystem_device; + mach->mach_params.subsystem_id_set = true; mach->mach_params.links = mach->links; mach->mach_params.link_mask = mach->link_mask; mach->mach_params.platform = dev_name(sdev->dev); + + dev_dbg(sdev->dev, "SSID %x%04x\n", mach->mach_params.subsystem_vendor, + mach->mach_params.subsystem_device); return mach; } } diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index b059039c9e0d..71c39eb36194 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1198,6 +1198,8 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) adata->addr = addr; adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; adata->pci_rev = pci->revision; + adata->subsystem_vendor = pci->subsystem_vendor; + adata->subsystem_device = pci->subsystem_device; mutex_init(&adata->acp_lock); sdev->pdata->hw_pdata = adata; @@ -1335,6 +1337,8 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) adata->addr = addr; adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; adata->pci_rev = pci->revision; + adata->subsystem_vendor = pci->subsystem_vendor; + adata->subsystem_device = pci->subsystem_device; mutex_init(&adata->acp_lock); sdev->pdata->hw_pdata = adata; diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 1bc6b572448d..929251772526 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -296,6 +296,8 @@ struct acp_dev_data { unsigned int pdm_sel; bool is_sdw_dev; unsigned int pci_rev; + u32 subsystem_vendor; + u32 subsystem_device; int acp_sof_signed_firmware_image; }; From b23e9b1f3b4c8f1be4e359cd576eda082391d0cd Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:38 +0530 Subject: [PATCH 725/924] ASoC: SOF: amd: consolidate local variable declarations Use u32 instead of unsigned int for hardware register variables and combine related declarations onto single lines in sof_amd_check_and_handle_acp7x_sdw_wake_irq() and handle_amd_sof_acp7x_sdw_pme_event(). No functional change. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-7-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 71c39eb36194..790af65be34a 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -674,9 +674,8 @@ static int sof_amd_check_and_handle_acp7x_sdw_wake_irq(struct snd_sof_dev *sdev) { struct acp_dev_data *adata = sdev->pdata->hw_pdata; const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); - unsigned int ext_intr_stat1; - unsigned int sdw_pme_stat, sdw_wake_en; - unsigned int i; + u32 ext_intr_stat1, sdw_pme_stat, sdw_wake_en; + u32 i; bool sdw_wake_irq = false; ext_intr_stat1 = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat1); @@ -1445,10 +1444,7 @@ static void handle_amd_sof_acp7x_sdw_pme_event(struct snd_sof_dev *sdev) { struct acp_dev_data *adata; struct amd_sdw_manager *amd_manager; - u32 sdw_pme_stat; - u32 sdw_wake_en; - u32 pme_reg; - u32 wake_mask; + u32 sdw_pme_stat, sdw_wake_en, pme_reg, wake_mask; unsigned int instance; adata = sdev->pdata->hw_pdata; From 0e4c4081169bb7bbe755de62c2d09c4af80fd61a Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:39 +0530 Subject: [PATCH 726/924] ASoC: amd: acp: add acp-mach-common.h for shared machine driver types ACP7.B/7.F platforms expose a PDM controller selection via the acp-audio-ep-port ACPI _DSD property. struct amd_pdm_pdata carries this selection and is required by multiple machine driver variants - the legacy (non-DSP) and SOF stacks - for both I2S and SoundWire paths. Currently the struct lives in soc_amd_sdw_common.h, which is SoundWire-specific, making it unavailable to I2S machine drivers without an inappropriate header dependency. Add acp-mach-common.h as a minimal header for types shared across all AMD ACP machine drivers. Move struct amd_pdm_pdata there and retain an #include of it in soc_amd_sdw_common.h so that no call sites require modification. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-8-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-mach-common.h | 25 +++++++++++++++++++++++++ sound/soc/amd/acp/soc_amd_sdw_common.h | 14 +------------- 2 files changed, 26 insertions(+), 13 deletions(-) create mode 100644 sound/soc/amd/acp/acp-mach-common.h diff --git a/sound/soc/amd/acp/acp-mach-common.h b/sound/soc/amd/acp/acp-mach-common.h new file mode 100644 index 000000000000..a2cb25be6bf8 --- /dev/null +++ b/sound/soc/amd/acp/acp-mach-common.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved + */ +/* + * acp-mach-common.h - Structures shared across all AMD ACP machine drivers + * (I2S, SoundWire, and SOF paths). + */ + +#ifndef __ACP_MACH_COMMON_H +#define __ACP_MACH_COMMON_H + +/** + * struct amd_pdm_pdata - PDM controller platform data passed via mach->pdata + * @pdm_sel: active PDM controller (ACP7X_PDM_DMIC0 or ACP7X_PDM_DMIC1), + * non-zero when a PDM controller was identified via ACPI _DSD for + * ACP7.B/7.F platforms. + * + * Passed via mach->pdata by machine select logic to both I2S and SoundWire + * machine drivers so each can configure the correct DMIC DAI link. + */ +struct amd_pdm_pdata { + unsigned int pdm_sel; +}; + +#endif /* __ACP_MACH_COMMON_H */ diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index 17e4e97fb3d1..034c9ab6e4ea 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -13,6 +13,7 @@ #include #include #include +#include "acp-mach-common.h" #define ACP63_SDW_MAX_CPU_DAIS 8 #define ACP63_SDW_MAX_LINKS 2 @@ -23,19 +24,6 @@ #define ACP71_PCI_REV 0x71 #define ACP72_PCI_REV 0x72 -/** - * struct amd_pdm_pdata - platform data passed via mach->pdata to machine driver - * @pdm_sel: active PDM controller (ACP7X_PDM_DMIC0 or ACP7X_PDM_DMIC1), - * non-zero when a PDM controller was identified via ACPI _DSD - * - * Carries the PDM controller selection for ACP7.B/7.F platforms, derived - * from the acp-audio-ep-port ACPI _DSD property and passed via mach->pdata - * to the machine driver. - */ -struct amd_pdm_pdata { - unsigned int pdm_sel; -}; - #define SOC_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define ASOC_SDW_FOUR_SPK BIT(4) #define ASOC_SDW_ACP_DMIC BIT(5) From 439c43d9628ee38c72fdc5933f7b6091461441b6 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:40 +0530 Subject: [PATCH 727/924] ASoC: SOF: amd: propagate pdm_sel to SDW machine driver via mach->pdata When amd_sof_sdw_machine_select() returns a SoundWire machine, it did not carry the PDM controller selection to the machine driver. Without this, acp-sdw-sof-mach.c cannot configure the correct DMIC DAI link for ACP7.B/7.F platforms. Allocate amd_pdm_pdata and assign it to mach->pdata in the SDW machine select return path when acp_data->pdm_sel is non-zero, mirroring the existing handling in amd_sof_machine_select(). Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-9-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp-common.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/soc/sof/amd/acp-common.c b/sound/soc/sof/amd/acp-common.c index 60e5c851c874..3bb1de8c0376 100644 --- a/sound/soc/sof/amd/acp-common.c +++ b/sound/soc/sof/amd/acp-common.c @@ -132,6 +132,7 @@ static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev * struct snd_soc_acpi_mach *mach; const struct snd_soc_acpi_link_adr *link; struct acp_dev_data *acp_data = sdev->pdata->hw_pdata; + struct amd_pdm_pdata *pdm_pdata; int ret, i; if (acp_data->info.count) { @@ -162,6 +163,16 @@ static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev * mach->mach_params.link_mask = mach->link_mask; mach->mach_params.platform = dev_name(sdev->dev); + if (acp_data->pdm_sel) { + pdm_pdata = devm_kzalloc(sdev->dev, + sizeof(*pdm_pdata), + GFP_KERNEL); + if (!pdm_pdata) + return NULL; + pdm_pdata->pdm_sel = acp_data->pdm_sel; + mach->pdata = pdm_pdata; + } + dev_dbg(sdev->dev, "SSID %x%04x\n", mach->mach_params.subsystem_vendor, mach->mach_params.subsystem_device); return mach; From cec3598305a6df306fc4eb0c49a72e0f9949c06f Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Tue, 13 Oct 2026 21:11:41 +0530 Subject: [PATCH 728/924] ASoC: SOF: amd: add ASP carveout firmware loading for ACP7.B/7.F ACP7.B and ACP7.F require signed firmware to be authenticated by the AMD Security Processor (ASP) before the DSP can execute it. Implement a new two-stage firmware load path via the ASP MPASP mailbox. Stage 1 (probe time): query ASP for the physical base address and ATU group assignment of a pre-allocated 32 MB MALL carveout region. If ASP does not support carveout, probe fails immediately. Stage 2 (load time): load the signed image from disk, send it to ASP via VALIDATE_IMAGE; ASP authenticates the image and deposits the raw binary into the carveout. The driver then SHA-DMAs the raw binary from the carveout into DSP IRAM. On D3->D0 resume the raw binary is preserved in the carveout; the driver skips the filesystem load and ASP authentication and restores directly from the saved carveout address. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20261013154644.3788064-10-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/Makefile | 2 +- sound/soc/sof/amd/acp-dsp-offset.h | 58 +++ sound/soc/sof/amd/acp-loader.c | 64 ++++ sound/soc/sof/amd/acp-stream.c | 9 - sound/soc/sof/amd/acp.c | 77 +++- sound/soc/sof/amd/acp.h | 105 ++++++ sound/soc/sof/amd/acp7x.c | 11 +- sound/soc/sof/amd/acp_asp_mailbox.c | 559 ++++++++++++++++++++++++++++ 8 files changed, 869 insertions(+), 16 deletions(-) create mode 100644 sound/soc/sof/amd/acp_asp_mailbox.c diff --git a/sound/soc/sof/amd/Makefile b/sound/soc/sof/amd/Makefile index bb1c0ddef69d..3a7d230a4c46 100644 --- a/sound/soc/sof/amd/Makefile +++ b/sound/soc/sof/amd/Makefile @@ -4,7 +4,7 @@ # # Copyright(c) 2021, 2023, 2024 Advanced Micro Devices, Inc. All rights reserved. -snd-sof-amd-acp-y := acp.o acp-loader.o acp-ipc.o acp-pcm.o acp-stream.o acp-trace.o acp-common.o +snd-sof-amd-acp-y := acp.o acp-loader.o acp_asp_mailbox.o acp-ipc.o acp-pcm.o acp-stream.o acp-trace.o acp-common.o snd-sof-amd-acp-$(CONFIG_SND_SOC_SOF_ACP_PROBES) += acp-probes.o snd-sof-amd-renoir-y := pci-rn.o renoir.o snd-sof-amd-rembrandt-y := pci-rmb.o rembrandt.o diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h index 3984cd627db6..0bcccf27d133 100644 --- a/sound/soc/sof/amd/acp-dsp-offset.h +++ b/sound/soc/sof/amd/acp-dsp-offset.h @@ -37,6 +37,30 @@ /* Registers from ACP_DSP_0 block */ #define ACP_DSP0_RUNSTALL 0x414 +/* + * ATU group registers for groups 9 to 16. + * ACPAXI2AXI_ATU_CTRL (0xC40) sits between GRP_8 and GRP_9, + * so groups 9 to 16 are offset by 4 bytes relative to a simple stride. + */ +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_9 0xC44 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_9 0xC48 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_10 0xC4C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_10 0xC50 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_11 0xC54 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_11 0xC58 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_12 0xC5C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_12 0xC60 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_13 0xC64 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_13 0xC68 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_14 0xC6C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_14 0xC70 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_15 0xC74 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_15 0xC78 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_16 0xC7C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_16 0xC80 + +/* ATU page size value for 2 MB pages - used for carveout groups */ +#define PAGE_SIZE_2M_ENABLE 0x0 /* Registers from ACP_AXI2AXIATU block */ #define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 0xC00 @@ -132,6 +156,40 @@ #define ACP_SHA_DMA_INCLUDE_HDR 0x1CCC #define ACP_SHA_PSP_ACK 0x1C74 +/* ACP7.B/7.F MALL/Carveout configuration registers (ACP MMIO offsets) */ +#define ACP7X_MALL_ADDR_VALID 0x114C +#define ACP7X_MALL_BASE_ADDR_LOW 0x1150 +#define ACP7X_MALL_BASE_ADDR_HIGH 0x1154 +#define ACP7X_MALL_SIZE 0x1158 +#define ACP7X_MALL_VALID 0x115C + +/* ASP MPASP mailbox SMN addresses for ACP7.B/7.F carveout firmware load */ +#define MPASP_C2PMSG_173_REG 0x03810BB4 +#define MPASP_C2PMSG_174_REG 0x03810BB8 +#define MPASP_C2PMSG_175_REG 0x03810BBC +#define MPASP_C2PMSG_73_REG 0x03810A24 + +/* ASP mailbox command IDs */ +#define ASP_MBOX_CMD_VALIDATE_IMAGE 0x0001 +#define ASP_MBOX_CMD_CALC_HMAC 0x0002 +#define ASP_MBOX_CMD_VALIDATE_HMAC 0x0003 +#define ASP_MBOX_CMD_GET_CARVEOUT_ADDR 0x0004 + +/* MPASP_C2PMSG_173 control register bit definitions */ +#define ASP_MBOX_READY_BIT BIT(31) +#define ASP_MBOX_CMD_ID_MASK 0xFFFF + +/* ASP mailbox timeout: 5 seconds per ASP firmware load specification */ +#define ASP_MBOX_TIMEOUT_US 5000000 +/* Short timeout for the initial GET_CARVEOUT_ADDR probe: fail fast when + * ASP does not support carveout rather than blocking probe for 5 seconds. + */ +#define ASP_MBOX_PROBE_TIMEOUT_US 50000 +#define ASP_MBOX_POLL_INTERVAL_US 1000 + +/* ASP mailbox payload cookie "ACPB" */ +#define ASP_MBOX_COOKIE 0x41435042 + #define ACP_SCRATCH_REG_0 0x10000 #define ACP6X_DSP_FUSION_RUNSTALL 0x0644 #define ACP70_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL diff --git a/sound/soc/sof/amd/acp-loader.c b/sound/soc/sof/amd/acp-loader.c index f1ec85694323..abd080484804 100644 --- a/sound/soc/sof/amd/acp-loader.c +++ b/sound/soc/sof/amd/acp-loader.c @@ -168,12 +168,76 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev) struct pci_dev *pci = to_pci_dev(sdev->dev); const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); struct acp_dev_data *adata; + u64 carveout_offset; unsigned int src_addr, size_fw, dest_addr; u32 page_count, dma_size; + u32 acp_logical_addr; int ret; adata = sdev->pdata->hw_pdata; + if (adata->asp_carveout_base) { + size_fw = adata->fw_code_raw_size; + page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT; + adata->fw_bin_page_count = page_count; + + if (adata->fw_code_carveout_addr < adata->asp_carveout_base || + adata->fw_code_carveout_addr - adata->asp_carveout_base + + size_fw > adata->asp_carveout_size) { + dev_err(sdev->dev, + "carveout addr 0x%llx out of window [0x%llx+0x%x]\n", + adata->fw_code_carveout_addr, + adata->asp_carveout_base, + adata->asp_carveout_size); + return -EINVAL; + } + carveout_offset = adata->fw_code_carveout_addr - adata->asp_carveout_base; + acp_logical_addr = ACP_SYSTEM_MEMORY_WINDOW + + (adata->asp_carveout_group_start * 8 * SZ_1M) + + (u32)carveout_offset; + + ret = configure_and_run_sha_dma(adata, NULL, + acp_logical_addr, + ACP_IRAM_BASE_ADDRESS, size_fw); + if (ret < 0) { + dev_err(sdev->dev, "SHA DMA from carveout failed: %d\n", ret); + return ret; + } + + if (adata->is_sram_in_use) { + configure_pte_for_fw_loading(FW_SRAM_DATA_BIN, + ACP_SRAM_PAGE_COUNT, adata); + src_addr = ACP_SYSTEM_MEMORY_WINDOW + ACP_DEFAULT_SRAM_LENGTH + + (page_count * ACP_PAGE_SIZE); + dest_addr = ACP7X_SRAM_BASE_ADDRESS; + ret = configure_and_run_dma(adata, src_addr, dest_addr, + adata->fw_sram_data_bin_size); + if (ret < 0) { + dev_err(sdev->dev, "SRAM DMA failed: %d\n", ret); + dma_free_coherent(&pci->dev, ACP_DEFAULT_SRAM_LENGTH, + adata->sram_data_buf, adata->sram_dma_addr); + adata->sram_data_buf = NULL; + return ret; + } + ret = acp_dma_status(adata, 0); + if (ret < 0) + dev_err(sdev->dev, "SRAM DMA status error: %d\n", ret); + } + + /* Enable cache window */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_CACHE_OFFSET0, + desc->sram_pte_offset); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_CACHE_SIZE0, + SRAM1_SIZE | BIT(31)); + + if (adata->is_sram_in_use) { + dma_free_coherent(&pci->dev, ACP_DEFAULT_SRAM_LENGTH, + adata->sram_data_buf, adata->sram_dma_addr); + adata->sram_data_buf = NULL; + } + return ret; + } + if (adata->pci_rev >= ACP7B_PCI_ID) { if (adata->acp_sof_signed_firmware_image) { if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { diff --git a/sound/soc/sof/amd/acp-stream.c b/sound/soc/sof/amd/acp-stream.c index 9212a3137cfd..a751ffdb0adf 100644 --- a/sound/soc/sof/amd/acp-stream.c +++ b/sound/soc/sof/amd/acp-stream.c @@ -15,15 +15,6 @@ #include "acp-dsp-offset.h" #include "acp.h" -#define PTE_GRP1_OFFSET 0x00000000 -#define PTE_GRP2_OFFSET 0x00800000 -#define PTE_GRP3_OFFSET 0x01000000 -#define PTE_GRP4_OFFSET 0x01800000 -#define PTE_GRP5_OFFSET 0x02000000 -#define PTE_GRP6_OFFSET 0x02800000 -#define PTE_GRP7_OFFSET 0x03000000 -#define PTE_GRP8_OFFSET 0x03800000 - int acp_dsp_stream_config(struct snd_sof_dev *sdev, struct acp_dsp_stream *stream) { const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 790af65be34a..86e2402b8ffd 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -279,11 +279,18 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, unsigned int tx_count, fw_qualifier, val; int ret; - if (!image_addr) { + if (!image_addr && + !adata->asp_carveout_base) { dev_err(sdev->dev, "SHA DMA image address is NULL\n"); return -EINVAL; } + /* + * Flush the ATU cache before programming SHA DMA registers + * to guarantee coherency at SHA DMA start time. + */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID); + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD); if (val & ACP_SHA_RUN) { snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RESET); @@ -297,8 +304,13 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, } } - if ((adata->quirks && adata->quirks->signed_fw_image) || - adata->acp_sof_signed_firmware_image) + /* + * The signing header must NOT be included for the carveout path: ASP + * stripped it when depositing the raw binary during VALIDATE_IMAGE. + */ + if (((adata->quirks && adata->quirks->signed_fw_image) || + adata->acp_sof_signed_firmware_image) && + !adata->asp_carveout_base) snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_INCLUDE_HDR, ACP_SHA_HEADER); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_STRT_ADDR, start_addr); @@ -316,7 +328,17 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, if (ret) return ret; } - snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN); + /* + * SHA_IOC_En triggers an interrupt-on-completion to ASP for source + * address validation. Only set it on the carveout path; enabling it + * on the legacy ATU-window path breaks PSP validation. + */ + if (adata->asp_carveout_base) { + reinit_completion(&adata->sha_dma_complete); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN_WITH_IOC); + } else { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN); + } ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_TRANSFER_BYTE_CNT, tx_count, tx_count == image_length, @@ -333,6 +355,23 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, return ret; } + if (adata->asp_carveout_base) { + /* + * Wait for the SHA IOC interrupt which fires after ASP validates + * the source address. Use ASP_MBOX_TIMEOUT_US to match the + * budget used for all other ASP mailbox operations. + * usecs_to_jiffies() can round to 0 at low HZ so clamp to at + * least 1 jiffy. + */ + unsigned long timeout_jiffies = + max(1UL, usecs_to_jiffies(ASP_MBOX_TIMEOUT_US)); + + if (!wait_for_completion_timeout(&adata->sha_dma_complete, + timeout_jiffies)) + dev_warn(sdev->dev, + "SHA DMA interrupt not received within timeout, continuing\n"); + } + ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_DSP_FW_QUALIFIER, fw_qualifier, fw_qualifier & DSP_FW_RUN_ENABLE, ACP_REG_POLL_INTERVAL, ACP_DMA_COMPLETE_TIMEOUT_US); @@ -768,6 +807,20 @@ static irqreturn_t acp7x_irq_handler(int irq, void *dev_id) if (adata->sdw) wake_irq_flag = sof_amd_check_and_handle_acp7x_sdw_wake_irq(sdev); + /* + * SHA DMA completion interrupt (ACP_SHA_STAT, bit 15 of ext_intr_stat). + * ACP_SHA_STAT has no dedicated mask bit in ACP_EXTERNAL_INTR_CNTL; + * it is always enabled by default and fires only when SHA DMA completes + * with SHA_IOC_En set. SHA DMA is triggered exclusively during firmware + * loading, so this interrupt is only asserted in that context. + * Acknowledge and signal sha_dma_complete so configure_and_run_sha_dma() + * can proceed to poll ACP_SHA_DSP_FW_QUALIFIER. + */ + if (ext_intr_stat & ACP_SHA_STAT) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_SHA_STAT); + complete(&adata->sha_dma_complete); + irq_flag = 1; + } if (ext_intr_stat & ACP7X_ERROR_IRQ) { snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP7X_ERROR_IRQ); @@ -1200,6 +1253,7 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) adata->subsystem_vendor = pci->subsystem_vendor; adata->subsystem_device = pci->subsystem_device; mutex_init(&adata->acp_lock); + init_completion(&adata->sha_dma_complete); sdev->pdata->hw_pdata = adata; ret = acp_init(sdev); @@ -1339,6 +1393,7 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) adata->subsystem_vendor = pci->subsystem_vendor; adata->subsystem_device = pci->subsystem_device; mutex_init(&adata->acp_lock); + init_completion(&adata->sha_dma_complete); sdev->pdata->hw_pdata = adata; ret = acp_init(sdev); @@ -1411,6 +1466,20 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) acp_memory_init(sdev); acp_dsp_stream_init(sdev); + /* + * Carveout is the only supported signed firmware load path on + * ACP7.B/7.F. Query ASP now when signed firmware is enabled so + * probe fails cleanly if carveout is unavailable. + */ + if (adata->acp_sof_signed_firmware_image) { + ret = acp7x_query_asp_carveout(sdev); + if (ret) { + dev_err(sdev->dev, + "ASP carveout unavailable, cannot load firmware: %d\n", ret); + goto free_ipc_irq; + } + } + return 0; free_ipc_irq: diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 929251772526..4eefc9cdba1b 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -49,10 +49,14 @@ #define ACP_PAGE_SIZE 0x1000 #define ACP_DMA_CH_RUN 0x02 #define ACP_MAX_DESC_CNT 0x02 +#define ACP_DSP_RUN 0x00 #define DSP_FW_RUN_ENABLE 0x01 #define ACP_SHA_RUN 0x01 #define ACP_SHA_RESET 0x02 #define ACP_SHA_HEADER 0x01 +/* SHA_IOC_En (bit 2): interrupt-on-completion to ASP; must be set alongside sha_run */ +#define ACP_SHA_IOC_EN 0x04 +#define ACP_SHA_RUN_WITH_IOC (ACP_SHA_RUN | ACP_SHA_IOC_EN) #define ACP_DMA_CH_RST 0x01 #define ACP_DMA_CH_GRACEFUL_RST_EN 0x10 #define ACP_ATU_CACHE_INVALID 0x01 @@ -115,6 +119,28 @@ #define ACP_FIRMWARE_SIGNATURE 0x100 #define ACP_IMAGE_HEADER_SIZE ACP_FIRMWARE_SIGNATURE #define ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF 0x14 +#define ACP_ASP_SIGNATURE_LENGTH 512 + +/* + * ACP7.B/7.F carveout: 32 MB region mapped at 2 MB page granularity. + * Use AXI2AXIATU_PAGE_SIZE_Mask2MB (value 0x0) for carveout ATU + * groups giving 32MB / 2MB = 16 PTEs. 4KB pages would require 8192 PTEs and + * is not how the hardware carveout path is configured. + */ +#define ACP7X_CARVEOUT_MAX_SIZE (32 * SZ_1M) +#define ACP7X_CARVEOUT_PAGE_SIZE (2 * SZ_1M) +#define ACP7X_CARVEOUT_PAGE_COUNT (ACP7X_CARVEOUT_MAX_SIZE / ACP7X_CARVEOUT_PAGE_SIZE) +/* + * PTE high-word flags for ATU entries: + * bit[31]: PAGE_Enable - entry is valid + * bit[30]: MALL_Enable - route accesses through carveout (MALL) memory, + * not standard DRAM. Required for ASP to recognise the SHA DMA + * source as within the carveout and grant ACP_SHA_DSP_FW_QUALIFIER. + * (PAGE_Enable | MALL_Enable ATU entry flags) + */ +#define ACP_ATU_PTE_PAGE_ENABLE BIT(31) +#define ACP_ATU_PTE_MALL_ENABLE BIT(30) +#define ACP_ATU_PTE_CARVEOUT_FLAGS (ACP_ATU_PTE_PAGE_ENABLE | ACP_ATU_PTE_MALL_ENABLE) #define ACP_ERROR_IRQ_MASK BIT(29) #define ACP_SDW0_IRQ_MASK BIT(21) @@ -139,6 +165,30 @@ #define ACP_DSP_MSG_SET 1 #define ACP_DSP_ACK_SET 1 +/* + * ATU PTE group ACP logical address offsets. + * Each group covers 8 MB (0x00800000). Groups 1-8 are used for PCM + * streams; groups 9-16 are extended groups. + * + * GRP_N_OFFSET = (N - 1) * 8 MB + */ +#define PTE_GRP1_OFFSET 0x00000000 +#define PTE_GRP2_OFFSET 0x00800000 +#define PTE_GRP3_OFFSET 0x01000000 +#define PTE_GRP4_OFFSET 0x01800000 +#define PTE_GRP5_OFFSET 0x02000000 +#define PTE_GRP6_OFFSET 0x02800000 +#define PTE_GRP7_OFFSET 0x03000000 +#define PTE_GRP8_OFFSET 0x03800000 +#define PTE_GRP9_OFFSET 0x04000000 +#define PTE_GRP10_OFFSET 0x04800000 +#define PTE_GRP11_OFFSET 0x05000000 +#define PTE_GRP12_OFFSET 0x05800000 +#define PTE_GRP13_OFFSET 0x06000000 +#define PTE_GRP14_OFFSET 0x06800000 +#define PTE_GRP15_OFFSET 0x07000000 +#define PTE_GRP16_OFFSET 0x07800000 + enum clock_source { ACP_CLOCK_96M = 0, ACP_CLOCK_48M, @@ -152,6 +202,29 @@ struct acp_atu_grp_pte { u32 high; }; +/* ASP mailbox payload for GET_CARVEOUT_ADDR command (0x04) */ +struct asp_get_carveout_payload { + u32 cookie; /* [0x00] Driver: ASP_MBOX_COOKIE */ + u32 status; /* [0x04] ASP: 0 = success */ + u64 carveout_addr; /* [0x08] ASP: carveout physical base address */ + u32 carveout_size; /* [0x10] ASP: ACP_MALL_SIZE register value */ + u32 mall_addr_valid; /* [0x14] ASP: ACP_MALL_ADDR_VALID register value */ + u32 mall_valid; /* [0x18] ASP: ACP_MALL_VALID register value */ +}; + +/* + * ASP mailbox payload for VALIDATE_IMAGE command (0x01). + * Driver fills src/dest fields; ASP fills carveout_dest_addr and status. + */ +struct asp_validate_image_payload { + u32 cookie; /* [0x00] Driver: ASP_MBOX_COOKIE */ + u32 status; /* [0x04] ASP: 0 = success */ + u64 carveout_dest_addr; /* [0x08] ASP: physical addr of raw binary in carveout */ + u64 dest_offset; /* [0x10] Driver: destination offset within carveout */ + u64 src_phys_addr; /* [0x18] Driver: signed FW source physical addr in DDR */ + u32 fw_image_size; /* [0x20] Driver: total signed image size in bytes */ +}; + union dma_tx_cnt { struct { unsigned int count : 19; @@ -299,6 +372,33 @@ struct acp_dev_data { u32 subsystem_vendor; u32 subsystem_device; int acp_sof_signed_firmware_image; + /* ACP7.F ASP carveout firmware load state */ + u64 asp_carveout_base; + u32 asp_carveout_size; + u32 asp_carveout_group_start; /* first ATU group index for carveout (from ASP) */ + u32 asp_carveout_group_count; /* number of consecutive ATU groups for carveout */ + /* + * Physical address and raw size of each validated DSP code binary + * within the carveout region (returned by ASP VALIDATE_IMAGE). + * Preserved across suspend/resume so that SHA DMA can reload DSP IRAM + * from the carveout without re-authenticating from disk. + */ + u64 fw_code_carveout_addr; + u32 fw_code_raw_size; + /* Real CPU physical address of the ASP MPASP mailbox payload page */ + phys_addr_t asp_mbox_buf_phys; + void *asp_mbox_buf; + /* + * SHA DMA completion for the carveout path. + * ACP_SHA_STAT (bit 15 of ACP_EXTERNAL_INTR_STAT) has no dedicated + * mask bit in ACP_EXTERNAL_INTR_CNTL - it is always enabled by default. + * SHA DMA is triggered exclusively during firmware loading, so no + * spurious ACP_SHA_STAT interrupts are expected outside that window. + * acp7x_irq_handler signals sha_dma_complete when ACP_SHA_STAT fires; + * configure_and_run_sha_dma() waits on it before polling + * ACP_SHA_DSP_FW_QUALIFIER to ensure ASP has validated the source. + */ + struct completion sha_dma_complete; }; void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes); @@ -321,6 +421,11 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev); int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev); int acp_get_bar_index(struct snd_sof_dev *sdev, u32 type); +/* ACP7.B/7.F ASP carveout firmware load helper functions */ +int acp7x_query_asp_carveout(struct snd_sof_dev *sdev); +int acp7x_load_firmware_carveout(struct snd_sof_dev *sdev); +int acp7x_configure_carveout_pte(struct snd_sof_dev *sdev); + /* Block IO callbacks */ int acp_dsp_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, u32 offset, void *src, size_t size); diff --git a/sound/soc/sof/amd/acp7x.c b/sound/soc/sof/amd/acp7x.c index b6722d11168c..521b7c00c761 100644 --- a/sound/soc/sof/amd/acp7x.c +++ b/sound/soc/sof/amd/acp7x.c @@ -170,8 +170,15 @@ int sof_acp7x_ops_init(struct snd_sof_dev *sdev) acp_sof_post_fw_run_delay = obj->integer.value; } - if (acp_sof_signed_firmware_image) - sof_acp7x_ops.load_firmware = acp_sof_load_signed_firmware; + if (acp_sof_signed_firmware_image) { + /* + * This driver handles only ACP7.B/7.F which both use the ASP + * carveout firmware load path. Assign unconditionally rather + * than rechecking the revision so the correct loader is always + * selected when signed firmware is enabled. + */ + sof_acp7x_ops.load_firmware = acp7x_load_firmware_carveout; + } if (acp_sof_post_fw_run_delay) sof_acp7x_ops.post_fw_run = sof_acp7x_post_fw_run_delay; diff --git a/sound/soc/sof/amd/acp_asp_mailbox.c b/sound/soc/sof/amd/acp_asp_mailbox.c new file mode 100644 index 000000000000..a9c0edfe0c3f --- /dev/null +++ b/sound/soc/sof/amd/acp_asp_mailbox.c @@ -0,0 +1,559 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2026 Advanced Micro Devices, Inc. All rights reserved. +// +// Authors: Vijendar Mukunda + +/* + * ASP (AMD Security Processor) mailbox communication and ACP7.B/7.F carveout + * memory firmware load/restore for the ACP7.B/7.F platform. + */ + +#include +#include +#include +#include +#include +#include + +#include "../ops.h" +#include "acp-dsp-offset.h" +#include "acp.h" + +/* + * acp7x_asp_send_cmd - send one ASP mailbox command via the ACPI AMSG method. + * + * The ACPI method "AMSG" (stored as 'GSMA' LE) is used to + * proxy commands to ASP. Direct SMN writes to MPASP_C2PMSG_* do not work + * because the BIOS gates host access to those registers and requires all + * ASP mailbox traffic to go through this ACPI method. + * + * The caller must fill adata->asp_mbox_buf with the payload before calling. + * On return the same buffer contains ASP's response fields. + * + * ACPI method signature: AMSG(CommandId, PayloadPhysAddrHi, PayloadPhysAddrLo) + * GET_CARVEOUT_ADDR uses a short 50 ms probe timeout; other commands use 5 s. + */ +static int acp7x_asp_send_cmd(struct snd_sof_dev *sdev, u16 cmd_id) +{ + struct pci_dev *pci = to_pci_dev(sdev->dev); + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + struct acpi_device *adev = ACPI_COMPANION(&pci->dev); + union acpi_object args[3]; + struct acpi_object_list arg_list = { ARRAY_SIZE(args), args }; + unsigned long timeout; + acpi_handle handle; + acpi_status status; + unsigned int ready_timeout; + u32 ctrl; + int ret, smn_ret; + + if (!adev) { + dev_err(sdev->dev, "ASP cmd 0x%04x: no ACPI companion device\n", cmd_id); + return -ENODEV; + } + + handle = adev->handle; + + /* Short timeout for GET_CARVEOUT_ADDR to fail fast if ASP lacks carveout support. */ + ready_timeout = (cmd_id == ASP_MBOX_CMD_GET_CARVEOUT_ADDR) ? + ASP_MBOX_PROBE_TIMEOUT_US : ASP_MBOX_TIMEOUT_US; + + timeout = jiffies + usecs_to_jiffies(ready_timeout); + ret = -ETIMEDOUT; + do { + smn_ret = amd_smn_read(0, MPASP_C2PMSG_173_REG, &ctrl); + if (smn_ret) { + dev_err(sdev->dev, "ASP mailbox SMN read failed: %d\n", smn_ret); + return smn_ret; + } + if (ctrl & ASP_MBOX_READY_BIT) { + ret = 0; + break; + } + usleep_range(ASP_MBOX_POLL_INTERVAL_US, ASP_MBOX_POLL_INTERVAL_US + 100); + } while (!time_after(jiffies, timeout)); + + if (ret) { + dev_err(sdev->dev, "ASP mailbox not ready before cmd 0x%04x\n", cmd_id); + return -ETIMEDOUT; + } + + args[0].type = ACPI_TYPE_INTEGER; + args[0].integer.value = cmd_id; + args[1].type = ACPI_TYPE_INTEGER; + args[1].integer.value = upper_32_bits(adata->asp_mbox_buf_phys); + args[2].type = ACPI_TYPE_INTEGER; + args[2].integer.value = lower_32_bits(adata->asp_mbox_buf_phys); + + status = acpi_evaluate_object(handle, "AMSG", &arg_list, NULL); + if (ACPI_FAILURE(status)) { + dev_err(sdev->dev, "ASP AMSG cmd 0x%04x: ACPI method failed: %s\n", + cmd_id, acpi_format_exception(status)); + return -EIO; + } + return 0; +} + +/* + * acp7x_configure_carveout_pte - program ATU groups for ACP7.B/7.F carveout. + * + * Programs PAGE_SIZE, BASE_ADDR and PTE entries for each ATU group covering + * the carveout region using 2 MB pages with PAGE_Enable | MALL_Enable flags. + * Must be called after GET_CARVEOUT_ADDR and on every D0 resume since + * acp_init() resets the scratch SRAM and ATU registers. + */ +int acp7x_configure_carveout_pte(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u64 phys_addr; + u32 grp_start = adata->asp_carveout_group_start; + u32 grp_count = adata->asp_carveout_group_count; + u32 total_pages, pages_per_group; + u32 grp, page_in_grp, page_idx; + u32 pte_scratch_offset, page_size_reg, base_addr_reg, reg_val; + u32 pte_lo_addr, pte_hi_addr; + u32 low, high; + u32 abs_grp; + + if (!adata->asp_carveout_base) { + dev_err(sdev->dev, "carveout PTE: no valid carveout address\n"); + return -EINVAL; + } + + if (!grp_count || grp_start < 8 || grp_start + grp_count > 16) { + dev_err(sdev->dev, "carveout PTE: invalid ATU group range %u+%u\n", + grp_start, grp_count); + return -EINVAL; + } + + /* + * ASP is expected to allocate exactly four contiguous 8 MB ATU groups + * for the fixed 32 MB carveout. Warn if the response differs so that + * unexpected firmware behaviour is clearly visible in the log. + */ + if (grp_count != 4) + dev_warn(sdev->dev, "carveout PTE: expected 4 ATU groups, got %u\n", + grp_count); + + total_pages = adata->asp_carveout_size / ACP7X_CARVEOUT_PAGE_SIZE; + pages_per_group = total_pages / grp_count; + page_idx = 0; + + for (grp = 0; grp < grp_count; grp++) { + abs_grp = grp_start + grp; + + /* + * asp_carveout_group_start is the 0-based index from + * __ffs(mall_addr_valid): bit N in MallGroupEntries represents + * hardware GRP_(N+1), so abs_grp is 0-based and GRP number is + * (abs_grp + 1). Example: bits 11-14 set -> abs_grp 11..14 -> + * programs GRP_12..GRP_15 (registers 0xC5C..0xC74). + * ATU_CTRL (0xC40) sits between GRP_8 and GRP_9, adding a + * 4-byte gap for abs_grp >= 8 (i.e. GRP_9 and above). + */ + if (abs_grp < 8) + page_size_reg = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 + abs_grp * 8; + else + page_size_reg = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 + abs_grp * 8 + 4; + + base_addr_reg = page_size_reg + 4; + + pte_scratch_offset = abs_grp * 0x20; + reg_val = desc->sram_pte_offset + pte_scratch_offset; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, page_size_reg, PAGE_SIZE_2M_ENABLE); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, base_addr_reg, reg_val | BIT(31)); + + dev_dbg(sdev->dev, + "carveout ATU: abs_grp=%u PAGE_SIZE_reg=0x%x BASE_ADDR_reg=0x%x val=0x%x\n", + abs_grp, page_size_reg, base_addr_reg, + (u32)(reg_val | BIT(31))); + + for (page_in_grp = 0; page_in_grp < pages_per_group; + page_in_grp++, page_idx++) { + pte_lo_addr = ACP_SCRATCH_REG_0 + pte_scratch_offset + (page_in_grp * 8); + pte_hi_addr = pte_lo_addr + 4; + phys_addr = adata->asp_carveout_base + + ((u64)page_idx * ACP7X_CARVEOUT_PAGE_SIZE); + low = lower_32_bits(phys_addr); + high = upper_32_bits(phys_addr) | ACP_ATU_PTE_CARVEOUT_FLAGS; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_lo_addr, low); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_hi_addr, high); + } + } + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID); + + dev_dbg(sdev->dev, + "carveout PTE done: %u groups [%u..%u] covering 0x%llx..0x%llx\n", + grp_count, grp_start, grp_start + grp_count - 1, + adata->asp_carveout_base, + adata->asp_carveout_base + (u64)adata->asp_carveout_size); + return 0; +} +EXPORT_SYMBOL_NS(acp7x_configure_carveout_pte, "SND_SOC_SOF_AMD_COMMON"); + +/* + * acp7x_alloc_asp_payload - allocate a physically contiguous buffer below 4 GB. + * + * ASP accesses memory via the SoC fabric, bypassing the IOMMU, so the buffer + * must be below 4 GB and page_to_phys() must return the real CPU physical + * address. Use __GFP_DMA32/__GFP_DMA as hard zone constraints (not hints) + * to guarantee the physical address fits in 32 bits. + */ +static void *acp7x_alloc_asp_payload(struct snd_sof_dev *sdev, phys_addr_t *phys_out) +{ + struct page *page; + void *vaddr; + + page = alloc_page(GFP_KERNEL | __GFP_DMA32 | __GFP_ZERO); + if (!page) + page = alloc_page(GFP_KERNEL | __GFP_DMA | __GFP_ZERO); + if (!page) { + dev_err(sdev->dev, "ASP mbox: failed to allocate below-4GB payload page\n"); + return NULL; + } + + vaddr = page_address(page); + *phys_out = page_to_phys(page); + + if (*phys_out > 0xFFFFFFFFULL) { + dev_err(sdev->dev, "ASP mbox: page at phys=0x%llx still exceeds 4 GB\n", + (u64)*phys_out); + __free_page(page); + return NULL; + } + + return vaddr; +} + +static void acp7x_free_asp_payload(void *vaddr) +{ + if (vaddr) + __free_page(virt_to_page(vaddr)); +} + +int acp7x_query_asp_carveout(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + struct asp_get_carveout_payload *co_payload; + phys_addr_t phys; + u32 group_bits, first_bit, count; + int ret = 0; + + co_payload = acp7x_alloc_asp_payload(sdev, &phys); + if (!co_payload) + return -ENOMEM; + + co_payload->cookie = ASP_MBOX_COOKIE; + /* Sentinel: ASP overwrites on success; 0xFFFFFFFF after the call means no carveout */ + co_payload->carveout_size = 0xFFFFFFFF; + co_payload->mall_addr_valid = 0xFFFFFFFF; + co_payload->mall_valid = 0xFFFFFFFF; + + adata->asp_mbox_buf = co_payload; + adata->asp_mbox_buf_phys = phys; + + ret = acp7x_asp_send_cmd(sdev, ASP_MBOX_CMD_GET_CARVEOUT_ADDR); + + dev_dbg(sdev->dev, + "GET_CARVEOUT response: status=0x%x addr=0x%llx size=0x%x mall_valid=0x%x mall_addr_valid=0x%x\n", + co_payload->status, co_payload->carveout_addr, + co_payload->carveout_size, co_payload->mall_valid, + co_payload->mall_addr_valid); + + if (ret) + goto out_free; + + if (co_payload->status || !co_payload->carveout_addr || + !co_payload->carveout_size || co_payload->carveout_size == 0xFFFFFFFF) { + dev_warn(sdev->dev, + "ASP GET_CARVEOUT_ADDR: status=0x%x addr=0x%llx size=0x%x - no carveout\n", + co_payload->status, co_payload->carveout_addr, + co_payload->carveout_size); + ret = -ENODATA; + goto out_free; + } + + if (!IS_ALIGNED(co_payload->carveout_addr, ACP7X_CARVEOUT_PAGE_SIZE) || + co_payload->carveout_size != ACP7X_CARVEOUT_MAX_SIZE) { + dev_warn(sdev->dev, + "ASP carveout: unexpected base alignment or size (addr=0x%llx size=0x%x, expected 0x%x)\n", + co_payload->carveout_addr, co_payload->carveout_size, + ACP7X_CARVEOUT_MAX_SIZE); + ret = -ENODATA; + goto out_free; + } + /* + * Extract ATU group start and count from MallGroupEntries bits[15:0]. + * Scan to find first set bit (start), then count + * contiguous set bits. Example: 0x7800 -> start=11, count=4. + * Defer committing asp_carveout_base/size until all validation passes + * so adata is never left with a non-zero base but zero group_count. + */ + group_bits = co_payload->mall_addr_valid & 0xFFFF; + first_bit = 0; + count = 0; + + if (group_bits) { + first_bit = __ffs(group_bits); + while ((group_bits >> (first_bit + count)) & 1) + count++; + /* Validate contiguous run - reject non-contiguous masks */ + if (group_bits != (((1u << count) - 1) << first_bit)) { + dev_warn(sdev->dev, + "ASP carveout: non-contiguous mall_addr_valid 0x%x unsupported\n", + group_bits); + ret = -ENODATA; + goto out_free; + } + } + if (!count) { + dev_warn(sdev->dev, "ASP carveout: no valid ATU groups in mall_addr_valid\n"); + ret = -ENODATA; + goto out_free; + } + adata->asp_carveout_base = co_payload->carveout_addr; + adata->asp_carveout_size = co_payload->carveout_size; + adata->asp_carveout_group_start = first_bit; + adata->asp_carveout_group_count = count; + + dev_dbg(sdev->dev, "ASP carveout: base=0x%llx size=0x%x grp_start=%u grp_count=%u\n", + adata->asp_carveout_base, adata->asp_carveout_size, + adata->asp_carveout_group_start, adata->asp_carveout_group_count); + +out_free: + adata->asp_mbox_buf = NULL; + adata->asp_mbox_buf_phys = 0; + acp7x_free_asp_payload(co_payload); + return ret; +} +EXPORT_SYMBOL_NS(acp7x_query_asp_carveout, "SND_SOC_SOF_AMD_COMMON"); + +int acp7x_load_firmware_carveout(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + struct acp_dev_data *adata = plat_data->hw_pdata; + struct asp_validate_image_payload *vi_payload; + const char *fw_filename; + struct page *fw_page; + phys_addr_t vi_phys; + u64 dest; + u32 page_count, dma_size; + u32 size_fw_signed; + unsigned int order; + int ret; + + /* + * Carveout query was already done in amd_sof_acp7x_probe() via + * acp7x_query_asp_carveout(). If ASP did not return a valid carveout + * address, fail immediately - no fallback to legacy path. + */ + + /* Program PTEs for the entire carveout region before any DMA into it. + * acp_init() in resume clears the scratch SRAM so PTEs must be reprogrammed + * on every boot including D3->D0 resume. + */ + ret = acp7x_configure_carveout_pte(sdev); + if (ret) + return ret; + + /* + * On D3->D0 resume the raw binary is already in the carveout region + * (placed by ASP during the previous VALIDATE_IMAGE call). Skip the + * filesystem load and VALIDATE_IMAGE - go directly to the data binary + * path. acp_dsp_pre_fw_run() will SHA-DMA from the preserved carveout + * address into DSP IRAM. + * + * On first boot fw_code_carveout_addr is 0 so the condition below is + * false and the full firmware load path executes. + */ + if (adata->fw_code_carveout_addr) + goto load_data_binary; + + fw_filename = kasprintf(GFP_KERNEL, "%s/%s", + plat_data->fw_filename_prefix, adata->fw_code_bin); + if (!fw_filename) + return -ENOMEM; + + /* Release any previously loaded firmware before requesting again */ + if (sdev->basefw.fw) { + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + } + + ret = request_firmware(&sdev->basefw.fw, fw_filename, sdev->dev); + kfree(fw_filename); + if (ret < 0) { + dev_err(sdev->dev, "request_firmware %s failed: %d\n", + adata->fw_code_bin, ret); + return ret; + } + + page_count = PAGE_ALIGN(sdev->basefw.fw->size) >> PAGE_SHIFT; + dma_size = page_count * ACP_PAGE_SIZE; + + /* + * Allocate the firmware staging buffer below 4 GB using alloc_pages so + * virt_to_phys() gives the real CPU physical address that ASP can read + * via the SoC fabric (bypassing the IOMMU). dma_alloc_coherent() gives + * an IOMMU-remapped DMA address which ASP cannot use. + * + * get_order() gives the smallest power-of-2 number of pages covering + * dma_size; alloc_pages() allocates that contiguous range. + */ + order = get_order(dma_size); + fw_page = alloc_pages(GFP_KERNEL | __GFP_DMA32 | __GFP_ZERO, order); + if (!fw_page) + fw_page = alloc_pages(GFP_KERNEL | __GFP_DMA | __GFP_ZERO, order); + if (!fw_page) { + dev_err(sdev->dev, "failed to allocate FW staging buffer\n"); + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return -ENOMEM; + } + adata->bin_buf = page_address(fw_page); + adata->sha_dma_addr = page_to_phys(fw_page); + + if (adata->sha_dma_addr > 0xFFFFFFFFULL) { + dev_err(sdev->dev, + "FW staging buffer at phys=0x%llx exceeds 4 GB\n", + (u64)adata->sha_dma_addr); + __free_pages(fw_page, order); + adata->bin_buf = NULL; + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return -ENOMEM; + } + + memcpy(adata->bin_buf, sdev->basefw.fw->data, sdev->basefw.fw->size); + adata->fw_bin_size = sdev->basefw.fw->size; + + if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { + dev_err(sdev->dev, "signed FW too small: %u\n", adata->fw_bin_size); + ret = -EINVAL; + goto free_bin; + } + /* + * SizeFWSigned (header[0x14]) is the size of the signed payload + * including the ASP signature trailer. ASP strips the ACP image + * header (0x100 bytes) but preserves the signature, so the raw + * binary deposited in carveout has size = SizeFWSigned and the + * SHA DMA length must equal SizeFWSigned. + */ + size_fw_signed = get_unaligned_le32(adata->bin_buf + ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF); + + if (size_fw_signed <= ACP_ASP_SIGNATURE_LENGTH || + size_fw_signed > adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE) { + dev_err(sdev->dev, + "invalid SizeFWSigned 0x%x in carveout firmware header\n", + size_fw_signed); + ret = -EINVAL; + goto free_bin; + } + adata->fw_code_raw_size = size_fw_signed; + vi_payload = acp7x_alloc_asp_payload(sdev, &vi_phys); + if (!vi_payload) { + ret = -ENOMEM; + goto free_bin; + } + + vi_payload->cookie = ASP_MBOX_COOKIE; + vi_payload->dest_offset = 0; + /* + * ASP bypasses the CPU IOMMU and accesses memory via the SoC + * fabric directly, so it must be given a real CPU physical + * address for the staging buffer (not an IOMMU/DMA-translated + * address). Use virt_to_phys(bin_buf) which returns the real + * CPU physical address, same as for mailbox payload buffers. + */ + vi_payload->src_phys_addr = (u64)virt_to_phys(adata->bin_buf); + vi_payload->fw_image_size = adata->fw_bin_size; + + adata->asp_mbox_buf = vi_payload; + adata->asp_mbox_buf_phys = vi_phys; + + dev_dbg(sdev->dev, "sending VALIDATE_IMAGE(0x01): src=0x%llx size=0x%x dest_offset=0x%llx phys=0x%llx\n", + vi_payload->src_phys_addr, vi_payload->fw_image_size, + vi_payload->dest_offset, (u64)adata->asp_mbox_buf_phys); + + ret = acp7x_asp_send_cmd(sdev, ASP_MBOX_CMD_VALIDATE_IMAGE); + + /* Read result before freeing payload */ + if (!ret && !vi_payload->status) { + dest = vi_payload->carveout_dest_addr; + + if (dest < adata->asp_carveout_base || + dest - adata->asp_carveout_base + adata->fw_code_raw_size > + adata->asp_carveout_size) { + dev_err(sdev->dev, + "VALIDATE_IMAGE: dest 0x%llx outside carveout window\n", + dest); + ret = -EIO; + } else { + adata->fw_code_carveout_addr = dest; + } + } else if (!ret && vi_payload->status) { + ret = -EIO; + } + + adata->asp_mbox_buf = NULL; + adata->asp_mbox_buf_phys = 0; + acp7x_free_asp_payload(vi_payload); + + if (ret) { + if (ret == -EIO) + dev_err(sdev->dev, "ASP VALIDATE_IMAGE failed\n"); + goto free_bin; + } + + dev_dbg(sdev->dev, "ASP VALIDATE_IMAGE: raw binary at carveout 0x%llx size 0x%x\n", + adata->fw_code_carveout_addr, adata->fw_code_raw_size); + + /* DDR staging buffer is no longer needed - raw binary is in carveout */ + free_pages((unsigned long)adata->bin_buf, get_order(dma_size)); + adata->bin_buf = NULL; + +load_data_binary: + fw_filename = kasprintf(GFP_KERNEL, "%s/%s", + plat_data->fw_filename_prefix, adata->fw_data_bin); + if (!fw_filename) { + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return -ENOMEM; + } + + ret = request_firmware(&adata->fw_dbin, fw_filename, sdev->dev); + kfree(fw_filename); + if (ret < 0) { + dev_err(sdev->dev, "request_firmware %s failed: %d\n", + adata->fw_data_bin, ret); + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return ret; + } + + ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_SRAM, 0, + (void *)adata->fw_dbin->data, + adata->fw_dbin->size); + release_firmware(adata->fw_dbin); + adata->fw_dbin = NULL; + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return ret; + +free_bin: + free_pages((unsigned long)adata->bin_buf, get_order(dma_size)); + adata->bin_buf = NULL; + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return ret; +} +EXPORT_SYMBOL_NS(acp7x_load_firmware_carveout, "SND_SOC_SOF_AMD_COMMON"); + From ea83a1103b18ec094f9c99fcfff81e0033d53586 Mon Sep 17 00:00:00 2001 From: Zachary Huang Date: Thu, 8 Oct 2026 17:03:19 +0800 Subject: [PATCH 729/924] ASoC: es9356: add missing DAI rates and formats The ES9356 DAI definitions only describe the channel count. Since snd_soc_runtime_calc_hw() derives hw->rates and hw->formats by intersecting the capabilities of the CPU and codec DAIs, an empty mask on the codec side leaves the whole PCM with no usable configuration, and soc_hw_sanity_check() fails every open with -EINVAL: ASoC error (-22): es9356-sdp-aif1 <-> SDW0 Pin0 No matching rates es9356_sdw_pcm_hw_params() already programs 16 kHz, 44.1 kHz, 48 kHz and 96 kHz, so advertise that set, plus the formats used by the other SDCA codecs. Verified on a MECHREVO WUJIE 14SAH (AMD ACP6.3, ES9356 on SoundWire link 0): with this change the PCMs open and speaker playback, headset playback, internal DMIC capture and headset mic capture all work. Link: https://lore.kernel.org/linux-sound/20261007193038.2473068-1-mario.limonciello@amd.com/ Link: https://bugzilla.kernel.org/show_bug.cgi?id=222092 Assisted-by: LLM Signed-off-by: Zachary Huang Link: https://patch.msgid.link/179145019931.16888.9682263186729943710@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/es9356.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/sound/soc/codecs/es9356.c b/sound/soc/codecs/es9356.c index 80db0f2f4aef..b8b3ffc13fd5 100644 --- a/sound/soc/codecs/es9356.c +++ b/sound/soc/codecs/es9356.c @@ -26,6 +26,20 @@ #include #include "es9356.h" +/* + * The DAI stream capabilities were never filled in. snd_soc_runtime_calc_hw() + * intersects the capabilities of the CPU and codec DAIs to derive hw->rates + * and hw->formats, so an empty mask on the codec side leaves the PCM with no + * usable configuration and soc_hw_sanity_check() fails every open with -EINVAL + * ("No matching rates"). es9356_sdw_pcm_hw_params() already programs 16 kHz, + * 44.1 kHz, 48 kHz and 96 kHz, so advertise exactly that set, with the formats + * used by the other SDCA codecs in the tree. + */ +#define ES9356_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) +#define ES9356_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + struct es9356_sdw_priv { struct sdw_slave *slave; struct device *dev; @@ -617,6 +631,8 @@ static struct snd_soc_dai_driver es9356_sdw_dai[] = { .id = ES9356_DMIC, .capture = { .stream_name = "DP1 Capture", + .rates = ES9356_RATES, + .formats = ES9356_FORMATS, .channels_min = 1, .channels_max = 2, }, @@ -627,6 +643,8 @@ static struct snd_soc_dai_driver es9356_sdw_dai[] = { .id = ES9356_JACK_IN, .capture = { .stream_name = "DP2 Capture", + .rates = ES9356_RATES, + .formats = ES9356_FORMATS, .channels_min = 1, .channels_max = 2, }, @@ -637,6 +655,8 @@ static struct snd_soc_dai_driver es9356_sdw_dai[] = { .id = ES9356_AMP, .playback = { .stream_name = "DP3 Playback", + .rates = ES9356_RATES, + .formats = ES9356_FORMATS, .channels_min = 1, .channels_max = 2, }, @@ -647,6 +667,8 @@ static struct snd_soc_dai_driver es9356_sdw_dai[] = { .id = ES9356_JACK_OUT, .playback = { .stream_name = "DP4 Playback", + .rates = ES9356_RATES, + .formats = ES9356_FORMATS, .channels_min = 1, .channels_max = 2, }, From e913917febd31998e3d0b9e8c861e538a6185045 Mon Sep 17 00:00:00 2001 From: Priyanka Mani Date: Mon, 12 Oct 2026 05:31:19 +0000 Subject: [PATCH 730/924] ASoC: mediatek: mt8365: Fix out-of-bounds read in get_iir_coef() get_iir_coef() looks up a resampling coefficient set by reading an index "k" from iir_coef_tbl_matrix[][] and using it to index iir_coef_tbl_list[], which has 8 entries (valid indices 0..7). Before the lookup, "k" is only rejected when it is >= IIR_NO_NEED (11), or equal to IIR_RATIOVER (9) or IIR_INV_COEF (10). The value 8 is neither a sentinel nor a valid table index, so it falls through to the else branch and iir_coef_tbl_list[8] is read one element past the end of the array. The bogus coef pointer and count are then returned to mt8365_dai_set_config(), which dereferences coef. The current matrix never produces 8, so this cannot be triggered today, but the bounds check is wrong: it relies on the matrix contents instead of the size of the array it guards. Bound the lookup with the actual array size so only valid indices reach the table. This was found by Smatch: sound/soc/mediatek/mt8365/mt8365-dai-i2s.c:271 get_iir_coef() error: buffer overflow 'iir_coef_tbl_list' 8 <= 8 Fixes: 402bbb13a195 ("ASoC: mediatek: mt8365: Add I2S DAI support") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/67af3580-a907-403d-8bfd-76b5c304ab99@stanley.mountain/ Signed-off-by: Priyanka Mani Link: https://patch.msgid.link/403cc04bb103dff096edf72b99ae6868ee91f359.1791782072.git.priyankamani2100@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8365/mt8365-dai-i2s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8365/mt8365-dai-i2s.c b/sound/soc/mediatek/mt8365/mt8365-dai-i2s.c index a058973662b3..2e678f4aa4b1 100644 --- a/sound/soc/mediatek/mt8365/mt8365-dai-i2s.c +++ b/sound/soc/mediatek/mt8365/mt8365-dai-i2s.c @@ -267,7 +267,7 @@ static const u32 *get_iir_coef(unsigned int input_fs, if (k >= IIR_NO_NEED) { } else if (k == IIR_RATIOVER) { } else if (k == IIR_INV_COEF) { - } else { + } else if (k < ARRAY_SIZE(iir_coef_tbl_list)) { coef = iir_coef_tbl_list[k].coef; cnt = iir_coef_tbl_list[k].cnt; } From b3f17433a1eb9f89b79f8459d7661207abce0b9c Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Sun, 4 Oct 2026 18:23:46 +0800 Subject: [PATCH 731/924] ASoC: codecs: wcd937x: request watchdog IRQs without devm The watchdog IRQs are requested from the codec component probe and released from its remove callback. Use the same non-devm lifetime for both operations. Use request_threaded_irq() and keep free_irq() in the component remove callback. Return errors from each request and unwind only IRQs that were successfully requested, including failures later in component probe. Fixes: 9be3ec196da4 ("ASoC: codecs: wcd937x: add wcd937x codec driver") Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20261004102346.1203410-1-runyu.xiao@seu.edu.cn Signed-off-by: Mark Brown --- sound/soc/codecs/wcd937x.c | 44 +++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/sound/soc/codecs/wcd937x.c b/sound/soc/codecs/wcd937x.c index 0dd05604f5b8..7ffbb445f8ac 100644 --- a/sound/soc/codecs/wcd937x.c +++ b/sound/soc/codecs/wcd937x.c @@ -2536,23 +2536,23 @@ static int wcd937x_soc_codec_probe(struct snd_soc_component *component) WCD937X_IRQ_AUX_PDM_WD_INT); /* Request for watchdog interrupt */ - ret = devm_request_threaded_irq(dev, wcd937x->hphr_pdm_wd_int, NULL, wcd937x_wd_handle_irq, - IRQF_ONESHOT | IRQF_TRIGGER_RISING, - "HPHR PDM WDOG INT", wcd937x); + ret = request_threaded_irq(wcd937x->hphr_pdm_wd_int, NULL, wcd937x_wd_handle_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "HPHR PDM WDOG INT", wcd937x); if (ret) - dev_err(dev, "Failed to request HPHR watchdog interrupt (%d)\n", ret); + goto err_free_clsh; - ret = devm_request_threaded_irq(dev, wcd937x->hphl_pdm_wd_int, NULL, wcd937x_wd_handle_irq, - IRQF_ONESHOT | IRQF_TRIGGER_RISING, - "HPHL PDM WDOG INT", wcd937x); + ret = request_threaded_irq(wcd937x->hphl_pdm_wd_int, NULL, wcd937x_wd_handle_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "HPHL PDM WDOG INT", wcd937x); if (ret) - dev_err(dev, "Failed to request HPHL watchdog interrupt (%d)\n", ret); + goto err_free_hphr_irq; - ret = devm_request_threaded_irq(dev, wcd937x->aux_pdm_wd_int, NULL, wcd937x_wd_handle_irq, - IRQF_ONESHOT | IRQF_TRIGGER_RISING, - "AUX PDM WDOG INT", wcd937x); + ret = request_threaded_irq(wcd937x->aux_pdm_wd_int, NULL, wcd937x_wd_handle_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "AUX PDM WDOG INT", wcd937x); if (ret) - dev_err(dev, "Failed to request Aux watchdog interrupt (%d)\n", ret); + goto err_free_hphl_irq; /* Disable watchdog interrupt for HPH and AUX */ disable_irq_nosync(wcd937x->hphr_pdm_wd_int); @@ -2564,24 +2564,34 @@ static int wcd937x_soc_codec_probe(struct snd_soc_component *component) ARRAY_SIZE(wcd9375_dapm_widgets)); if (ret < 0) { dev_err(component->dev, "Failed to add snd_ctls\n"); - wcd_clsh_ctrl_free(wcd937x->clsh_info); - return ret; + goto err_free_irqs; } ret = snd_soc_dapm_add_routes(dapm, wcd9375_audio_map, ARRAY_SIZE(wcd9375_audio_map)); if (ret < 0) { dev_err(component->dev, "Failed to add routes\n"); - wcd_clsh_ctrl_free(wcd937x->clsh_info); - return ret; + goto err_free_irqs; } } ret = wcd937x_mbhc_init(component); - if (ret) + if (ret) { dev_err(component->dev, "mbhc initialization failed\n"); + goto err_free_irqs; + } return ret; + +err_free_irqs: + free_irq(wcd937x->aux_pdm_wd_int, wcd937x); +err_free_hphl_irq: + free_irq(wcd937x->hphl_pdm_wd_int, wcd937x); +err_free_hphr_irq: + free_irq(wcd937x->hphr_pdm_wd_int, wcd937x); +err_free_clsh: + wcd_clsh_ctrl_free(wcd937x->clsh_info); + return ret; } static void wcd937x_soc_codec_remove(struct snd_soc_component *component) From a08e335c192d57153ffcc5baa44d9ed7a9d1b7ce Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 14 Oct 2026 07:51:17 +0200 Subject: [PATCH 732/924] Revert "ALSA: hda/tas2781: Enable bass speakers support" This reverts commit 3bc4fc135ec741c5657cba6b2300bf8bf81179d2. The problem will be addressed in a more proper way later. Signed-off-by: Takashi Iwai --- include/sound/tas2781.h | 12 --- .../hda/codecs/side-codecs/tas2781_hda_i2c.c | 98 ------------------- 2 files changed, 110 deletions(-) diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index ccc0142fbad5..b763da613769 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -91,18 +91,6 @@ /* prm_TE_1_Beta1 */ #define TAS2563_TE_DT_REG TASDEVICE_REG(0x00, 0x0f, 0x70) -/* TAS2563 ASI (Audio Serial Interface) Register Definitions */ -#define TAS2563_ASI_CFG_REG TASDEVICE_REG(0x00, 0x00, 0x06) -#define TAS2563_ASI_SR_REG TASDEVICE_REG(0x00, 0x00, 0x07) -#define TAS2563_ASI_FS_REG TASDEVICE_REG(0x00, 0x00, 0x08) -#define TAS2563_ASI_DA_REG TASDEVICE_REG(0x00, 0x00, 0x0a) -#define TAS2563_ASI_TDM_REG TASDEVICE_REG(0x00, 0x00, 0x0b) -#define TAS2563_ASI_DL_REG TASDEVICE_REG(0x00, 0x00, 0x0c) -#define TAS2563_ASI_SLOT_REG TASDEVICE_REG(0x00, 0x00, 0x30) - -/* TAS2563 ASI Apply Register (triggers configuration update) */ -#define TAS2563_ASI_APPLY_REG TASDEVICE_REG(0x00, 0x00, 0x02) - #define TAS2781_PRM_INT_MASK_REG TASDEVICE_REG(0x00, 0x00, 0x3b) #define TAS2781_PRM_CLK_CFG_REG TASDEVICE_REG(0x00, 0x00, 0x5c) #define TAS2781_PRM_RSVD_REG TASDEVICE_REG(0x00, 0x01, 0x19) diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index cef33159eba4..5e5f46c9fb6b 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -46,83 +46,6 @@ #define TAS2563_CAL_TLIM TASDEVICE_REG(0, 0x10, 0x14) #define TAS2563_CAL_R0 TASDEVICE_REG(0, 0x0f, 0x34) -/* - * TAS2563 Post-Firmware ASI Configuration. - * - * The DSP firmware loading can overwrite the ASI (Audio Serial Interface) - * registers that configure the I2S/TDM audio path to the amplifier. - * This function re-applies the correct ASI configuration after firmware load, - * matching the reference initialization sequence used by the vendor script: - * - Software reset - * - ASI_CFG = 0x09 (I2S mode, master/slave config) - * - ASI_SR = 0x02 (sample rate divider for 48 kHz) - * - ASI_DA = 0xf1 (data alignment / slot assignment) - * - ASI_TDM = 0x02 (TDM mode select) - * - ASI_DL = 0x00 (delay line off) - * - ASI_SLOT = 0x99 (slot enable mask for 2-channel stereo) - * - CHNL_0 = 0x02 (channel mapping) - * - ASI_FS = 0x1e or 0x2e (frame sync, device-specific: bit4 selects - * which of the two devices is the "primary" for FS generation) - * - ASI_APPLY= 0x00 (trigger configuration update) - */ -static void tas2563_post_fw_init(struct tasdevice_priv *tas_priv) -{ - int i, ret; - - dev_info(tas_priv->dev, "%s: ndev=%d, chip_id=%d\n", - __func__, tas_priv->ndev, tas_priv->chip_id); - - for (i = 0; i < tas_priv->ndev; i++) { - struct tasdevice *tasdev = &tas_priv->tasdevice[i]; - u8 fs_val; - - dev_info(tas_priv->dev, - "%s: device[%d] dev_addr=0x%02x\n", - __func__, i, tasdev->dev_addr); - - /* Step 1: Software reset (matches script: 0x01 = 0x01) */ - ret = tasdevice_dev_write(tas_priv, i, TASDEVICE_REG_SWRESET, - TASDEVICE_REG_SWRESET_RESET); - dev_info(tas_priv->dev, - "%s: chn=%d SWRESET ret=%d\n", __func__, i, ret); - usleep_range(1000, 2000); - - /* Step 2: ASI configuration registers */ - ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_CFG_REG, 0x09); - dev_info(tas_priv->dev, "%s: chn=%d ASI_CFG=0x09 ret=%d\n", __func__, i, ret); - - ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_SR_REG, 0x02); - dev_info(tas_priv->dev, "%s: chn=%d ASI_SR=0x02 ret=%d\n", __func__, i, ret); - - ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_DA_REG, 0xf1); - dev_info(tas_priv->dev, "%s: chn=%d ASI_DA=0xf1 ret=%d\n", __func__, i, ret); - - ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_TDM_REG, 0x02); - dev_info(tas_priv->dev, "%s: chn=%d ASI_TDM=0x02 ret=%d\n", __func__, i, ret); - - ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_DL_REG, 0x00); - dev_info(tas_priv->dev, "%s: chn=%d ASI_DL=0x00 ret=%d\n", __func__, i, ret); - - ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_SLOT_REG, 0x99); - dev_info(tas_priv->dev, "%s: chn=%d ASI_SLOT=0x99 ret=%d\n", __func__, i, ret); - - ret = tasdevice_dev_write(tas_priv, i, TAS2781_PRM_CHNL_0_REG, 0x02); - dev_info(tas_priv->dev, "%s: chn=%d CHNL_0=0x02 ret=%d\n", __func__, i, ret); - - /* Step 3: Device-specific frame sync (0x1e for dev0, 0x2e for dev1) */ - fs_val = 0x1e | ((i & 0x01) << 4); - ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_FS_REG, fs_val); - dev_info(tas_priv->dev, "%s: chn=%d ASI_FS=0x%02x ret=%d\n", - __func__, i, fs_val, ret); - - /* Step 4: Apply ASI configuration */ - ret = tasdevice_dev_write(tas_priv, i, TAS2563_ASI_APPLY_REG, 0x00); - dev_info(tas_priv->dev, "%s: chn=%d ASI_APPLY ret=%d\n", __func__, i, ret); - - usleep_range(1000, 2000); - } -} - enum device_chip_id { HDA_TAS2563, HDA_TAS2770, @@ -249,13 +172,6 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) pm_runtime_get_sync(dev); scoped_guard(mutex, &tas_hda->priv->codec_lock) { tasdevice_tuning_switch(tas_hda->priv, 0, false); - /* Re-apply ASI configuration for TAS2563 after - * tuning switch - DSP firmware loading can - * overwrite ASI registers. - */ - if (tas_hda->priv->chip_id == TAS2563) - tas2563_post_fw_init(tas_hda->priv); - tas_hda->priv->playback_started = true; } break; @@ -559,12 +475,6 @@ static void tasdevice_dspfw_init(void *context) tas_priv->rcabin.init_profile_id, TASDEVICE_BIN_BLK_PRE_POWER_UP); - /* Apply ASI configuration for TAS2563 after firmware load. - * DSP firmware loading can overwrite ASI registers. - */ - if (tas_hda->priv->chip_id == TAS2563) - tas2563_post_fw_init(tas_hda->priv); - /* If calibrated data occurs error, dsp will still works with default * calibrated data inside algo. */ @@ -749,7 +659,6 @@ static int tas2781_hda_i2c_probe(struct i2c_client *clt) */ device_name = "INT8866"; hda_priv->hda_chip_id = HDA_TAS2563; - tas_hda->priv->chip_id = TAS2563; hda_priv->save_calibration = tas2563_save_calibration; tas_hda->priv->global_addr = TAS2563_GLOBAL_ADDR; } else if (strstarts(dev_name(&clt->dev), "i2c-TXNW5825")) { @@ -873,13 +782,6 @@ static int tas2781_system_resume(struct device *dev) tas_hda->priv->rcabin.init_profile_id, TASDEVICE_BIN_BLK_PRE_POWER_UP); - /* Re-apply ASI configuration for TAS2563 after system sleep - * resume — amplifiers may lose their register state during - * suspend-to-ram or suspend-to-disk regardless of playback state. - */ - if (tas_hda->priv->chip_id == TAS2563) - tas2563_post_fw_init(tas_hda->priv); - if (tas_hda->priv->playback_started) tasdevice_tuning_switch(tas_hda->priv, 0, false); From 35911ab70c41957b4a8ac972af137a8e5ccbe259 Mon Sep 17 00:00:00 2001 From: Gergo Koteles Date: Tue, 13 Oct 2026 15:02:20 +0200 Subject: [PATCH 733/924] ALSA: hda/tas2781: Assign total_sz at the end of the function In the tas2563_save_calibration() function, cd->total_sz is assigned in several error paths. Move the assignment to the end of the function. Fixes: b6016332b889 ("ALSA: hda/tas2781: clear cali_data.total_sz when calibration read fails") Cc: stable@vger.kernel.org # 6.18 Signed-off-by: Gergo Koteles Tested-by: Philipp Oster Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/4b357778dce1b70a647e7e47b5ab3f0a6b2fc0cc.1791895478.git.soyer@irl.hu --- sound/hda/codecs/side-codecs/tas2781_hda_i2c.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index 5e5f46c9fb6b..cff8fab8133a 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -335,10 +335,11 @@ static int tas2563_save_calibration(struct tas2781_hda *h) } cd->cali_dat_sz_per_dev = TAS2563_CAL_DATA_SIZE * TASDEV_CALIB_N; + cd->total_sz = 0; /* extra byte for each device is the device number */ - cd->total_sz = (cd->cali_dat_sz_per_dev + 1) * p->ndev; - data = cd->data = devm_kzalloc(p->dev, cd->total_sz, + data = cd->data = devm_kzalloc(p->dev, + (cd->cali_dat_sz_per_dev + 1) * p->ndev, GFP_KERNEL); if (!data) return -ENOMEM; @@ -352,7 +353,6 @@ static int tas2563_save_calibration(struct tas2781_hda *h) if (ret < 0 || ret >= sizeof(var8) - 1) { dev_err(p->dev, "%s: Read %s failed\n", __func__, var8); - cd->total_sz = 0; return -EINVAL; } /* @@ -370,7 +370,6 @@ static int tas2563_save_calibration(struct tas2781_hda *h) dev_warn(p->dev, "Dev %d: Caldat[%d] read failed %ld\n", i, j, status); - cd->total_sz = 0; return -EINVAL; } bedata = cpu_to_be32(*(uint32_t *)&data[offset]); @@ -379,12 +378,7 @@ static int tas2563_save_calibration(struct tas2781_hda *h) } } - if (cd->total_sz != offset) { - dev_err(p->dev, "%s: tot_size(%lu) and offset(%u) mismatch\n", - __func__, cd->total_sz, offset); - cd->total_sz = 0; - return -EINVAL; - } + cd->total_sz = offset; r->r0_reg = TAS2563_CAL_R0; r->invr0_reg = TAS2563_CAL_INVR0; From a0f0325e92e505db6a6879d79da5aa88e94a36ab Mon Sep 17 00:00:00 2001 From: Gergo Koteles Date: Tue, 13 Oct 2026 15:02:21 +0200 Subject: [PATCH 734/924] ALSA: hda/tas2781: Skip invalid TAS2563 UEFI calibration data Some Lenovo 14ARB7 laptops do not have proper calibration data. Skip these zero calibration values and fall back to the calibration data provided by the DSP firmware. Tested-by: Georg Wenzel Fixes: c3ca4458cc2f ("ALSA: hda/tas2781: add TAS2563 support for 14ARB7") Cc: stable@vger.kernel.org # 6.18 Signed-off-by: Gergo Koteles Tested-by: Philipp Oster Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/12a1ab265a1dc0e3a062a54287c8ef48682c4efc.1791895478.git.soyer@irl.hu --- sound/hda/codecs/side-codecs/tas2781_hda_i2c.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index cff8fab8133a..44855f593daf 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -372,6 +372,11 @@ static int tas2563_save_calibration(struct tas2781_hda *h) i, j, status); return -EINVAL; } + if (*(uint32_t *)&data[offset] == 0) { + dev_warn(p->dev, + "Dev %d: Caldat[%d] is zero\n", i, j); + return -EINVAL; + } bedata = cpu_to_be32(*(uint32_t *)&data[offset]); memcpy(&data[offset], &bedata, sizeof(bedata)); offset += TAS2563_CAL_DATA_SIZE; From 8ff1161ac5aa02f3261e00f07d6212be2b12384b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amadeusz=20S=C5=82awi=C5=84ski?= Date: Tue, 13 Oct 2026 18:42:56 +0200 Subject: [PATCH 735/924] ALSA: pcm: Fix driver_tstamp value reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In some cases (system32 and compat64) due to incorrect assignment wrong value is being reported. Fix it. Fixes: 3ddee7f88aaf ("ALSA: Avoid using timespec for struct snd_pcm_status") Signed-off-by: Amadeusz SÅ‚awiÅ„ski Link: https://patch.msgid.link/20261013164256.4949-1-amade@asmblr.net Signed-off-by: Takashi Iwai --- sound/core/pcm_compat.c | 4 ++-- sound/core/pcm_native.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c index 55ecf87586c4..359197aa6076 100644 --- a/sound/core/pcm_compat.c +++ b/sound/core/pcm_compat.c @@ -219,8 +219,8 @@ static int snd_pcm_status_user_compat64(struct snd_pcm_substream *substream, .audio_tstamp_data = status.audio_tstamp_data, .audio_tstamp_sec = status.audio_tstamp_sec, .audio_tstamp_nsec = status.audio_tstamp_nsec, - .driver_tstamp_sec = status.audio_tstamp_sec, - .driver_tstamp_nsec = status.audio_tstamp_nsec, + .driver_tstamp_sec = status.driver_tstamp_sec, + .driver_tstamp_nsec = status.driver_tstamp_nsec, .audio_tstamp_accuracy = status.audio_tstamp_accuracy, }; diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index f774358629bb..fcd3594e4bdf 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1206,8 +1206,8 @@ static int snd_pcm_status_user32(struct snd_pcm_substream *substream, .audio_tstamp_data = status64.audio_tstamp_data, .audio_tstamp_sec = status64.audio_tstamp_sec, .audio_tstamp_nsec = status64.audio_tstamp_nsec, - .driver_tstamp_sec = status64.audio_tstamp_sec, - .driver_tstamp_nsec = status64.audio_tstamp_nsec, + .driver_tstamp_sec = status64.driver_tstamp_sec, + .driver_tstamp_nsec = status64.driver_tstamp_nsec, .audio_tstamp_accuracy = status64.audio_tstamp_accuracy, }; From f421be924fec67f74bb0b6988ea8f0a3480df2a7 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 13 Oct 2026 14:05:22 +0200 Subject: [PATCH 736/924] ALSA: jack: Fix kctl free at error in snd_jack_kctl_new() When an error happens in snd_jack_kctl_new(), it tries to free the new kcontrol element via snd_ctl_free_one(). But snd_ctl_free_one() releases only the memories, and the internal linked list and hash entries still remain, which may lead to bogus accesses to user. Replace with the correct destructor, snd_ctl_remove(), for clearing the whole resources of the given kcontrol, instead. Fixes: 9058cbe1eed2 ("ALSA: jack: implement kctl creating for jack devices") Reported-by: Sashiko Link: https://patch.msgid.link/20261013120528.52586-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/jack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/jack.c b/sound/core/jack.c index 5cc9c42de981..d9132b885ea3 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -453,7 +453,7 @@ static struct snd_jack_kctl * snd_jack_kctl_new(struct snd_card *card, const cha return jack_kctl; error: - snd_ctl_free_one(kctl); + snd_ctl_remove(card, kctl); return NULL; } From d5096bb9beacb448091c1c9541432021dd5c1fcd Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 13 Oct 2026 14:05:24 +0200 Subject: [PATCH 737/924] ALSA: pcm: Protect delay DMA sync in forward and rewind, too Like the previous fix for snd_pcm_delay() (in the commit 50f413b83b31 "ALSA: pcm: Protect delay DMA sync against concurrent HW_FREE"), the snd_pcm_dma_buffer_sync() call in snd_pcm_forward() and snd_pcm_rewind() needs the similar protection against the concurrent hw_free action for avoiding potential UAFs. Fixes: a25684a95646 ("ALSA: memalloc: Support for non-contiguous page allocation") Reported-by: Sashiko Link: https://patch.msgid.link/20261013120528.52586-4-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index fcd3594e4bdf..a845f242a163 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -3078,11 +3078,15 @@ static snd_pcm_sframes_t rewind_appl_ptr(struct snd_pcm_substream *substream, static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream, snd_pcm_uframes_t frames) { + struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_sframes_t ret; if (frames == 0) return 0; + /* Keep the DMA buffer alive through the sync below. */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; scoped_guard(pcm_stream_lock_irq, substream) { ret = do_pcm_hwsync(substream); if (!ret) @@ -3091,17 +3095,22 @@ static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream, } if (ret >= 0) snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + atomic_dec(&runtime->buffer_accessing); return ret; } static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, snd_pcm_uframes_t frames) { + struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_sframes_t ret; if (frames == 0) return 0; + /* Keep the DMA buffer alive through the sync below. */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; scoped_guard(pcm_stream_lock_irq, substream) { ret = do_pcm_hwsync(substream); if (!ret) @@ -3110,6 +3119,7 @@ static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, } if (ret >= 0) snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + atomic_dec(&runtime->buffer_accessing); return ret; } From 9ec633573c1d162bfc82cae52d14c220c875612f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 13 Oct 2026 14:05:25 +0200 Subject: [PATCH 738/924] ALSA: line6: Use kzalloc() for PCM buffer allocation LINE6 driver allocates the PCM buffer via kmalloc(), and this might leave some bytes uninitialized that are out of the actual ring buffer size, while it's still exposed via mmap. For avoiding the leak of the uninitialized kernel memory, simply replace it with kzalloc(). Fixes: 705ececd1c60 ("Staging: add line6 usb driver") Reported-by: Sashiko Link: https://patch.msgid.link/20261013120528.52586-5-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/line6/pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index f30d82cc0fc1..d17baf7761c6 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -155,7 +155,7 @@ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, /* Invoked multiple times in a row so allocate once only */ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) { pstr->buffer = - kmalloc(array3_size(line6pcm->line6->iso_buffers, + kzalloc(array3_size(line6pcm->line6->iso_buffers, LINE6_ISO_PACKETS, pkt_size), GFP_KERNEL); if (!pstr->buffer) From b23012149ca8e7a9c7c72e93b7639ca2a282d6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Tue, 13 Oct 2026 17:42:19 -0300 Subject: [PATCH 739/924] ALSA: hda/realtek: keep headset mic on Acer SSID 0x1640 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The micmute-LED quirk for the Aspire A315-44P matches PCI SSID 1025:1640 before the Acer ALC256 pin fallback. The Aspire Go 15 AG15-42P shares that SSID and leaves nid 0x19 as 0x411111f0, so selecting the LED-only fixup drops the headset-mic pin remap that the fallback would have applied. Chain ALC2XX_FIXUP_HEADSET_MIC before the GPIO3 micmute LED on that SSID. Leave the Swift SFG16 entry (0x169a) unchanged. Tested on Acer Aspire AG15-42P (Callisto_BCU): PCI pick 1025:1640, Headset Mic=0x19, GPIO3 enabled with no physical micmute LED, speaker and capture paths functional. Fixes: d830631a128b ("ALSA: hda/realtek: Add micmute LED quirk for Acer Aspire A315-44P") Cc: stable@vger.kernel.org Signed-off-by: Iván Ezequiel Rodriguez Link: https://patch.msgid.link/20261013204219.9649-1-ivanrwcm25@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 92686e71dad1..542245156643 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4446,6 +4446,7 @@ enum { ALC2XX_FIXUP_HEADSET_MIC, ALC289_FIXUP_DELL_CS35L41_SPI_2, ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, + ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED, ALC256_FIXUP_HEADPHONE_AMP_VOL, ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX, ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX, @@ -7063,6 +7064,13 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc256_fixup_acer_sfg16_micmute_led, }, + [ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_acer_sfg16_micmute_led, + .chained = true, + .chained_before = true, + .chain_id = ALC2XX_FIXUP_HEADSET_MIC, + }, [ALC256_FIXUP_HEADPHONE_AMP_VOL] = { .type = HDA_FIXUP_FUNC, .v.func = alc256_decrease_headphone_amp_val, @@ -7422,7 +7430,13 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_COLDBOOT), SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + /* + * Shared SSID: A315-44P needs GPIO3 micmute LED; AG15-42P needs + * the headset pin remap (BIOS leaves 0x19 as 0x411111f0). One + * entry applies both. GPIO3 is unused on AG15. + */ + SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P / AG15-42P", + ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), From 6215cf3ff5b0ff1ca5ab66b79021f9e28b02eae1 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Wed, 14 Oct 2026 11:59:49 +0800 Subject: [PATCH 740/924] ALSA: vmaster: Serialize hook registration with control callbacks An HDA virtual-master hook must have its private argument initialized before a control callback can invoke it. snd_hda_add_vmaster_hook() installs the trampoline through snd_ctl_add_vmaster_hook(), which stores master->hook before master->hook_private_data without serialization. When a codec with a vmaster mute hook binds to an already registered card, snd_hda_gen_build_controls() publishes and initializes the Master Playback Switch before installing the hook. A native write that changes the switch can then run in the following order: 1. The codec bind path publishes and initializes the master control in __snd_hda_add_vmaster(), then calls snd_hda_add_vmaster_hook(). 2. The hook setter stores master->hook, but has not yet stored its private argument. 3. SNDRV_CTL_IOCTL_ELEM_WRITE takes controls_rwsem and calls master_put() while the setter remains between those stores. 4. master_put() sees the callback and invokes vmaster_hook() with a NULL argument. The trampoline dereferences it before the setter can complete publication. The write lock held by snd_ctl_elem_write() does not exclude the hook setter, so this can cause a NULL pointer dereference. Take the same controls_rwsem for writing inside snd_ctl_add_vmaster_hook() so the callback and its argument are installed together with respect to native control callbacks. Pass the card into the helper and update the HDA caller, keeping the lock in the ALSA core. Release it when registration returns, before the caller synchronizes the hook. KASAN report as below: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000001: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000008-0x000000000000000f] CPU: 1 UID: 0 PID: 499 Comm: alsa-vmaster-tr Not tainted 7.2.0-rc6-pmb-bt-functional-v1+ #1 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 RIP: 0010:vmaster_hook+0x35/0xb0 Code: f5 53 48 89 fb 48 83 ec 08 e8 57 f0 9a fc eb 50 e8 50 f0 9a fc 48 8d 7b 08 48 b8 00 00 00 00 00 fc ff df 48 89 fa 48 c1 ea 03 <80> 3c 02 00 75 59 48 8d 7b 10 48 8b 43 08 48 ba 00 00 00 00 00 fc RSP: 0018:ffff888117647cd0 EFLAGS: 00010212 RAX: dffffc0000000000 RBX: 0000000000000000 RCX: ffffffff84e9ff50 RDX: 0000000000000001 RSI: 0000000000000000 RDI: 0000000000000008 RBP: 0000000000000001 R08: 0000000000000001 R09: 0000000000000001 R10: ffffffff89427457 R11: ffff888115ad1d00 R12: ffffffff84e9ff30 R13: ffff888114943ca0 R14: 0000000000000000 R15: 0000000000000001 FS: 00007fe8167b3780(0000) GS:ffff88826b7e3000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000557c8c2842a8 CR3: 000000011579f005 CR4: 0000000000770ef0 PKRU: 55555554 Call Trace: master_put+0x20e/0x2b0 snd_ctl_elem_write+0x37f/0x5e0 snd_ctl_ioctl+0x997/0xdd0 ? __pfx_snd_ctl_ioctl+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? selinux_file_ioctl+0xaa/0x280 ? __pfx_snd_ctl_ioctl+0x10/0x10 __x64_sys_ioctl+0x163/0x1d0 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7fe8168c291b Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00 48 89 44 24 08 48 8d 44 24 20 48 89 44 24 10 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1c 48 8b 44 24 18 64 48 2b 04 25 28 00 00 RSP: 002b:00007fffab0cb150 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 0000000000000003 RCX: 00007fe8168c291b RDX: 00007fffab0cb310 RSI: 00000000c4c85513 RDI: 0000000000000003 RBP: 00007fffab0cb310 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001 R13: 00007fffab0cb1c0 R14: 0000000000000001 R15: 0000557c4f71fd10 Modules linked in: ---[ end trace 0000000000000000 ]--- Fixes: ee52e56e7b12 ("ALSA: hda - Fix mute-LED fixed mode") Signed-off-by: Cen Zhang Assisted-by: LLM Link: https://patch.msgid.link/pm-pmb-c2667fb9cc42fbb7943c-v4-b7bc2fe90e950accb3f7@gmail.com Signed-off-by: Takashi Iwai --- include/sound/control.h | 2 +- sound/core/vmaster.c | 7 ++++++- sound/hda/common/codec.c | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/include/sound/control.h b/include/sound/control.h index 37e9c545503e..8e24b668d796 100644 --- a/include/sound/control.h +++ b/include/sound/control.h @@ -289,7 +289,7 @@ snd_ctl_add_follower_uncached(struct snd_kcontrol *master, return _snd_ctl_add_follower(master, follower, SND_CTL_FOLLOWER_NEED_UPDATE); } -int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kctl, +int snd_ctl_add_vmaster_hook(struct snd_card *card, struct snd_kcontrol *kctl, void (*hook)(void *private_data, int), void *private_data); void snd_ctl_sync_vmaster(struct snd_kcontrol *kctl, bool hook_only); diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c index cd3dcaaf9f72..3d57182031d9 100644 --- a/sound/core/vmaster.c +++ b/sound/core/vmaster.c @@ -463,6 +463,7 @@ EXPORT_SYMBOL(snd_ctl_make_virtual_master); /** * snd_ctl_add_vmaster_hook - Add a hook to a vmaster control + * @card: the card instance * @kcontrol: vmaster kctl element * @hook: the hook function * @private_data: the private_data pointer to be saved @@ -470,13 +471,17 @@ EXPORT_SYMBOL(snd_ctl_make_virtual_master); * Adds the given hook to the vmaster control element so that it's called * at each time when the value is changed. * + * Takes the card's controls_rwsem lock internally. + * * Return: Zero. */ -int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kcontrol, +int snd_ctl_add_vmaster_hook(struct snd_card *card, struct snd_kcontrol *kcontrol, void (*hook)(void *private_data, int), void *private_data) { struct link_master *master = snd_kcontrol_chip(kcontrol); + + guard(rwsem_write)(&card->controls_rwsem); master->hook = hook; master->hook_private_data = private_data; return 0; diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 18838a45e6f7..31e5fad78b0f 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -2025,7 +2025,7 @@ int snd_hda_add_vmaster_hook(struct hda_codec *codec, if (!hook->hook || !hook->sw_kctl) return 0; hook->codec = codec; - snd_ctl_add_vmaster_hook(hook->sw_kctl, vmaster_hook, hook); + snd_ctl_add_vmaster_hook(codec->card, hook->sw_kctl, vmaster_hook, hook); return 0; } EXPORT_SYMBOL_GPL(snd_hda_add_vmaster_hook); From d6e4158d92936987d963040c8ae2657a328459b8 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Wed, 14 Oct 2026 12:02:50 +0800 Subject: [PATCH 741/924] ALSA: emux: Guard the OSS argument during port initialization The OSS argument must be non-NULL before reset_port_mode() writes its event_passing field. snd_emux_open_seq_oss() assigns p->oss_arg only after snd_emux_create_port() attaches the writable sequencer port, so this field is still NULL when the callback first becomes reachable. With sequencer OSS and an EMUX provider enabled, another sequencer client can learn the new address from a PORT_START announcement and send a direct SNDRV_SEQ_EVENT_OSS channel-mode command, with marker 0xfe and command byte 0x8a. The following ordering is possible: OSS opener Sequencer writer snd_emux_create_port() snd_seq_event_port_attach() publish port, announce receive PORT_START snd_seq_write() snd_emux_event_oss_input() reset_port_mode() write via NULL oss_arg p->oss_arg = arg The callback's write precedes the opener's assignment. The core's port reference keeps the private data allocated but does not wait for its initialization. The write therefore dereferences NULL and can panic the kernel, as shown by the report below. Take a single READ_ONCE() snapshot of oss_arg and update event_passing only when that snapshot is non-NULL. Pair it with WRITE_ONCE() in the open path, so the check and write use the same pointer despite concurrent initialization. The open path still sets the initial mode after assigning the argument, and subsequent channel-mode events retain their existing handling. KASAN report as below: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000003: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000018-0x000000000000001f] CPU: 2 UID: 0 PID: 495 Comm: fixture Tainted: G O 7.2.0-rc6-pmb-bt-functional-v1+ #1 PREEMPT(lazy) Tainted: [O]=OOT_MODULE Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 RIP: 0010:snd_emux_event_oss_input+0xa76/0xdd0 Code: fc ff df 48 c1 ea 03 80 3c 02 00 0f 85 e1 02 00 00 48 b8 00 00 00 00 00 fc ff df 48 8b 5d 78 48 8d 7b 18 48 89 fa 48 c1 ea 03 <0f> b6 04 02 84 c0 74 08 3c 03 0f 8e 40 03 00 00 c7 43 18 00 00 00 RSP: 0018:ffff88810a937888 EFLAGS: 00010216 RAX: dffffc0000000000 RBX: 0000000000000000 RCX: ffffffff84f391b2 RDX: 0000000000000003 RSI: 0000000000000000 RDI: 0000000000000018 RBP: ffff8881179fc000 R08: 0000000000000001 R09: 0000000000000001 R10: ffffffff89444257 R11: ffff888107dd3a00 R12: 1ffff11021526f17 R13: 0000000000000000 R14: 0000000000000001 R15: ffff888112cda000 FS: 00007fdd13c9f780(0000) GS:ffff8881fd826000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007fdd13e2f943 CR3: 00000001057ba005 CR4: 0000000000770ef0 PKRU: 55555554 Call Trace: ? __pfx_snd_emux_event_oss_input+0x10/0x10 ? snd_seq_port_use_ptr+0x40/0x170 ? srso_alias_return_thunk+0x5/0xfbef5 ? find_held_lock+0x2b/0x80 ? snd_seq_port_use_ptr+0x10c/0x170 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_release+0xc8/0x280 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_snd_emux_event_oss_input+0x10/0x10 __snd_seq_deliver_single_event+0x105/0x1a0 ? snd_seq_port_use_ptr+0x111/0x170 snd_seq_deliver_single_event+0x2fc/0x770 ? __pfx_snd_seq_deliver_single_event+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? add_lock_to_list+0x9d/0x1e0 ? srso_alias_return_thunk+0x5/0xfbef5 ? check_prev_add+0x34c/0xe80 snd_seq_deliver_event+0x45a/0x760 ? __pfx_snd_seq_deliver_event+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_acquire+0x17b/0x2f0 snd_seq_client_enqueue_event.constprop.0+0x278/0x430 ? __pfx_snd_seq_client_enqueue_event.constprop.0+0x10/0x10 ? __might_fault+0xdd/0x190 ? srso_alias_return_thunk+0x5/0xfbef5 ? __might_fault+0x14e/0x190 ? _raw_spin_unlock_irq+0x28/0x50 snd_seq_write+0x30a/0x5e0 ? __pfx_snd_seq_write+0x10/0x10 ? selinux_file_permission+0x122/0x570 ? srso_alias_return_thunk+0x5/0xfbef5 ? security_file_permission+0x26/0x80 vfs_write+0x25a/0x1010 ? __pfx_snd_seq_write+0x10/0x10 ? __pfx_vfs_write+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? trace_hardirqs_on+0x18/0x160 ? srso_alias_return_thunk+0x5/0xfbef5 ? __call_rcu_common.constprop.0+0x5c1/0xbd0 ? srso_alias_return_thunk+0x5/0xfbef5 ? __fput+0x5ba/0xa60 ksys_write+0x1b7/0x200 ? __pfx_ksys_write+0x10/0x10 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7fdd13d31687 Code: 48 89 fa 4c 89 df e8 58 b3 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff RSP: 002b:00007fff073e9f20 EFLAGS: 00000202 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 00007fdd13c9f780 RCX: 00007fdd13d31687 RDX: 000000000000001c RSI: 00007fff073e9fc0 RDI: 0000000000000003 RBP: 0000000000000003 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000202 R12: 0000561260c48082 R13: 0000561260c48011 R14: 0000561260c4809d R15: 0000000000000002 Modules linked in: snd_emux_validation(O) ---[ end trace 0000000000000000 ]--- RIP: 0010:snd_emux_event_oss_input+0xa76/0xdd0 Code: fc ff df 48 c1 ea 03 80 3c 02 00 0f 85 e1 02 00 00 48 b8 00 00 00 00 00 fc ff df 48 8b 5d 78 48 8d 7b 18 48 89 fa 48 c1 ea 03 <0f> b6 04 02 84 c0 74 08 3c 03 0f 8e 40 03 00 00 c7 43 18 00 00 00 RSP: 0018:ffff88810a937888 EFLAGS: 00010216 RAX: dffffc0000000000 RBX: 0000000000000000 RCX: ffffffff84f391b2 RDX: 0000000000000003 RSI: 0000000000000000 RDI: 0000000000000018 RBP: ffff8881179fc000 R08: 0000000000000001 R09: 0000000000000001 R10: ffffffff89444257 R11: ffff888107dd3a00 R12: 1ffff11021526f17 R13: 0000000000000000 R14: 0000000000000001 R15: ffff888112cda000 FS: 00007fdd13c9f780(0000) GS:ffff8881fd826000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007fdd13e2f943 CR3: 00000001057ba005 CR4: 0000000000770ef0 PKRU: 55555554 Kernel panic - not syncing: Fatal exception Kernel Offset: disabled Rebooting in 1 seconds.. Assisted-by: LLM Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-f8d6ccb28f196910444f-v2-c253bf022b6e954a7bdc@gmail.com Signed-off-by: Takashi Iwai --- sound/synth/emux/emux_oss.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sound/synth/emux/emux_oss.c b/sound/synth/emux/emux_oss.c index 1145c7956afd..30e353e7323d 100644 --- a/sound/synth/emux/emux_oss.c +++ b/sound/synth/emux/emux_oss.c @@ -124,7 +124,7 @@ snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure) arg->private_data = p; arg->addr.client = p->chset.client; arg->addr.port = p->chset.port; - p->oss_arg = arg; + WRITE_ONCE(p->oss_arg, arg); reset_port_mode(p, arg->seq_mode); @@ -141,16 +141,24 @@ snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure) static void reset_port_mode(struct snd_emux_port *port, int midi_mode) { + struct snd_seq_oss_arg *arg; + + /* OSS events can arrive before the open path has set oss_arg. */ + arg = READ_ONCE(port->oss_arg); + if (midi_mode) { port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_MIDI; port->drum_flags = DEFAULT_DRUM_FLAGS; port->volume_atten = 0; - port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_KEYPRESS; + if (arg) + arg->event_passing = + SNDRV_SEQ_OSS_PROCESS_KEYPRESS; } else { port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_SYNTH; port->drum_flags = 0; port->volume_atten = 32; - port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; + if (arg) + arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; } } From ab669ac2bc418a3ef1a1541ea6e1ef5dc8c87a3f Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Wed, 14 Oct 2026 12:06:52 +0800 Subject: [PATCH 742/924] ALSA: seq: oss: Prevent duplicate MIDI device teardown Each OSS MIDI alias and its coder must have one final-release owner. find_slot() pins the alias under register_lock, but snd_seq_oss_midi_check_exit_port() clears its slot in a separate critical section without checking whether another callback already removed it. The use counter protects active users, but draining it does not elect a single remover. This requires overlapping raw kernel-client port controls for the same client. snd_seq_kernel_client_ctl() does not take ioctl_mutex, and snd_seq_delete_port() releases ports_mutex before returning to the DELETE_PORT handler's PORT_EXIT broadcast. If the old deletion is delayed before that broadcast, a new port can reuse the vacant address. Its PORT_START finds and keeps the old alias. Deleting the replacement then produces a second PORT_EXIT for the same address. Non-atomic subscriber delivery holds a read rwsem, so the two receive_announce() callbacks can run concurrently: Old-port deletion Replacement-port controls ----------------- ------------------------- Unlink and drain old port Delay before PORT_EXIT Create port at the same address PORT_START keeps existing alias Delete port; PORT_EXIT pins mdev PORT_EXIT pins the same mdev Clear slot; drop pin Clear slot; drop pin Wait for users to drain Wait for users to drain Free coder; pause before kfree(mdev) Free coder again: read freed coder->buf Both callbacks can select the same alias before either clears its slot. After both drop their pins, both waits can return. The second call to snd_midi_event_free() can then read the freed coder's buffer pointer, as in the report below, and attempt to release the same objects twice. Check that the pinned alias still occupies its slot under register_lock before clearing it. A callback that finds it already removed drops its pin and returns. Only the callback that removes the alias drains users and frees the coder and record. The pin keeps the record alive through this check, and the wait remains outside the spinlock. KASAN report as below: ================================================================== BUG: KASAN: slab-use-after-free in snd_midi_event_free+0x53/0x60 Read of size 8 at addr ffff888107074518 by task alsa-seq-fixtur/492 CPU: 3 UID: 0 PID: 492 Comm: alsa-seq-fixtur Not tainted 7.2.0-rc6-pmb-bt-functional-v1+ #1 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 ? snd_midi_event_free+0x53/0x60 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x20d/0x410 ? snd_midi_event_free+0x53/0x60 kasan_report+0xe0/0x110 ? snd_midi_event_free+0x53/0x60 snd_midi_event_free+0x53/0x60 snd_seq_oss_midi_check_exit_port+0x1d8/0x370 receive_announce+0x1b8/0x370 ? __pfx_receive_announce+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __lock_acquire+0x466/0x2260 ? snd_seq_port_use_ptr+0x40/0x170 ? srso_alias_return_thunk+0x5/0xfbef5 ? find_held_lock+0x2b/0x80 ? snd_seq_port_use_ptr+0x10c/0x170 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_release+0xc8/0x280 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_receive_announce+0x10/0x10 __snd_seq_deliver_single_event+0x105/0x1a0 ? snd_seq_port_use_ptr+0x111/0x170 snd_seq_deliver_single_event+0x2fc/0x770 ? __pfx_snd_seq_deliver_single_event+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_down_read_nested+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? do_raw_read_unlock+0x49/0xe0 ? srso_alias_return_thunk+0x5/0xfbef5 ? _raw_read_unlock+0x23/0x40 snd_seq_deliver_event+0x351/0x760 ? __pfx_snd_seq_deliver_event+0x10/0x10 ? _raw_spin_unlock_irqrestore+0x44/0x60 ? trace_hardirqs_on+0x18/0x160 ? srso_alias_return_thunk+0x5/0xfbef5 ? _raw_spin_unlock_irqrestore+0x44/0x60 ? srso_alias_return_thunk+0x5/0xfbef5 snd_seq_kernel_client_dispatch+0x13f/0x170 snd_seq_system_broadcast+0x100/0x150 ? __pfx_snd_seq_system_broadcast+0x10/0x10 ? mark_held_locks+0x40/0x70 ? _raw_spin_unlock_irqrestore+0x44/0x60 ? srso_alias_return_thunk+0x5/0xfbef5 ? lockdep_hardirqs_on_prepare+0xea/0x1a0 ? srso_alias_return_thunk+0x5/0xfbef5 snd_seq_ioctl_delete_port+0x179/0x4b0 call_seq_client_ctl+0x90/0x110 snd_seq_kernel_client_ctl+0x8a/0x100 pmbd_case_delete_port+0xa6/0xf0 ? __pfx_pmbd_case_delete_port+0x10/0x10 ? __might_fault+0xdd/0x190 ? srso_alias_return_thunk+0x5/0xfbef5 ? __might_fault+0x14e/0x190 ? srso_alias_return_thunk+0x5/0xfbef5 ? strim+0x19c/0x200 pmbd_case_control_write+0x2c5/0x470 ? __pfx_pmbd_case_control_write+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __debugfs_file_get+0x1bd/0x7f0 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_acquire+0x17b/0x2f0 ? ksys_write+0x111/0x200 full_proxy_write+0x11f/0x180 vfs_write+0x25a/0x1010 ? __pfx_full_proxy_write+0x10/0x10 ? __pfx___mutex_lock+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_acquire+0x17b/0x2f0 ? __pfx_vfs_write+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? srso_alias_return_thunk+0x5/0xfbef5 ? __fget_files+0x1e1/0x370 ksys_write+0x111/0x200 ? __pfx_ksys_write+0x10/0x10 ? rseq_register.isra.0+0x1f3/0x2e0 ? srso_alias_return_thunk+0x5/0xfbef5 ? exit_to_user_mode_loop+0x3a5/0x570 ? srso_alias_return_thunk+0x5/0xfbef5 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f3532e4e9ee Code: 08 0f 85 f5 4b ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 80 00 00 00 00 48 83 ec 08 RSP: 002b:00007f3532dafe08 EFLAGS: 00000246 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 00007f3532db06c0 RCX: 00007f3532e4e9ee RDX: 000000000000000a RSI: 000055efbd747156 RDI: 0000000000000003 RBP: 000055efbd747156 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 000000000000000a R13: 0000000000000000 R14: 00007ffe7894e7f0 R15: 00007f35325b0000 Allocated by task 490: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_cache_noprof+0x251/0x610 snd_midi_event_new+0x74/0x210 snd_seq_oss_midi_check_new_port+0x329/0x610 receive_announce+0x277/0x370 __snd_seq_deliver_single_event+0x105/0x1a0 snd_seq_deliver_single_event+0x2fc/0x770 snd_seq_deliver_event+0x351/0x760 snd_seq_kernel_client_dispatch+0x13f/0x170 snd_seq_system_broadcast+0x100/0x150 snd_seq_ioctl_create_port+0x39a/0x9d0 call_seq_client_ctl+0x90/0x110 snd_seq_kernel_client_ctl+0x8a/0x100 pmbd_case_create_port+0xcc/0x160 pmbd_case_control_write+0x21d/0x470 full_proxy_write+0x11f/0x180 vfs_write+0x25a/0x1010 ksys_write+0x111/0x200 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 493: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x307/0x580 snd_midi_event_free+0x48/0x60 snd_seq_oss_midi_check_exit_port+0x1d8/0x370 receive_announce+0x1b8/0x370 __snd_seq_deliver_single_event+0x105/0x1a0 snd_seq_deliver_single_event+0x2fc/0x770 snd_seq_deliver_event+0x351/0x760 snd_seq_kernel_client_dispatch+0x13f/0x170 snd_seq_system_broadcast+0x100/0x150 snd_seq_ioctl_delete_port+0x179/0x4b0 call_seq_client_ctl+0x90/0x110 snd_seq_kernel_client_ctl+0x8a/0x100 pmbd_case_delete_port+0xa6/0xf0 pmbd_case_control_write+0x398/0x470 full_proxy_write+0x11f/0x180 vfs_write+0x25a/0x1010 ksys_write+0x111/0x200 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff888107074500 which belongs to the cache kmalloc-96 of size 96 The buggy address is located 24 bytes inside of freed 96-byte region [ffff888107074500, ffff888107074560) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x107074 flags: 0x200000000000000(node=0|zone=2) page_type: f5(slab) raw: 0200000000000000 ffff888100042280 dead000000000122 0000000000000000 raw: 0000000000000000 0000000000200020 00000000f5000000 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff888107074400: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ffff888107074480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc >ffff888107074500: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc ^ ffff888107074580: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc ffff888107074600: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc ================================================================== Assisted-by: LLM Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-7c5128efe2110a87b872-v2-c718183fd693e73d8e95@gmail.com Signed-off-by: Takashi Iwai --- sound/core/seq/oss/seq_oss_midi.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/core/seq/oss/seq_oss_midi.c b/sound/core/seq/oss/seq_oss_midi.c index 70f94df65144..b456bd37d87b 100644 --- a/sound/core/seq/oss/seq_oss_midi.c +++ b/sound/core/seq/oss/seq_oss_midi.c @@ -211,6 +211,11 @@ snd_seq_oss_midi_check_exit_port(int client, int port) mdev = find_slot(client, port); if (mdev) { scoped_guard(spinlock_irqsave, ®ister_lock) { + /* Only one callback may release the device. */ + if (midi_devs[mdev->seq_device] != mdev) { + snd_use_lock_free(&mdev->use_lock); + return 0; + } midi_devs[mdev->seq_device] = NULL; } snd_use_lock_free(&mdev->use_lock); From a68a688ce220f1b359bec53fa38d1504314dc70f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 14 Oct 2026 08:14:05 +0200 Subject: [PATCH 743/924] ALSA: usb-audio: Use fixed control index for us16x08 mixer elements The callbacks for us16x08 mixer elements assume that id.index points to the channel to be operated, and the assumption is correct for now. However, since the kctls are added via snd_usb_mixer_add_control() and it looks for the empty slot if the same control name is already present, in theory, the index can become more than the channel numbers (if a conflicting name really happens). Since the mixer code in us16x08 doesn't need the search for an empty slot but requires the fixed index, this patch changes slightly how to assign the kcontrols. Now, a new argument is added to snd_usb_mixer_add_list() in order to disable the empty slot search, and use it in us16x08's add_new_ctl() helper function, instead. Fixes: d2bb390a2081 ("ALSA: usb-audio: Tascam US-16x08 DSP mixer quirk") Reported-by: Sashiko Link: https://patch.msgid.link/20261014061406.162938-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 13 +++++++++---- sound/usb/mixer.h | 5 +++-- sound/usb/mixer_quirks.c | 2 +- sound/usb/mixer_us16x08.c | 3 ++- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 49a8a26f1a96..a95f26a3cfa7 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -649,20 +649,25 @@ static int check_matrix_bitmap(unsigned char *bmap, /* * add an alsa control element - * search and increment the index until an empty slot is found. + * when @look_for_empty is set, search and increment the index until an empty + * slot is found. * * if failed, give up and free the control instance. */ int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, struct snd_kcontrol *kctl, - bool is_std_info) + bool is_std_info, + bool look_for_empty) { struct usb_mixer_interface *mixer = list->mixer; int err; - while (snd_ctl_find_id(mixer->chip->card, &kctl->id)) - kctl->id.index++; + if (look_for_empty) { + while (snd_ctl_find_id(mixer->chip->card, &kctl->id)) + kctl->id.index++; + } + err = snd_ctl_add(mixer->chip->card, kctl); if (err < 0) { usb_audio_dbg(mixer->chip, "cannot add control (err = %d)\n", diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 0d0a8b343756..c40f832852be 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -113,10 +113,11 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, struct snd_kcontrol *kctl, - bool is_std_info); + bool is_std_info, + bool look_for_empty); #define snd_usb_mixer_add_control(list, kctl) \ - snd_usb_mixer_add_list(list, kctl, true) + snd_usb_mixer_add_list(list, kctl, true, true) void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list, struct usb_mixer_interface *mixer, diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 1e266fdbbb8e..01210cc3fae7 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -167,7 +167,7 @@ static int add_single_ctl_with_resume(struct usb_mixer_interface *mixer, } kctl->private_free = snd_usb_mixer_elem_free; /* don't use snd_usb_mixer_add_control() here, this is a special list element */ - return snd_usb_mixer_add_list(list, kctl, false); + return snd_usb_mixer_add_list(list, kctl, false, true); } /* diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c index b447c4f7a161..18e8c2bd2678 100644 --- a/sound/usb/mixer_us16x08.c +++ b/sound/usb/mixer_us16x08.c @@ -1134,7 +1134,8 @@ static int add_new_ctl(struct usb_mixer_interface *mixer, strscpy(kctl->id.name, name, sizeof(kctl->id.name)); - err = snd_usb_mixer_add_control(&elem->head, kctl); + /* look_for_empty=false, as we want the fixed id.index as channel */ + err = snd_usb_mixer_add_list(&elem->head, kctl, true, false); if (err < 0) return err; From 9e4b51e048d2c402ae8954080bc45576d23a20dd Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Wed, 14 Oct 2026 12:55:05 +0530 Subject: [PATCH 744/924] ALSA: hda/realtek: Fix speaker and mic mute LEDs on IdeaPad Pro 5 14IAH10 The speaker and microphone mute LEDs on the IdeaPad Pro 5 14IAH10 do not follow the ALSA mute state. This machine has an ALC287 codec with SSID 17aa:3914, but shares PCI SSID 17aa:3843 with the Yoga Book 9i and currently gets its fixup. Add a codec SSID quirk that runs the IdeaPad ACPI LED fixup. Chain the existing bass speaker amp fixup so the audio setup is retained. Tested on the IdeaPad Pro 5 14IAH10: muting and unmuting the headphone sink toggled the physical speaker mute LED. Muting both microphone controls changed the ALSA mic LED state from 0 to 1. Signed-off-by: Aayush Kumar Link: https://patch.msgid.link/20261014072505.60358-1-aayushkmx@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 542245156643..aff1a1e40252 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4386,6 +4386,7 @@ enum { ALC295_FIXUP_HP_OMEN, ALC285_FIXUP_HP_SPECTRE_X360, ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP, + ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED, ALC623_FIXUP_LENOVO_THINKSTATION_P340, ALC255_FIXUP_ACER_HEADPHONE_AND_MIC, ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST, @@ -6611,6 +6612,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK, }, + [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_ideapad_acpi, + .chained = true, + .chain_id = ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP, + }, [ALC287_FIXUP_LENOVO_YOGA_PRO7] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_yoga_pro7_audio, @@ -8427,6 +8434,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3842, "ThinkBook X G2 IAH", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD), + /* IdeaPad Pro 5 14IAH10 shares PCI SSID 17aa:3843 with Yoga Book 9i; + * use codec SSID to distinguish them + */ + HDA_CODEC_QUIRK(0x17aa, 0x3914, "Lenovo IdeaPad Pro 5 14IAH10", + ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED), SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; * use codec SSID to distinguish them From 684c52dd270286dde0685e4bbfbd1b731c050de9 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Wed, 14 Oct 2026 17:14:45 +0530 Subject: [PATCH 745/924] ASoC: SOF: amd: fix trailing blank line at EOF in acp_asp_mailbox.c Remove the spurious blank line at the end of acp_asp_mailbox.c. Signed-off-by: Vijendar Mukunda Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp_asp_mailbox.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/sof/amd/acp_asp_mailbox.c b/sound/soc/sof/amd/acp_asp_mailbox.c index a9c0edfe0c3f..bc183814b973 100644 --- a/sound/soc/sof/amd/acp_asp_mailbox.c +++ b/sound/soc/sof/amd/acp_asp_mailbox.c @@ -556,4 +556,3 @@ int acp7x_load_firmware_carveout(struct snd_sof_dev *sdev) return ret; } EXPORT_SYMBOL_NS(acp7x_load_firmware_carveout, "SND_SOC_SOF_AMD_COMMON"); - From 3bf175cf275f89282897a872d5c9b7e81fd78e72 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Wed, 14 Oct 2026 17:14:46 +0530 Subject: [PATCH 746/924] ASoC: amd: acp: move ACP7X_PDM_DMIC0/1 to acp-mach-common.h ACP7X_PDM_DMIC0 and ACP7X_PDM_DMIC1 identify the active PDM controller on ACP7.B/7.F platforms. They are carried in amd_pdm_pdata.pdm_sel, which is defined in acp-mach-common.h, and in amd_mc_ctx.pdm_sel, which is defined in soc_amd_sdw_common.h. Move the constants to acp-mach-common.h so there is a single shared location for all PDM types and values, accessible to every AMD ACP machine driver (I2S, SoundWire, and SOF paths) without driver-specific header duplication. Remove the duplicate definition from sound/soc/sof/amd/acp.h. Signed-off-by: Vijendar Mukunda Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-mach-common.h | 4 ++++ sound/soc/sof/amd/acp.h | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sound/soc/amd/acp/acp-mach-common.h b/sound/soc/amd/acp/acp-mach-common.h index a2cb25be6bf8..c5f1bb41c826 100644 --- a/sound/soc/amd/acp/acp-mach-common.h +++ b/sound/soc/amd/acp/acp-mach-common.h @@ -9,6 +9,10 @@ #ifndef __ACP_MACH_COMMON_H #define __ACP_MACH_COMMON_H +/* ACP7X PDM controller selection values for pdm_sel; 0 = not set */ +#define ACP7X_PDM_DMIC0 1 +#define ACP7X_PDM_DMIC1 2 + /** * struct amd_pdm_pdata - PDM controller platform data passed via mach->pdata * @pdm_sel: active PDM controller (ACP7X_PDM_DMIC0 or ACP7X_PDM_DMIC1), diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 4eefc9cdba1b..b17a36b31ee0 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -159,9 +159,6 @@ /* ACPI child device address for the ACP7x PDM/DMIC device */ #define ACP7X_DMIC_ADDR 2 -/* ACP7X PDM controller selection values for acp_dev_data.pdm_sel; 0 = not set */ -#define ACP7X_PDM_DMIC0 1 -#define ACP7X_PDM_DMIC1 2 #define ACP_DSP_MSG_SET 1 #define ACP_DSP_ACK_SET 1 From fd65108f5001ffb416dc9bd37ec1893f55cff24a Mon Sep 17 00:00:00 2001 From: Denis Arefev Date: Wed, 14 Oct 2026 13:26:18 +0300 Subject: [PATCH 747/924] ALSA: cs46xx: fix unit mismatch in DSP code area bounds check ins->code.offset and code->size are measured in 32-bit words, while DSP_CODE_BYTE_SIZE is the size of the DSP code area in bytes. The check ins->code.offset + code->size > DSP_CODE_BYTE_SIZE in cs46xx_dsp_load_module() therefore allowed up to four times more code than the code area can hold. The shadow buffer ins->code.data is allocated with kmalloc(DSP_CODE_BYTE_SIZE), i.e. it holds DSP_CODE_BYTE_SIZE/4 words. A firmware file with a code segment larger than DSP_CODE_BYTE_SIZE/4 words (28 KiB) passed the check and caused an out-of-bounds write in shadow_and_reallocate_code() and an out-of-bounds read in snd_cs46xx_download(). Compare the sum in words against DSP_CODE_BYTE_SIZE/4 and cast it to u64 so that the sum cannot wrap for large code->size values. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Denis Arefev Link: https://patch.msgid.link/20261014102619.16415-1-arefev@swemel.ru Signed-off-by: Takashi Iwai --- sound/pci/cs46xx/dsp_spos.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/cs46xx/dsp_spos.c b/sound/pci/cs46xx/dsp_spos.c index 6fd4f629d1d5..a77e6f5a14ad 100644 --- a/sound/pci/cs46xx/dsp_spos.c +++ b/sound/pci/cs46xx/dsp_spos.c @@ -393,7 +393,7 @@ int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * m dev_dbg(chip->card->dev, "dsp_spos: module got no code segment\n"); } else { - if (ins->code.offset + code->size > DSP_CODE_BYTE_SIZE) { + if ((u64)ins->code.offset + code->size > DSP_CODE_BYTE_SIZE / 4) { dev_err(chip->card->dev, "dsp_spos: no space available in DSP\n"); return -ENOMEM; From d1e57f8fb7a6b6d32a4aaa31fd6fc4c77339608f Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Wed, 14 Oct 2026 18:52:15 +0800 Subject: [PATCH 748/924] ALSA: pcm: Unlink substreams before releasing them A closing PCM substream can remain in its linked group after the driver close callback has freed its private data. With two configured dummy capture substreams on the same PCM device, linked with LINK, PREPARE on the surviving substream can still visit the closing member: Final close of A PREPARE on linked B ---------------- ------------------- snd_pcm_drop(): group -> SETUP do_hw_free(A) dummy_pcm_close(A): free dpcm Take snd_pcm_link_rwsem for reading Walk the group, including A dummy_pcm_prepare(A) Read freed timer_ops pcm_release_private(A) snd_pcm_unlink(A) Unlink at the start of final substream release, before dropping the stream or releasing driver resources. The link write lock waits for an existing group PREPARE, and unlink removes the closing member from later group actions. Keep the existing stop synchronization in do_hw_free() before the driver resources are freed. Make unlink independent of the pcm_release callback so OSS substreams are covered too. Remove the native-only unlink callback and its registration, while retaining the callback hook for OSS cleanup. The existing KASAN report below records the freed timer_ops read in dummy_pcm_prepare(). KASAN report as below: BUG: KASAN: slab-use-after-free in dummy_pcm_prepare+0xb7/0xc0 Read of size 8 at addr ffff888106b35f00 by task alsa_pcm_fixtur/491 Call Trace: dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 ? dummy_pcm_prepare+0xb7/0xc0 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x20d/0x410 ? dummy_pcm_prepare+0xb7/0xc0 kasan_report+0xe0/0x110 ? dummy_pcm_prepare+0xb7/0xc0 dummy_pcm_prepare+0xb7/0xc0 snd_pcm_do_prepare+0x71/0xb0 snd_pcm_action_group+0x505/0xb30 snd_pcm_action_nonatomic+0xab/0x150 snd_pcm_common_ioctl+0x1bf5/0x3a50 ? srso_alias_return_thunk+0x5/0xfbef5 ? do_vfs_ioctl+0x1f5/0x1280 ? __pfx_do_vfs_ioctl+0x10/0x10 ? ioctl_has_perm.constprop.0.isra.0+0x34f/0x500 ? __pfx_snd_pcm_common_ioctl+0x10/0x10 ? __fput+0x5ba/0xa60 ? srso_alias_return_thunk+0x5/0xfbef5 snd_pcm_ioctl+0x76/0xb0 ? __pfx_snd_pcm_ioctl+0x10/0x10 __x64_sys_ioctl+0x163/0x1d0 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x41ee8d Code: 04 25 28 00 00 00 48 89 45 c8 31 c0 48 8d 45 10 c7 45 b0 10 00 00 00 48 89 45 b8 48 8d 45 d0 48 89 45 c0 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1a 48 8b 45 c8 64 48 2b 04 25 28 00 00 00 RSP: 002b:00007ffcfa72fb60 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 0000000000000004 RCX: 000000000041ee8d RDX: 0000000000000000 RSI: 0000000000004140 RDI: 0000000000000004 RBP: 00007ffcfa72fbb0 R08: 00007ffcfa72fb98 R09: 0000000000000110 R10: 0000000000000001 R11: 0000000000000246 R12: 00007ffcfa72fcd8 R13: 00007ffcfa72fcf0 R14: 00000000004a8f28 R15: 0000000000000001 Allocated by task 488: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_cache_noprof+0x251/0x610 dummy_hrtimer_create+0x4a/0x170 dummy_pcm_open+0xe7/0x730 snd_pcm_open_substream+0x926/0x1580 snd_pcm_open+0x28d/0x6d0 snd_pcm_capture_open+0x82/0xe0 snd_open+0x1ec/0x3e0 chrdev_open+0x21f/0x660 do_dentry_open+0x5c8/0x1300 vfs_open+0x87/0x390 path_openat+0x2460/0x3cd0 do_file_open+0x21e/0x470 do_sys_openat2+0xf4/0x1b0 __x64_sys_openat+0x136/0x1e0 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 490: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x307/0x580 dummy_hrtimer_free+0x72/0xe0 dummy_pcm_close+0x9f/0xd0 snd_pcm_release_substream.part.0+0x104/0x310 snd_pcm_release+0x106/0x240 __fput+0x39f/0xa60 fput_close_sync+0xff/0x200 __x64_sys_close+0x8c/0xf0 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f ================================================================== Fixes: 1576274d3028 ("[ALSA] Fix Oops of PCM OSS emulation") Suggested-by: Takashi Iwai Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-9c80f858ccc13b3de84d-v7-fcd8c7aa0e837bf0b036@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index a845f242a163..4fea3f6df682 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2792,18 +2792,14 @@ static int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream) return 0; } -static void pcm_release_private(struct snd_pcm_substream *substream) -{ - if (snd_pcm_stream_linked(substream)) - snd_pcm_unlink(substream); -} - void snd_pcm_release_substream(struct snd_pcm_substream *substream) { substream->ref_count--; if (substream->ref_count > 0) return; + snd_pcm_unlink(substream); + snd_pcm_drop(substream); if (substream->hw_opened) { if (substream->runtime->state != SNDRV_PCM_STATE_OPEN) @@ -2887,8 +2883,6 @@ static int snd_pcm_open_file(struct file *file, return -ENOMEM; } pcm_file->substream = substream; - if (substream->ref_count == 1) - substream->pcm_release = pcm_release_private; file->private_data = pcm_file; return 0; From ad28d2f607d1792c04531f2c5fd9ac0bf5bdcc9c Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Wed, 14 Oct 2026 18:52:33 +0800 Subject: [PATCH 749/924] ALSA: pcm: Serialize and check HW_REFINE ioctls Hardware refinement must reject disconnected streams before entering their runtime constraint rules. The native ioctl checks the state at the outer dispatch, before copying the parameters, while the compat HW_REFINE32 path only checks that the runtime exists. Both call the unlocked refinement helper without the buffer access lock used by HW_PARAMS. The BUFFER_BYTES rule keeps a substream pointer as its private payload. A compat request can still enter that rule after component disconnect. If component free overlaps its execution, the following order permits a use-after-free read of buffer_bytes_max: Component cleanup Compat HW_REFINE ----------------- ---------------- snd_pcm_dev_disconnect() Runtime -> DISCONNECTED Enter compat ioctl snd_pcm_hw_refine() Enter BUFFER_BYTES rule snd_device_free() snd_pcm_free_stream() Free substream Read buffer_bytes_max Add a wrapper that acquires the existing buffer access lock and checks the same allowed states and mmap conditions as HW_PARAMS before running the refinement rules. Share the state checker with snd_pcm_hw_params() and use the wrapper for native, compat and legacy PCM HW_REFINE ioctls. This rejects the described entry after disconnect and serializes these refinement calls with other buffer-locked parameter operations. Keep the original snd_pcm_hw_refine() helper for existing internal callers, including snd_pcm_hw_params() which already holds the buffer lock, to avoid locking that mutex recursively. KASAN report as below: BUG: KASAN: slab-use-after-free in snd_pcm_hw_rule_buffer_bytes_max+0x276/0x2a0 Read of size 8 at addr ffff888111b36870 by task pmbd_alsa_reade/486 CPU: 2 UID: 0 PID: 486 Comm: pmbd_alsa_reade Tainted: G O 7.2.0-rc6-pmb-bt-functional-v1+ #1 PREEMPT(lazy) Tainted: [O]=OOT_MODULE Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 ? snd_pcm_hw_rule_buffer_bytes_max+0x276/0x2a0 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x20d/0x410 ? snd_pcm_hw_rule_buffer_bytes_max+0x276/0x2a0 kasan_report+0xe0/0x110 ? snd_pcm_hw_rule_buffer_bytes_max+0x276/0x2a0 snd_pcm_hw_rule_buffer_bytes_max+0x276/0x2a0 ? snd_pcm_hw_rule_div+0x16b/0x200 ? __pfx_snd_pcm_hw_rule_buffer_bytes_max+0x10/0x10 ? constrain_params_by_rules+0x17a/0xfe0 constrain_params_by_rules+0x506/0xfe0 ? __pfx_constrain_params_by_rules+0x10/0x10 ? __ia32_compat_sys_ioctl+0x289/0x2f0 ? do_int80_emulation+0x14a/0x5e0 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_release+0xc8/0x280 ? srso_alias_return_thunk+0x5/0xfbef5 ? snd_interval_refine+0x284/0x470 snd_pcm_hw_refine+0x768/0xcc0 ? __pfx_snd_pcm_hw_refine+0x10/0x10 ? _copy_from_user+0x51/0xa0 snd_pcm_ioctl_hw_params_compat+0x138/0x280 snd_pcm_ioctl_compat+0x4c5/0x7c0 ? __pfx_snd_pcm_ioctl_compat+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? selinux_file_ioctl+0xaa/0x280 ? ksys_write+0x16b/0x200 ? __pfx_snd_pcm_ioctl_compat+0x10/0x10 __ia32_compat_sys_ioctl+0x289/0x2f0 ? srso_alias_return_thunk+0x5/0xfbef5 do_int80_emulation+0x14a/0x5e0 asm_int80_emulation+0x1a/0x20 RIP: 0023:0x8049084 Code: 80 b8 04 00 00 00 bb 01 00 00 00 b9 43 a0 04 08 ba 2b 00 00 00 cd 80 b8 36 00 00 00 89 f3 b9 10 41 5c c2 ba b8 b0 04 08 cd 80 <85> c0 78 6c b8 04 00 00 00 bb 01 00 00 00 b9 27 a0 04 08 ba 1c 00 RSP: 002b:00000000ffd35e40 EFLAGS: 00000246 ORIG_RAX: 0000000000000036 RAX: ffffffffffffffda RBX: 0000000000000003 RCX: 00000000c25c4110 RDX: 000000000804b0b8 RSI: 0000000000000003 RDI: 0000000000000004 RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000000 R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000 Allocated by task 486: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_cache_noprof+0x251/0x610 snd_pcm_new_stream+0x98f/0x1350 _snd_pcm_new+0x1d1/0x310 snd_pcm_new+0x43/0x60 0xffffffffc0401115 do_one_initcall+0xf3/0x4e0 do_init_module+0x281/0x8e0 load_module+0x658c/0x7cc0 init_module_from_file+0x15b/0x180 idempotent_init_module+0x22d/0x770 __x64_sys_finit_module+0xd2/0x160 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 494: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x307/0x580 snd_pcm_free_stream+0xcf/0x290 snd_pcm_free+0xb1/0xf0 __snd_device_free+0x1a1/0x490 snd_device_free+0xcf/0x190 case_owner_ioctl+0xb8/0x160 [pmbd_alsa_fixture] __x64_sys_ioctl+0x163/0x1d0 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff888111b36800 which belongs to the cache kmalloc-1k of size 1024 The buggy address is located 112 bytes inside of freed 1024-byte region [ffff888111b36800, ffff888111b36c00) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff888111b35800 pfn:0x111b30 head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0 flags: 0x200000000000240(workingset|head|node=0|zone=2) page_type: f5(slab) raw: 0200000000000240 ffff888100042dc0 ffffea0004218610 ffffea00042c6810 raw: ffff888111b35800 000000000010000f 00000000f5000000 0000000000000000 head: 0200000000000240 ffff888100042dc0 ffffea0004218610 ffffea00042c6810 head: ffff888111b35800 000000000010000f 00000000f5000000 0000000000000000 head: 0200000000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff head: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff888111b36700: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ffff888111b36780: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc >ffff888111b36800: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ^ ffff888111b36880: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ffff888111b36900: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ================================================================== Suggested-by: Takashi Iwai Signed-off-by: Cen Zhang Assisted-by: LLM Link: https://patch.msgid.link/pm-pmb-b3f7b3445bb11aaa9a7b-v3-371faafc2599ec7fa136@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_compat.c | 2 +- sound/core/pcm_native.c | 107 ++++++++++++++++++++++++++-------------- 2 files changed, 70 insertions(+), 39 deletions(-) diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c index 359197aa6076..c54cb07304ad 100644 --- a/sound/core/pcm_compat.c +++ b/sound/core/pcm_compat.c @@ -252,7 +252,7 @@ static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream, return -EFAULT; if (refine) { - err = snd_pcm_hw_refine(substream, data); + err = snd_pcm_hw_refine_with_lock(substream, data); if (err < 0) return err; err = fixup_unreferenced_params(substream, data); diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 4fea3f6df682..6c379068b54c 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -580,29 +580,6 @@ int snd_pcm_hw_refine(struct snd_pcm_substream *substream, } EXPORT_SYMBOL(snd_pcm_hw_refine); -static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params __user * _params) -{ - int err; - struct snd_pcm_hw_params *params __free(kfree) = - memdup_user(_params, sizeof(*params)); - - if (IS_ERR(params)) - return PTR_ERR(params); - - err = snd_pcm_hw_refine(substream, params); - if (err < 0) - return err; - - err = fixup_unreferenced_params(substream, params); - if (err < 0) - return err; - - if (copy_to_user(_params, params, sizeof(*params))) - return -EFAULT; - return 0; -} - static int period_to_usecs(struct snd_pcm_runtime *runtime) { int usecs; @@ -799,6 +776,22 @@ static void pcm_qos_req_update(struct snd_pcm_substream *substream, int usecs) usecs); } +static int hw_params_state_check(struct snd_pcm_substream *substream) +{ + guard(pcm_stream_lock_irq)(substream); + switch (substream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + if (!is_oss_stream(substream) && + atomic_read(&substream->mmap_count)) + return -EBADFD; + return 0; + default: + return -EBADFD; + } +} + static int snd_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -813,20 +806,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, err = snd_pcm_buffer_access_lock(runtime); if (err < 0) return err; - scoped_guard(pcm_stream_lock_irq, substream) { - switch (runtime->state) { - case SNDRV_PCM_STATE_OPEN: - case SNDRV_PCM_STATE_SETUP: - case SNDRV_PCM_STATE_PREPARED: - if (!is_oss_stream(substream) && - atomic_read(&substream->mmap_count)) - err = -EBADFD; - break; - default: - err = -EBADFD; - break; - } - } + err = hw_params_state_check(substream); if (err) goto unlock; @@ -947,6 +927,57 @@ static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream, return err; } +/* + * call snd_pcm_hw_refine() after buffer lock and sanity check; + * this one should be used for ioctls and other external paths instead of + * direct call of snd_pcm_hw_refine() + */ +static int snd_pcm_hw_refine_with_lock(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime; + int err; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + err = snd_pcm_buffer_access_lock(runtime); + if (err < 0) + return err; + err = hw_params_state_check(substream); + if (err) + goto unlock; + + err = snd_pcm_hw_refine(substream, params); + + unlock: + snd_pcm_buffer_access_unlock(runtime); + return err; +} + +static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params __user * _params) +{ + int err; + struct snd_pcm_hw_params *params __free(kfree) = + memdup_user(_params, sizeof(*params)); + + if (IS_ERR(params)) + return PTR_ERR(params); + + err = snd_pcm_hw_refine_with_lock(substream, params); + if (err < 0) + return err; + + err = fixup_unreferenced_params(substream, params); + if (err < 0) + return err; + + if (copy_to_user(_params, params, sizeof(*params))) + return -EFAULT; + return 0; +} + static int do_hw_free(struct snd_pcm_substream *substream) { int result = 0; @@ -4212,7 +4243,7 @@ static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream, if (IS_ERR(oparams)) return PTR_ERR(oparams); snd_pcm_hw_convert_from_old_params(params, oparams); - err = snd_pcm_hw_refine(substream, params); + err = snd_pcm_hw_refine_with_lock(substream, params); if (err < 0) return err; From 5b4d158b309a151979fce2334fec616d71506b77 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Wed, 14 Oct 2026 15:01:47 +0300 Subject: [PATCH 750/924] ASoC: dt-bindings: nvidia,tegra-audio: Document WM8994 CODEC Add dt-binding for WM8994 CODEC. Signed-off-by: Svyatoslav Ryhel Link: https://patch.msgid.link/20261014120148.126160-2-clamor95@gmail.com Signed-off-by: Mark Brown --- .../sound/nvidia,tegra-audio-wm8994.yaml | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml new file mode 100644 index 000000000000..d2c317f831ce --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml @@ -0,0 +1,110 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm8994.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra audio complex with WM8994 CODEC + +maintainers: + - Svyatoslav Ryhel + +allOf: + - $ref: nvidia,tegra-audio-common.yaml# + +properties: + compatible: + items: + - pattern: '^[a-z0-9]+,tegra-audio-wm8994(-[a-z0-9]+)+$' + - const: nvidia,tegra-audio-wm8994 + + nvidia,audio-routing: + $ref: /schemas/types.yaml#/definitions/non-unique-string-array + description: | + A list of the connections between audio components. + Each entry is a pair of strings, the first being the connection's sink, + the second being the connection's source. Valid names for sources and + sinks are the pins (documented in the binding document), + and the jacks on the board. + minItems: 2 + items: + enum: + # Board Connectors + - Headphone Jack + - Int Spk + - Earpiece + - Line Out + - Mic Jack + - Int Mic + - Internal Mic 1 + - Internal Mic 2 + - FM Radio + + # CODEC Pins + - SPKOUTLP + - SPKOUTLN + - SPKOUTRP + - SPKOUTRN + - HPOUT1L + - HPOUT1R + - HPOUT2P + - HPOUT2N + - LINEOUT1P + - LINEOUT1N + - LINEOUT2P + - LINEOUT2N + - IN1LN + - IN1LP + - IN2LN + - IN2LP:VXRN + - IN1RN + - IN1RP + - IN2RN + - IN2RP:VXRP + - DMIC1DAT + - DMIC2DAT + - MICBIAS1 + - MICBIAS2 + +required: + - nvidia,i2s-controller + +unevaluatedProperties: false + +examples: + - | + #include + #include + + sound { + compatible = "lg,tegra-audio-wm8994-star", + "nvidia,tegra-audio-wm8994"; + nvidia,model = "LG Optimus 2X WM8994"; + + nvidia,audio-routing = + "Headphone Jack", "HPOUT1L", + "Headphone Jack", "HPOUT1R", + "Int Spk", "SPKOUTLN", + "Int Spk", "SPKOUTLP", + "Earpiece", "HPOUT2N", + "Earpiece", "HPOUT2P", + "IN2RN", "MICBIAS2", + "IN2RN", "Mic Jack", + "IN1LN", "MICBIAS1", + "IN1LN", "Internal Mic 1", + "IN1RN", "MICBIAS1", + "IN1RN", "Internal Mic 2"; + + nvidia,i2s-controller = <&tegra_i2s1>; + nvidia,audio-codec = <&wm8994>; + + nvidia,hp-det-gpios = <&gpio 51 GPIO_ACTIVE_HIGH>; + nvidia,mic-det-gpios = <&gpio 27 GPIO_ACTIVE_HIGH>; + nvidia,ext-mic-en-gpios = <&gpio 57 GPIO_ACTIVE_HIGH>; + nvidia,coupled-mic-hp-det; + + clocks = <&tegra_car TEGRA20_CLK_PLL_A>, + <&tegra_car TEGRA20_CLK_PLL_A_OUT0>, + <&tegra_car TEGRA20_CLK_CDEV1>; + clock-names = "pll_a", "pll_a_out0", "mclk"; + }; From e38398539f79dddba1b695ae344aec811c63111a Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Wed, 14 Oct 2026 15:01:48 +0300 Subject: [PATCH 751/924] ASoC: tegra: Support WM8994 by machine driver Add Wolfson Microelectronics WM8994 codec support to the Tegra ASoC machine driver. The WM8994 codec is found on devices like LG Optimus 2X (P990/star) and Samsung Captivate Glide (SGH-i927). Signed-off-by: Svyatoslav Ryhel Link: https://patch.msgid.link/20261014120148.126160-3-clamor95@gmail.com Signed-off-by: Mark Brown --- sound/soc/tegra/Kconfig | 10 ++ sound/soc/tegra/Makefile | 2 + sound/soc/tegra/tegra_asoc_machine.c | 2 + sound/soc/tegra/tegra_wm8994.c | 175 +++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 sound/soc/tegra/tegra_wm8994.c diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index fdc954028d62..dd3b2d3d6c4c 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -239,6 +239,16 @@ config SND_SOC_TEGRA_WM8962 boards using the WM8962 codec. Currently, the supported boards are Microsoft Surface RT. +config SND_SOC_TEGRA_WM8994 + tristate "SoC Audio support for Tegra boards using a WM8994 codec" + depends on I2C && GPIOLIB && MFD_WM8994 + select SND_SOC_TEGRA_MACHINE_DRV + select SND_SOC_WM8994 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the WM8994 codec. Currently, the supported boards are + LG Optimus X2 P990. + config SND_SOC_TEGRA_WM9712 tristate "SoC Audio support for Tegra boards using a WM9712 codec" depends on GPIOLIB diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index 1c18ef6971c0..b8f90149bfa6 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -43,10 +43,12 @@ obj-$(CONFIG_SND_SOC_TEGRA210_OPE) += snd-soc-tegra210-ope.o # Tegra machine Support snd-soc-tegra-wm8903-y := tegra_wm8903.o snd-soc-tegra-wm8962-y := tegra_wm8962.o +snd-soc-tegra-wm8994-y := tegra_wm8994.o snd-soc-tegra-machine-y := tegra_asoc_machine.o snd-soc-tegra-audio-graph-card-y := tegra_audio_graph_card.o obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o obj-$(CONFIG_SND_SOC_TEGRA_WM8962) += snd-soc-tegra-wm8962.o +obj-$(CONFIG_SND_SOC_TEGRA_WM8994) += snd-soc-tegra-wm8994.o obj-$(CONFIG_SND_SOC_TEGRA_MACHINE_DRV) += snd-soc-tegra-machine.o obj-$(CONFIG_SND_SOC_TEGRA_AUDIO_GRAPH_CARD) += snd-soc-tegra-audio-graph-card.o diff --git a/sound/soc/tegra/tegra_asoc_machine.c b/sound/soc/tegra/tegra_asoc_machine.c index e4af049fd7e8..e2d0e5947339 100644 --- a/sound/soc/tegra/tegra_asoc_machine.c +++ b/sound/soc/tegra/tegra_asoc_machine.c @@ -122,6 +122,8 @@ static const struct snd_soc_dapm_widget tegra_machine_dapm_widgets[] = { SND_SOC_DAPM_LINE("Line In Jack", NULL), SND_SOC_DAPM_LINE("Line In", NULL), SND_SOC_DAPM_LINE("LineIn", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_INPUT("FM Radio"), }; static const struct snd_kcontrol_new tegra_machine_controls[] = { diff --git a/sound/soc/tegra/tegra_wm8994.c b/sound/soc/tegra/tegra_wm8994.c new file mode 100644 index 000000000000..1550faeba725 --- /dev/null +++ b/sound/soc/tegra/tegra_wm8994.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra_wm8994.c - Tegra machine ASoC driver for boards using WM8994 codec. +// +// Copyright (C) 2026 Svyatoslav Ryhel +// +// Based on tegra_wm8994 code copyright/by: +// +// Author: Sumit Bhattacharya +// Copyright (C) 2010-2011 - NVIDIA, Inc. +// +// Based on code copyright/by: +// +// (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. +// +// Copyright 2007 Wolfson Microelectronics PLC. +// Author: Graeme Gregory +// graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + +#include +#include +#include +#include + +#include +#include +#include + +#include "../codecs/wm8994.h" + +#include "tegra_asoc_machine.h" + +static struct snd_soc_jack_pin tegra_wm8994_mic_jack_pins[] = { + { .pin = "Mic Jack", .mask = SND_JACK_MICROPHONE }, +}; + +static unsigned int tegra_wm8994_mclk_rate(unsigned int srate) +{ + unsigned int mclk; + + switch (srate) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + mclk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + mclk = 12000000; + break; + } + + return mclk; +} + +static const struct snd_kcontrol_new tegra_wm8994_controls[] = { + SOC_DAPM_PIN_SWITCH("Speakers"), + SOC_DAPM_PIN_SWITCH("Int Spk"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic 1"), + SOC_DAPM_PIN_SWITCH("Internal Mic 2"), + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), + SOC_DAPM_PIN_SWITCH("Line Out"), + SOC_DAPM_PIN_SWITCH("FM Radio"), +}; + +static int tegra_wm8994_init(struct snd_soc_pcm_runtime *rtd) +{ + struct tegra_machine *machine = snd_soc_card_get_drvdata(rtd->card); + int err; + + err = tegra_asoc_machine_init(rtd); + if (err) + return err; + + if (!machine->gpiod_mic_det && machine->asoc->add_mic_jack) { + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + + err = snd_soc_card_jack_new_pins(rtd->card, "Mic Jack", + SND_JACK_MICROPHONE, + machine->mic_jack, + tegra_wm8994_mic_jack_pins, + ARRAY_SIZE(tegra_wm8994_mic_jack_pins)); + if (err) { + dev_err(rtd->dev, "Mic Jack creation failed: %d\n", err); + return err; + } + + wm8994_mic_detect(component, machine->mic_jack, 1); + } + + return 0; +} + +static int tegra_wm8994_remove(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *link = &card->dai_link[0]; + struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card, link); + struct tegra_machine *machine = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + + if (!machine->gpiod_mic_det && machine->asoc->add_mic_jack) + wm8994_mic_detect(component, NULL, 1); + + return 0; +} + +SND_SOC_DAILINK_DEFS(wm8994_hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif1")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_wm8994_dai = { + .name = "WM8994", + .stream_name = "WM8994 PCM", + .init = tegra_wm8994_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(wm8994_hifi), +}; + +static struct snd_soc_card snd_soc_tegra_wm8994 = { + .components = "codec:wm8994", + .owner = THIS_MODULE, + .dai_link = &tegra_wm8994_dai, + .num_links = 1, + .controls = tegra_wm8994_controls, + .num_controls = ARRAY_SIZE(tegra_wm8994_controls), + .remove = tegra_wm8994_remove, + .fully_routed = true, +}; + +static const struct tegra_asoc_data tegra_wm8994_data = { + .mclk_rate = tegra_wm8994_mclk_rate, + .mclk_id = WM8994_SYSCLK_MCLK1, + .card = &snd_soc_tegra_wm8994, + .add_common_dapm_widgets = true, + .add_common_snd_ops = true, + .add_mic_jack = true, + .add_hp_jack = true, +}; + +static const struct of_device_id tegra_wm8994_of_match[] = { + { .compatible = "nvidia,tegra-audio-wm8994", .data = &tegra_wm8994_data }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_wm8994_of_match); + +static struct platform_driver tegra_wm8994_driver = { + .driver = { + .name = "tegra-wm8994", + .of_match_table = tegra_wm8994_of_match, + .pm = &snd_soc_pm_ops, + }, + .probe = tegra_asoc_machine_probe, +}; +module_platform_driver(tegra_wm8994_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel "); +MODULE_DESCRIPTION("Tegra+WM8994 machine ASoC driver"); +MODULE_LICENSE("GPL"); From 5bd6d05197affaf5a2d7fae0e06e7373fd839b7c Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:28 +0900 Subject: [PATCH 752/924] ASoC: codecs: wm8990: Fix definition of right input PGA controls The right input PGA boost (R12MNBST, R34MNBST) and routing (R12MNB, R34MNB) bits are in Input Mixer4 (R42), not in Input Mixer3 (R41), where these controls write the left channel bits. Fixes: f10485e79882 ("[ALSA] ASoC: Add WM8990 driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/818cf8d7ae81fa46c67cd5b4d01291a63f44c9a1.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8990.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index 11dfbbbdcd92..a3843094866b 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -97,8 +97,8 @@ static const struct snd_kcontrol_new wm8990_snd_controls[] = { SOC_SINGLE("LIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L12MNBST_BIT, 1, 0), SOC_SINGLE("LIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L34MNBST_BIT, 1, 0), /* INMIXR */ -SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R12MNBST_BIT, 1, 0), -SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R34MNBST_BIT, 1, 0), +SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER4, WM8990_R12MNBST_BIT, 1, 0), +SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER4, WM8990_R34MNBST_BIT, 1, 0), /* LOMIX */ SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER3, @@ -377,9 +377,9 @@ SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8990_INPUT_MIXER4, WM8990_RDBVOL_SHIFT, WM8990_RDBVOL_MASK, 0, in_mix_tlv), SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8990_INPUT_MIXER6, WM8990_RI2BVOL_SHIFT, 7, 0, in_mix_tlv), -SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT, +SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER4, WM8990_R12MNB_BIT, 1, 0), -SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT, +SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER4, WM8990_R34MNB_BIT, 1, 0), }; From 75e0b607e3456c9eb15dfb783984f814a89cbcaf Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:29 +0900 Subject: [PATCH 753/924] ASoC: codecs: wm8990: Fix definition of LOMIX AINLMUX Bypass Volume control The LOMIX AINLMUX bypass volume (LLBLOVOL) is at bits 2:0 of Output Mixer5, not at bits 5:3, which hold the AINRMUX bypass volume (LRBLOVOL). Fixes: f10485e79882 ("[ALSA] ASoC: Add WM8990 driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/87b3bfa7711b61d89c21044d02fb3242dd934385.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8990.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index a3843094866b..5674d97fcd84 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -112,7 +112,7 @@ SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER5, SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER5, WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER5, - WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), + WM8990_LLBLOVOL_SHIFT, WM8990_LLBLOVOL_MASK, 1, out_mix_tlv), /* ROMIX */ SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER4, From 2dfecdc25d4937a36675c0c36ee0addc99e1e7f9 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:30 +0900 Subject: [PATCH 754/924] ASoC: codecs: wm8991: Fix definition of right input PGA controls The right input PGA boost (R12MNBST, R34MNBST) and routing (R12MNB, R34MNB) bits are in Input Mixer4 (R42), not in Input Mixer3 (R41), where these controls write the left channel bits. Fixes: 203db220718c ("ASoC: WM8991: Add initial WM8991 driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/5c0196e45dff1769682d885fc26e613648e05436.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8991.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/wm8991.c b/sound/soc/codecs/wm8991.c index 225c235d3d89..157b48f02451 100644 --- a/sound/soc/codecs/wm8991.c +++ b/sound/soc/codecs/wm8991.c @@ -169,8 +169,8 @@ static const struct snd_kcontrol_new wm8991_snd_controls[] = { SOC_SINGLE("LIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L12MNBST_BIT, 1, 0), SOC_SINGLE("LIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L34MNBST_BIT, 1, 0), /* INMIXR */ - SOC_SINGLE("RIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R12MNBST_BIT, 1, 0), - SOC_SINGLE("RIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R34MNBST_BIT, 1, 0), + SOC_SINGLE("RIN12 PGA Boost", WM8991_INPUT_MIXER4, WM8991_R12MNBST_BIT, 1, 0), + SOC_SINGLE("RIN34 PGA Boost", WM8991_INPUT_MIXER4, WM8991_R34MNBST_BIT, 1, 0), /* LOMIX */ SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER3, @@ -446,9 +446,9 @@ static const struct snd_kcontrol_new wm8991_dapm_inmixr_controls[] = { WM8991_RDBVOL_SHIFT, WM8991_RDBVOL_MASK, 0, in_mix_tlv), SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8991_INPUT_MIXER6, WM8991_RI2BVOL_SHIFT, 7, 0, in_mix_tlv), - SOC_DAPM_SINGLE("RINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT, + SOC_DAPM_SINGLE("RINPGA12 Switch", WM8991_INPUT_MIXER4, WM8991_R12MNB_BIT, 1, 0), - SOC_DAPM_SINGLE("RINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT, + SOC_DAPM_SINGLE("RINPGA34 Switch", WM8991_INPUT_MIXER4, WM8991_R34MNB_BIT, 1, 0), }; From fee722db39c2a1f18c944f747271fd1e131910a6 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:31 +0900 Subject: [PATCH 755/924] ASoC: codecs: wm8991: Fix definition of LOMIX AINLMUX Bypass Volume control The LOMIX AINLMUX bypass volume (LLBLOVOL) is at bits 2:0 of Output Mixer5, not at bits 5:3, which hold the AINRMUX bypass volume (LRBLOVOL). Fixes: 203db220718c ("ASoC: WM8991: Add initial WM8991 driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/c6e230b0e7d7e3db2136cc751e0b563030dbc16d.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8991.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8991.c b/sound/soc/codecs/wm8991.c index 157b48f02451..471310257123 100644 --- a/sound/soc/codecs/wm8991.c +++ b/sound/soc/codecs/wm8991.c @@ -184,7 +184,7 @@ static const struct snd_kcontrol_new wm8991_snd_controls[] = { SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER5, WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER5, - WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv), + WM8991_LLBLOVOL_SHIFT, WM8991_LLBLOVOL_MASK, 1, out_mix_tlv), /* ROMIX */ SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER4, From c1af9e2272f025ff75be76e03413074d5e7801c9 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:32 +0900 Subject: [PATCH 756/924] ASoC: codecs: wm8983: Fix definition of ADC 128x Oversampling control ADC 128x oversampling (ADCOSR128) is at bit 3 of ADC Control, not at bit 8, which is the high pass filter enable (HPFEN). Fixes: 6b3860b0a20a ("ASoC: WM8983: Initial driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/d6a5bbab35e1fd2bec5b13d30951546080d493a8.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8983.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8983.c b/sound/soc/codecs/wm8983.c index fab25f35fd14..5639dad90405 100644 --- a/sound/soc/codecs/wm8983.c +++ b/sound/soc/codecs/wm8983.c @@ -234,7 +234,7 @@ static const struct snd_kcontrol_new wm8983_snd_controls[] = { 8, 1, 0, pga_boost_tlv), SOC_DOUBLE("ADC Inversion Switch", WM8983_ADC_CONTROL, 0, 1, 1, 0), - SOC_SINGLE("ADC 128x Oversampling Switch", WM8983_ADC_CONTROL, 8, 1, 0), + SOC_SINGLE("ADC 128x Oversampling Switch", WM8983_ADC_CONTROL, 3, 1, 0), SOC_DOUBLE_R_TLV("Playback Volume", WM8983_LEFT_DAC_DIGITAL_VOL, WM8983_RIGHT_DAC_DIGITAL_VOL, 0, 255, 0, dac_tlv), From a747ec6e12cf7a71e42567a479af18b930890d9d Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:33 +0900 Subject: [PATCH 757/924] ASoC: codecs: wm8985: Fix definition of ADC 128x Oversampling control ADC 128x oversampling (ADCOSR128) is at bit 3 of ADC Control, not at bit 8, which is the high pass filter enable (HPFEN). Fixes: 6d6f8b832705 ("ASoC: WM8985: Initial driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/00ae032401aaad740b61d664c861b72d2e5ed520.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8985.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8985.c b/sound/soc/codecs/wm8985.c index 2a64d5a851da..f400f528321d 100644 --- a/sound/soc/codecs/wm8985.c +++ b/sound/soc/codecs/wm8985.c @@ -327,7 +327,7 @@ static const struct snd_kcontrol_new wm8985_common_snd_controls[] = { 8, 1, 0, pga_boost_tlv), SOC_DOUBLE("ADC Inversion Switch", WM8985_ADC_CONTROL, 0, 1, 1, 0), - SOC_SINGLE("ADC 128x Oversampling Switch", WM8985_ADC_CONTROL, 8, 1, 0), + SOC_SINGLE("ADC 128x Oversampling Switch", WM8985_ADC_CONTROL, 3, 1, 0), SOC_DOUBLE_R_TLV("Playback Volume", WM8985_LEFT_DAC_DIGITAL_VOL, WM8985_RIGHT_DAC_DIGITAL_VOL, 0, 255, 0, dac_tlv), From 291d81d628f3453026b029507a26b856056e4fdc Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:34 +0900 Subject: [PATCH 758/924] ASoC: codecs: wm8994: Fix definition of WM8958 noise gate hold time controls The noise gate hold time (NG_HLD) is at bits 6:5 of each WM8958 DAC noise gate register, not at bits 3:1, which hold the threshold (NG_THR). Fixes: 1ddc07d0f13a ("ASoC: Add WM8958 noise gate support") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/8acff0f7314eef9eb3d290ca7855a87052d7e706.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8994.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 8bf58a6a8af7..c1f388189b2e 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -701,17 +701,17 @@ static const char *wm8958_ng_text[] = { static SOC_ENUM_SINGLE_DECL(wm8958_aif1dac1_ng_hold, WM8958_AIF1_DAC1_NOISE_GATE, - WM8958_AIF1DAC1_NG_THR_SHIFT, + WM8958_AIF1DAC1_NG_HLD_SHIFT, wm8958_ng_text); static SOC_ENUM_SINGLE_DECL(wm8958_aif1dac2_ng_hold, WM8958_AIF1_DAC2_NOISE_GATE, - WM8958_AIF1DAC2_NG_THR_SHIFT, + WM8958_AIF1DAC2_NG_HLD_SHIFT, wm8958_ng_text); static SOC_ENUM_SINGLE_DECL(wm8958_aif2dac_ng_hold, WM8958_AIF2_DAC_NOISE_GATE, - WM8958_AIF2DAC_NG_THR_SHIFT, + WM8958_AIF2DAC_NG_HLD_SHIFT, wm8958_ng_text); static const struct snd_kcontrol_new wm8958_snd_controls[] = { From 71911bf5bb3fe97da2124d17fc1dd3dbb9379e03 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:35 +0900 Subject: [PATCH 759/924] ASoC: codecs: wm8955: Fix definition of Treble Cutoff control Treble cutoff (TC) is at bit 6 of Treble Control, not at bit 2, which is part of the treble volume (TRBL[3:0]). Fixes: 54db41c116c3 ("ASoC: wm8955: Use SOC_ENUM_SINGLE_DECL()") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/b398bc6dcf8dc2b93504608cdf952823ca238cd5.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8955.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c index c897a9ab764d..d872d4e00529 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -426,7 +426,7 @@ static const char *treble_cutoff_text[] = { "High", "Low" }; -static SOC_ENUM_SINGLE_DECL(treble_cutoff, WM8955_TREBLE_CONTROL, 2, +static SOC_ENUM_SINGLE_DECL(treble_cutoff, WM8955_TREBLE_CONTROL, 6, treble_cutoff_text); static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1); From 22bfcedd3febd6acc63ecf8bfd9638210e4b9a5e Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:36 +0900 Subject: [PATCH 760/924] ASoC: codecs: wm_hubs: Fix definition of MIXINR to MIXOUTR volume control The Right Output Mixer Right Input Volume (MIXINR_MIXOUTR_VOL) is at bits 2:0 of Output Mixer6, not at bits 8:6, which hold the IN2LN volume (IN2LN_MIXOUTR_VOL). Fixes: 942c435ba79f ("ASoC: Add WM8993 CODEC driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/1ea1d115cea5b183dd51bba5cd9e38157b3482ee.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_hubs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index f80dfc488e3c..ce16f2750979 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -427,7 +427,7 @@ SOC_SINGLE_TLV("Right Output Mixer IN2RP Volume", SOC_SINGLE_TLV("Right Output Mixer Left Input Volume", WM8993_OUTPUT_MIXER6, 3, 7, 1, outmix_tlv), SOC_SINGLE_TLV("Right Output Mixer Right Input Volume", - WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv), + WM8993_OUTPUT_MIXER6, 0, 7, 1, outmix_tlv), SOC_SINGLE_TLV("Right Output Mixer DAC Volume", WM8993_OUTPUT_MIXER6, 9, 7, 1, outmix_tlv), From 121963197200ca89050f05a704f53c281702ec16 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:37 +0900 Subject: [PATCH 761/924] ASoC: codecs: wm8737: Fix definition of zero cross switches The mic (xMZC) and PGA (xPZC) zero cross bits are bits 3 and 2 of each channel's Audio Path register. SOC_DOUBLE takes one register and two shifts, so both controls only write the left register. Use SOC_DOUBLE_R, as the Mic Boost Switch next to them does. Fixes: 2a9ae13a2641 ("ASoC: Add initial WM8737 driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/36cab58287bd24955e73bfe3a6fb24cae8fad7b8.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8737.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/wm8737.c b/sound/soc/codecs/wm8737.c index 33a3f88fffb3..0734f6032c8d 100644 --- a/sound/soc/codecs/wm8737.c +++ b/sound/soc/codecs/wm8737.c @@ -148,13 +148,13 @@ SOC_DOUBLE_R_TLV("Mic Boost Volume", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, 6, 3, 0, micboost_tlv), SOC_DOUBLE_R("Mic Boost Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, 4, 1, 0), -SOC_DOUBLE("Mic ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, - 3, 1, 0), +SOC_DOUBLE_R("Mic ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, + 3, 1, 0), SOC_DOUBLE_R_TLV("Capture Volume", WM8737_LEFT_PGA_VOLUME, WM8737_RIGHT_PGA_VOLUME, 0, 255, 0, pga_tlv), -SOC_DOUBLE("Capture ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, - 2, 1, 0), +SOC_DOUBLE_R("Capture ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, + 2, 1, 0), SOC_DOUBLE("INPUT1 DC Bias Switch", WM8737_MISC_BIAS_CONTROL, 0, 1, 1, 0), From 96d30816a9a4fa6b3cc470f82d0f53e8a6425ab9 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:38 +0900 Subject: [PATCH 762/924] ASoC: codecs: wm5100: Fix definition of EQ3 Band 1 Volume control The EQ3 band 1 gain is in WM5100_EQ3_1, not in WM5100_EQ1_1, which holds the EQ1 band 1 gain. Fixes: 6d4baf084f4d ("ASoC: Add WM5100 driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/d7f690e391feda7a6d8f38d0b238b165bc1f4fe2.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm5100.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c index 2fe937a93026..d0f5c4e89556 100644 --- a/sound/soc/codecs/wm5100.c +++ b/sound/soc/codecs/wm5100.c @@ -658,7 +658,7 @@ SOC_SINGLE_TLV("EQ2 Band 4 Volume", WM5100_EQ2_2, WM5100_EQ2_B4_GAIN_SHIFT, SOC_SINGLE_TLV("EQ2 Band 5 Volume", WM5100_EQ2_2, WM5100_EQ2_B5_GAIN_SHIFT, 24, 0, eq_tlv), -SOC_SINGLE_TLV("EQ3 Band 1 Volume", WM5100_EQ1_1, WM5100_EQ3_B1_GAIN_SHIFT, +SOC_SINGLE_TLV("EQ3 Band 1 Volume", WM5100_EQ3_1, WM5100_EQ3_B1_GAIN_SHIFT, 24, 0, eq_tlv), SOC_SINGLE_TLV("EQ3 Band 2 Volume", WM5100_EQ3_1, WM5100_EQ3_B2_GAIN_SHIFT, 24, 0, eq_tlv), From 8c299077c787c151c5c88928467d8e8d85cebdb7 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:39 +0900 Subject: [PATCH 763/924] ASoC: codecs: wm8750: Fix definition of ALC Capture NG Type control The noise gate type is wm8750_enum[7], bit 1 of WM8750_NGATE, not wm8750_enum[4], which is the 3D upper cut-off. Fixes: abadfc928a27 ("[ALSA] ASoC codecs: WM8750 support") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/ce530dfb932d9e8def75059ab9d669360adc76e8.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8750.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index d3f1178c2d34..df49d813aa4c 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -171,7 +171,7 @@ SOC_SINGLE("ALC Capture Hold Time", WM8750_ALC2, 0, 15, 0), SOC_SINGLE("ALC Capture Decay Time", WM8750_ALC3, 4, 15, 0), SOC_SINGLE("ALC Capture Attack Time", WM8750_ALC3, 0, 15, 0), SOC_SINGLE("ALC Capture NG Threshold", WM8750_NGATE, 3, 31, 0), -SOC_ENUM("ALC Capture NG Type", wm8750_enum[4]), +SOC_ENUM("ALC Capture NG Type", wm8750_enum[7]), SOC_SINGLE("ALC Capture NG Switch", WM8750_NGATE, 0, 1, 0), SOC_SINGLE("Left ADC Capture Volume", WM8750_LADC, 0, 255, 0), From 22d50113078dfbeff0d005d64fd9cc680249831c Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 14 Oct 2026 15:32:40 +0900 Subject: [PATCH 764/924] ASoC: codecs: wm8510: Fix definition of ADC Inversion Switch control ADC polarity is bit 0 of WM8510_ADC, not bit 0 of WM8510_COMP, which is the loopback enable. wm8974, which has the same register layout, already uses WM8974_ADC for this control. Fixes: 5d421516670e ("[ALSA] ASoC: Add WM8510 driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Reviewed-by: Charles Keepax Link: https://patch.msgid.link/6ada61df8c8fc97ffb1623e74f6742ca498032fa.1791959026.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8510.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 137dcd3d7487..c611eaf0ef5d 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -134,7 +134,7 @@ SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0), SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0), SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0), -SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0), +SOC_SINGLE("ADC Inversion Switch", WM8510_ADC, 0, 1, 0), SOC_SINGLE("Capture Volume", WM8510_ADCVOL, 0, 127, 0), From 2923259d08b097bb3c39733f67e635a542ad8917 Mon Sep 17 00:00:00 2001 From: Le Qi Date: Wed, 14 Oct 2026 14:53:21 +0800 Subject: [PATCH 765/924] ASoC: codecs: da7213: Get MCLK in I2C probe With deferrable card binding, a card that fails to bind is retried only when a new ASoC component is registered. da7213 requests its MCLK in the component probe callback, so if the clock provider registers late, the card bind defers and the provider's later registration never triggers a rebind, leaving the sound card unregistered. Request MCLK in the I2C probe instead, so a missing clock defers the codec driver probe through the driver core before the component is registered. Fixes: 42d99857d6f0 ("ASoC: core: Move all users to deferrable card binding") Signed-off-by: Le Qi Link: https://patch.msgid.link/20261014065321.740850-1-le.qi@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/da7213.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c index 923b997efbc4..4990feaa8259 100644 --- a/sound/soc/codecs/da7213.c +++ b/sound/soc/codecs/da7213.c @@ -2091,16 +2091,6 @@ static int da7213_probe(struct snd_soc_component *component) pm_runtime_put_sync(component->dev); - /* Check if MCLK provided */ - da7213->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(da7213->mclk)) - return PTR_ERR(da7213->mclk); - if (da7213->mclk) - /* Do automatic PLL handling assuming fixed clock until - * set_pll() has been called. This makes the codec usable - * with the simple-audio-card driver. */ - da7213->fixed_clk_auto_pll = true; - /* Default infinite tone gen, start/stop by Kcontrol */ snd_soc_component_write(component, DA7213_TONE_GEN_CYCLES, DA7213_BEEP_CYCLES_MASK); @@ -2229,6 +2219,19 @@ static int da7213_i2c_probe(struct i2c_client *i2c) mutex_init(&da7213->ctrl_lock); + /* Check if MCLK provided */ + da7213->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(da7213->mclk)) + return dev_err_probe(&i2c->dev, PTR_ERR(da7213->mclk), + "failed to get mclk\n"); + if (da7213->mclk) + /* + * Do automatic PLL handling assuming fixed clock until + * set_pll() has been called. This makes the codec usable + * with the simple-audio-card driver. + */ + da7213->fixed_clk_auto_pll = true; + pm_runtime_set_autosuspend_delay(&i2c->dev, 100); pm_runtime_use_autosuspend(&i2c->dev); pm_runtime_set_active(&i2c->dev); From 159294c01a0d7761a0530ede29b2c069251cf33e Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Thu, 15 Oct 2026 10:12:10 +0800 Subject: [PATCH 766/924] ALSA: hda/realtek: Limit Line In boost and ADC gain on DECENTA MATX03 The DECENTA MATX03 (ALC897, SSID 1e0b:1297) exposes a high gain range on both the Line In pin boost and the ADC input amp. Pin 0x1a reports nsteps=0x03 with stepsize=0x27, that is 0, 9.75, 19.5 and 29.25 dB. ADC 0x08 reports nsteps=0x3f with stepsize=0x02, that is 0 to 31.5 dB in 0.5 dB steps, and defaults to 0x3f (maximum). The combined 60.75 dB analogue cascade overloads the front-end when the source level is high, producing audible noise during Line In monitoring. Add a new fixup alc897_fixup_limit_linein_boost that overrides the amp capabilities of pin 0x1a to nsteps=1 (max ~11.75 dB) and ADC 0x08 to nsteps=1 (max ~0.5 dB). The total gain drops from 60.75 dB to ~12.25 dB, eliminating the clipping. Signed-off-by: wangdicheng Link: https://patch.msgid.link/20261015021210.106392-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc662.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c index eff7c83b9c59..c1c7277b6754 100644 --- a/sound/hda/codecs/realtek/alc662.c +++ b/sound/hda/codecs/realtek/alc662.c @@ -198,6 +198,24 @@ static void alc897_fixup_lenovo_headset_mode(struct hda_codec *codec, } } +static void alc897_fixup_limit_linein_boost(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PROBE) + return; + + snd_hda_override_amp_caps(codec, 0x1a, HDA_INPUT, + (0x00 << AC_AMPCAP_OFFSET_SHIFT) | + (0x01 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x2f << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); + snd_hda_override_amp_caps(codec, 0x08, HDA_INPUT, + (0x00 << AC_AMPCAP_OFFSET_SHIFT) | + (0x01 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x02 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); +} + static const struct coef_fw alc668_coefs[] = { WRITE_COEF(0x01, 0xbebe), WRITE_COEF(0x02, 0xaaaa), WRITE_COEF(0x03, 0x0), WRITE_COEF(0x04, 0x0180), WRITE_COEF(0x06, 0x0), WRITE_COEF(0x07, 0x0f80), @@ -326,6 +344,7 @@ enum { ALC897_FIXUP_HEADSET_MIC_PIN3, ALC662_FIXUP_CSL_GPIO, ALC897_FIXUP_BEELINK_SER6_AMP, + ALC897_FIXUP_LIMIT_LINEIN_BOOST, }; static const struct hda_fixup alc662_fixups[] = { @@ -787,6 +806,10 @@ static const struct hda_fixup alc662_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_gpio4, }, + [ALC897_FIXUP_LIMIT_LINEIN_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc897_fixup_limit_linein_boost, + }, }; static const struct hda_quirk alc662_fixup_tbl[] = { @@ -857,6 +880,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x1b35, 0x1234, "CZC ET26", ALC662_FIXUP_CZC_ET26), SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x1e0b, 0x1297, "DECENTA MATX03", ALC897_FIXUP_LIMIT_LINEIN_BOOST), SND_PCI_QUIRK(0x1e63, 0x6d9a, "F+ FLAPTOP r", ALC897_FIXUP_HP_HSMIC_VERB), SND_PCI_QUIRK(0x1f66, 0x0202, "Beelink SER6 Max 6900", ALC897_FIXUP_BEELINK_SER6_AMP), From fdf9a21cda7868b26430414fb963be976485de52 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 14 Oct 2026 16:24:52 +0200 Subject: [PATCH 767/924] ALSA: pcm: Apply sync-stop at SNDRV_PCM_IOCTL_DROP ioctl When a PCM stream is stopped via SNDRV_PCM_IOCTL_DROP ioctl, it calls snd_pcm_stop() but sync-stop operation is missing. Although this is usually no big issue, let's add snd_pcm_sync_stop() call, just to be sure. Link: https://patch.msgid.link/20261014142454.320703-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 6c379068b54c..7ffbf2c0ea92 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2332,6 +2332,8 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream) result = snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ + snd_pcm_sync_stop(substream, true); + return result; } From 044d41b43ee19aa29cb0c9b7b8e77fd113336522 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 15 Oct 2026 14:56:40 +0800 Subject: [PATCH 768/924] ALSA: hda/realtek: Fix quirk for HP Laptop 15-fc0xxx The HP Laptop 15-fc0xxx is based on an AMD platform where the digital microphone is provided by the snd-soc-acp6x-mach driver, not by the HDA codec. The ALC236_FIXUP_HP_DMIC quirk configures pin 0x12 as an internal DMIC, which is a misconfiguration for this machine: the pin is not wired to a codec DMIC and the setting has no effect on the actual microphone path (which works via ACP6x). Replace ALC236_FIXUP_HP_DMIC with ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO, the standard quirk used by the closely related HP Laptop 15-fd0xxx and other ALC236 HP machines. This removes the misleading DMIC pin config and adds proper mute LED and micmute LED support that was missing from the original quirk. Fixes: 720460722310 ("ALSA: hda/realtek: add quirk for HP Laptop 15-fc0xxx") Cc: stable@vger.kernel.org Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221233 Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222044 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20261015065640.377466-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index aff1a1e40252..76a7ed218012 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4482,7 +4482,6 @@ enum { ALC245_FIXUP_BASS_HP_DAC, ALC245_FIXUP_ACER_MICMUTE_LED, ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED, - ALC236_FIXUP_HP_DMIC, ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS, ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE, @@ -7262,13 +7261,6 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_CS35L41_I2C_2, }, - [ALC236_FIXUP_HP_DMIC] = { - .type = HDA_FIXUP_PINS, - .v.pins = (const struct hda_pintbl[]) { - { 0x12, 0x90a60160 }, /* use as internal mic */ - { } - }, - }, [ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -7940,7 +7932,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8da1, "HP 16 Clipper OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8da7, "HP 14 Enstrom OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8da8, "HP 16 Piston OmniBook X", ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX), - SND_PCI_QUIRK(0x103c, 0x8dc9, "HP Laptop 15-fc0xxx", ALC236_FIXUP_HP_DMIC), + SND_PCI_QUIRK(0x103c, 0x8dc9, "HP Laptop 15-fc0xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8dd4, "HP EliteStudio 8 AIO", ALC274_FIXUP_HP_AIO_BIND_DACS), SND_PCI_QUIRK(0x103c, 0x8dd7, "HP Laptop 15-fd0xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8de8, "HP Gemtree", ALC245_FIXUP_TAS2781_SPI_2), From 85e8c5f933ee3dbc9d061e121237797e9fa05315 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 15 Oct 2026 09:54:38 +0200 Subject: [PATCH 769/924] ALSA: ump: Correct return size of fill_rpn() The RPN/NRPN conversion in fill_rpn() is supposed to return the number of bytes of the resultant UMP packet, but it returns boolean values. This would result in the incorrect ump_bytes in the converter, which will confuse stream processing. Correct to the right value 8 as a MIDI 2.0 UMP packet. Fixes: e6ce8a28c768 ("ALSA: ump: Transmit RPN/NRPN message at each MSB/LSB data reception") Cc: Link: https://patch.msgid.link/20261015075439.50088-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/ump_convert.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index 85cc67de6330..0a7972d91ec5 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -323,7 +323,7 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc, cc->cc_data_lsb); reset_rpn(cc); - return 1; + return 8; } /* convert to a MIDI 1.0 Channel Voice message */ From 91d0048a97e0fdc0a071caa0ba59e659bc6f84df Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 15 Oct 2026 09:18:09 +0200 Subject: [PATCH 770/924] ALSA: pcm: Add refcount for races between drain and unlink We added a workaround for the case where a linked substream gets unlinked during the drain operation by another substream of the same linked group in the commit 9b1dbd69ba6f ("ALSA: pcm: fix use-after-free on linked stream runtime in snd_pcm_drain()"). There, we just cached the parameters of the draining substream for avoiding UAF. Then, we check the draining substream (to_check) again in the loop and remove it from the queue only when it's still present. It means, however, that the wait queue entry won't be removed if the substream went out of the link beforehand, hence a stale wq entry remains. Later on, this can be woken up, and this may hit a UAF, too. The problem surfaced more due to the recent change to call snd_pcm_unlink() earlier. For properly handling both cases above, in this patch, we introduce a refcount to the snd_pcm_runtime that indicates the substream is being accessed / used, then at snd_pcm_release_substream(), it's synced before the actual resource release via snd_refcount_sync(). This guarantees that the accessors are finished, and safe to release memories. Along with the introduction of a refcount, the comments and the code changes by the former fix were partially reverted as they don't fit with the updated code flow any longer. Last but not least, the setup of wait queue entry and prepare_to_wait() call are moved before the group unref. With this change, we can avoid the race between the enqueue and the wakeup, too. Fixes: 9b1dbd69ba6f ("ALSA: pcm: fix use-after-free on linked stream runtime in snd_pcm_drain()") Link: https://patch.msgid.link/20261015071838.31082-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/pcm.h | 2 ++ sound/core/pcm.c | 1 + sound/core/pcm_native.c | 54 ++++++++++++++++------------------------- 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 02d8689354c4..1cf511f8d880 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -8,6 +8,7 @@ * Abramo Bagnara */ +#include #include #include #include @@ -414,6 +415,7 @@ struct snd_pcm_runtime { snd_pcm_uframes_t twake; /* do transfer (!poll) wakeup if non-zero */ wait_queue_head_t sleep; /* poll sleep */ wait_queue_head_t tsleep; /* transfer sleep */ + struct snd_refcount refcnt; /* refcount for draining, etc */ struct snd_fasync *fasync; bool stop_operating; /* sync_stop will be called */ struct mutex buffer_mutex; /* protect for buffer changes */ diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 41c2cab7a52c..c133f23418fe 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -957,6 +957,7 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream, init_waitqueue_head(&runtime->sleep); init_waitqueue_head(&runtime->tsleep); + snd_refcount_init(&runtime->refcnt); __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_OPEN); mutex_init(&runtime->buffer_mutex); diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 7ffbf2c0ea92..05097f2630ad 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2226,9 +2226,6 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, for (;;) { long tout; struct snd_pcm_runtime *to_check; - unsigned int drain_rate; - snd_pcm_uframes_t drain_bufsz; - bool drain_no_period_wakeup; if (signal_pending(current)) { result = -ERESTARTSYS; @@ -2243,45 +2240,35 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, runtime = s->runtime; if (runtime->state == SNDRV_PCM_STATE_DRAINING) { to_check = runtime; + /* pin the draining substream */ + snd_refcount_get(&to_check->refcnt); + + init_wait_entry(&wait, 0); + prepare_to_wait(&to_check->sleep, &wait, + TASK_INTERRUPTIBLE); + if (to_check->no_period_wakeup) { + tout = MAX_SCHEDULE_TIMEOUT; + } else { + tout = 100; + if (to_check->rate) { + long t = to_check->buffer_size * + 1100 / to_check->rate; + tout = max(t, tout); + } + tout = msecs_to_jiffies(tout); + } break; } } snd_pcm_group_unref(group, substream); if (!to_check) break; /* all drained */ - /* - * Cache the runtime fields needed after unlock. - * A concurrent close() on the linked stream may free - * its runtime via snd_pcm_detach_substream() once we - * release the stream lock below. - */ - drain_no_period_wakeup = to_check->no_period_wakeup; - drain_rate = to_check->rate; - drain_bufsz = to_check->buffer_size; - init_wait_entry(&wait, 0); - prepare_to_wait(&to_check->sleep, &wait, TASK_INTERRUPTIBLE); snd_pcm_stream_unlock_irq(substream); - if (drain_no_period_wakeup) - tout = MAX_SCHEDULE_TIMEOUT; - else { - tout = 100; - if (drain_rate) { - long t = drain_bufsz * 1100 / drain_rate; - tout = max(t, tout); - } - tout = msecs_to_jiffies(tout); - } tout = schedule_timeout(tout); - snd_pcm_stream_lock_irq(substream); - group = snd_pcm_stream_group_ref(substream); - snd_pcm_group_for_each_entry(s, substream) { - if (s->runtime == to_check) { - finish_wait(&to_check->sleep, &wait); - break; - } - } - snd_pcm_group_unref(group, substream); + finish_wait(&to_check->sleep, &wait); + /* unpin the draining substream again now */ + snd_refcount_put(&to_check->refcnt); if (card->shutdown) { result = -ENODEV; @@ -2835,6 +2822,7 @@ void snd_pcm_release_substream(struct snd_pcm_substream *substream) snd_pcm_drop(substream); if (substream->hw_opened) { + snd_refcount_sync(&substream->runtime->refcnt); if (substream->runtime->state != SNDRV_PCM_STATE_OPEN) do_hw_free(substream); substream->ops->close(substream); From f0ecc305442f20a6df2575943e9897825d80cc5d Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Wed, 14 Oct 2026 22:51:10 -0400 Subject: [PATCH 771/924] ASoC: rt5645: Shut down the button timer on removal jack_detect_work can rearm btn_check_timer, and the timer callback can queue jack_detect_work. Deleting the timer before cancelling the work does not break this cycle: an already running worker can rearm the timer after timer_delete_sync() returns. The timer can then outlive removal. Use timer_shutdown_sync() before cancelling the delayed work so that subsequent attempts to rearm the timer are ignored. The IRQ producer has already been removed at this point. Keep the restartable timer deletion in the suspend path. This issue was identified as part of our ongoing research while reviewing kernel code. Fixes: 7ff6319e7da5 ("ASoC: rt5645: use polling to support HS button") Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/179203261324.2681924.5002100194676410503.rt5645-button-timer@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5645.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 63e3719059ea..5e0fc69576c0 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -4295,10 +4296,10 @@ static void rt5645_i2c_remove(struct i2c_client *i2c) free_irq(i2c->irq, rt5645); /* - * Since the rt5645_btn_check_callback() can queue jack_detect_work, - * the timer need to be delted first + * The timer and jack_detect_work can rearm each other. Shut down the + * timer before cancelling the work. */ - timer_delete_sync(&rt5645->btn_check_timer); + timer_shutdown_sync(&rt5645->btn_check_timer); cancel_delayed_work_sync(&rt5645->jack_detect_work); cancel_delayed_work_sync(&rt5645->rcclock_work); From 5195b34346115843961b20fbf39297a0ae4f5a48 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 15 Oct 2026 15:41:49 +0800 Subject: [PATCH 772/924] ASoC: amd: yc: Add DMI quirk for MSI Crosshair A17 HX D8WFKG The DMIC (digital microphone) does not work on the MSI Crosshair A17 HX D8WFKG because the ACP6x machine driver lacks a matching DMI quirk. Add the DMI entry so the driver binds correctly and the DMIC is functional. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221928 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20261015074149.426882-1-zhangheng@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 1be8504571ad..64a50fd5ab9c 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -514,6 +514,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Bravo 15 B7ED"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_PRODUCT_NAME, "Crosshair A17 HX D8WFKG"), + } + }, { .driver_data = &acp6x_card, .matches = { From f7b6fe0a9bd308f13db44321b252821683730c5b Mon Sep 17 00:00:00 2001 From: Harendra Gautam Date: Tue, 13 Oct 2026 11:21:24 +0530 Subject: [PATCH 773/924] ASoC: dt-bindings: qcom,shikra-qaif-cpu: Add binding Add the Devicetree binding for the Qualcomm Audio Interface (QAIF) CPU DAI controller used on the Shikra audio platform. QAIF routes audio between system memory, external PCM/TDM/MI2S serial interfaces and the internal Bolero/WCD codec. Platform Devicetree files need a binding to describe the controller resources, DAI cells and per- interface AIF configuration, and named constants to avoid raw numeric IDs in sound-dai references and child-node reg values. Describe the MMIO region, interrupt, clocks, DMA IOMMU mapping and aif-interface child nodes required by the controller. Signed-off-by: Harendra Gautam Reviewed-by: Ajay Kumar Nandam Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20261013-b4-qaif-shikra-audio-v9-1-f3022e28f2fd@oss.qualcomm.com Signed-off-by: Mark Brown --- .../bindings/sound/qcom,shikra-qaif-cpu.yaml | 290 ++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/qcom,shikra-qaif-cpu.yaml diff --git a/Documentation/devicetree/bindings/sound/qcom,shikra-qaif-cpu.yaml b/Documentation/devicetree/bindings/sound/qcom,shikra-qaif-cpu.yaml new file mode 100644 index 000000000000..acca6f617751 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,shikra-qaif-cpu.yaml @@ -0,0 +1,290 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/qcom,shikra-qaif-cpu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Audio Interface (QAIF) CPU DAI Controller + +maintainers: + - Harendra Gautam + +description: | + The Qualcomm Audio Interface (QAIF) is a fully configurable DMA-based + audio subsystem controller. It serialises and deserialises PCM audio + between system memory and external serial audio peripherals (PCM, TDM, + I2S, MI2S) through the AIF path, and transfers parallel audio between + memory and an internal WCD codec through the CIF path. + + AIF (Audio Interface): up to 13 multi-lane Unified Audio Interfaces, + each supporting up to 8 independent data lanes. Each lane is individually + configurable as TX (output/speaker) or RX (input/mic). All lanes of an + interface share a single bit clock and frame sync. Supported modes are + PCM (short/long sync), TDM, and MI2S (stereo/mono). Per-interface + configuration includes sync source (master/slave), sync mode, sync delay, + sync inversion, slot width (8/16/24/32-bit), sample width, active slot + masks (up to 32 slots), bits-per-lane frame size, lane enable/direction + masks, loopback, output-enable control, and full-cycle path support for + long chip-to-chip connections. + + CIF (Codec Interface): up to 10 RDDMA (playback) and 10 WRDMA (capture) + channels connecting to an internal codec over a parallel bus. Each channel + supports active-channel enable mask (up to 16 channels), frame-sync + selection, frame-sync delay, frame-sync output gating, dynamic clock + gating, and 16-bit packing/unpacking. + + Note on RX/TX naming convention: in QAIF, TX refers to the output + (speaker/playback) direction and RX refers to the input (mic/capture) + direction. This applies to both AIF lane directions and CIF slot/mask + properties. + + DMA engine: RDDMA fetches audio from DDR/TCM/LPM into a shared SRAM + latency buffer (SHRAM) and drains it to the interface. WRDMA collects + data from the interface into SHRAM and writes it to memory. Each DMA + owns a private SHRAM region defined by start address and length registers. + Burst sizes of 1/2/4/8/16 beats (64-bit) are supported with up to 4 + outstanding transactions per DMA. Two QSB master ports (QXM0 for TCM, + QXM1 for DDR/LPM) provide the memory interface. + + Resources are partitioned among up to 5 Execution Engines (EEs) via + EE map registers. Each EE owns a set of DMAs, audio interfaces, and + interface groups, and receives its own independent interrupt output. + The interrupt hierarchy has a two-level structure: a summary register + identifies the event class (DMA period, underflow/overflow, error + response, audio interface underflow/overflow, group done, rate detector, + VFR), and per-resource status registers identify the specific channel. + + Interface grouping (bonding) allows up to 6 groups of audio and codec + interfaces to start synchronously and align their DMA period interrupts + within half a frame duration using the RDDMA padding feature. + + Two rate detector blocks measure the frequency of incoming frame sync or + word select signals and generate interrupts on rate change, undetected + rate, or sync timeout. + + Block diagram:: + + System Memory (DDR / LPM / TCM) + +---------------------------------+ + | Circular Buffers (ping-pong) | + +----------+----------+-----------+ + | ^ + 64-bit AXI 64-bit AXI + | | + +----------v----------+-----------+ + | QSB Master Ports | + | +----------+ +----------+ | + | | QXM0 | | QXM1 | | + | +----+-----+ +-----+----+ | + +-------|--------------|----------+ + | | + +-------v--------------v----------+ + | Shared RAM (SHRAM) | + | +------------+ +------------+ | + | | QXM0 Read | | QXM0 Write | | + | | SHRAM | | SHRAM | | + | +------------+ +------------+ | + | +------------+ +------------+ | + | | QXM1 Read | | QXM1 Write | | + | | SHRAM | | SHRAM | | + | +------------+ +------------+ | + +---+--------+--------+-------+---+ + | | | | + +---v--+ +--v---+ +--v---+ +-v----+ + |RDDMA | |RDDMA | |WRDMA | |WRDMA | + | AIF | | CIF | | AIF | | CIF | + |[0..n]| |[0..n]| |[0..n]| |[0..n]| + +--+---+ +--+---+ +--+---+ +-+----+ + | | ^ ^ + | TX | TX | RX | RX + v v | | + +--+--------------------+ +----+----------+ + | Unified Audio Intf | | Codec DMA | + | (AIF 0..12) | | Interface | + | | | (CIF) | + | AUD_INTFa block: | | | + | - Serializer (TX) | | RDDMA: DDR -> | + | - De-serializer (RX) | | internal | + | - Sync gen/detect | | codec | + | - Up to 8 data lanes | | WRDMA: codec | + | - PCM / TDM / MI2S | | -> DDR | + | - Near Pad Logic | | Up to 16 ch | + +--+--------------------+ +----+----------+ + | Lane 0..7 (TX/RX) | Parallel bus + | Bit clk + Frame sync | + Frame sync + v v + +--+--------+ +------+------+ + | External | | Internal | + | Serial | | Digital | + | Peripherals| | Codec | + | (PCM/TDM/ | | (Bolero/ | + | MI2S) | | WCD) | + +-----------+ +-------------+ + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: qcom,shikra-qaif-cpu + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + description: + QAIF summary interrupt for this Execution Engine. + + iommus: + maxItems: 1 + + dma-coherent: true + + clocks: + maxItems: 9 + + clock-names: + description: + Core clocks for register access and DMA, followed by one bit + clock per AIF interface. + items: + - const: lpass_config + - const: lpass_core_axim + - const: aud_dma + - const: aud_dma_mem + - const: bus + - const: aif_if0_ibit + - const: aif_if1_ibit + - const: aif_if2_ibit + - const: aif_if3_ibit + + '#sound-dai-cells': + const: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + '^aif@[0-9a-f]+$': + type: object + description: + QAIF AIF interface child node for PCM, TDM or MI2S configuration. + + allOf: + - $ref: /schemas/sound/tdm-slot.yaml# + + properties: + reg: + maxItems: 1 + description: + DAI ID of the AIF interface (QAIF_MI2S_AIF* or QAIF_TDM_AIF*). + + dai-format: + description: Serial audio interface format (MI2S, TDM or PCM). + enum: [i2s, dsp_a, dsp_b] + + dai-tdm-slot-width: + enum: [8, 16, 24, 32] + + dai-tdm-slot-num: + minimum: 1 + maximum: 32 + + dai-tdm-slot-rx-mask: + minItems: 1 + maxItems: 32 + + dai-tdm-slot-tx-mask: + minItems: 1 + maxItems: 32 + + qcom,qaif-aif-lane-map: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Per-lane direction map, one entry per lane starting from lane 0. + Use 0 for TX (output/speaker) and 1 for RX (input/mic). + minItems: 1 + maxItems: 8 + items: + enum: [0, 1] + + required: + - reg + - dai-format + - qcom,qaif-aif-lane-map + + if: + properties: + dai-format: + enum: [dsp_a, dsp_b] + then: + required: + - dai-tdm-slot-width + - dai-tdm-slot-num + - dai-tdm-slot-rx-mask + - dai-tdm-slot-tx-mask + + unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + - iommus + - clocks + - clock-names + - '#sound-dai-cells' + - '#address-cells' + - '#size-cells' + +unevaluatedProperties: false + +examples: + - | + #include + soc { + #address-cells = <2>; + #size-cells = <2>; + + qaif_cpu: audio-controller@a000000 { + compatible = "qcom,shikra-qaif-cpu"; + reg = <0x0 0x0a000000 0x0 0x20000>; + interrupts = ; + iommus = <&apps_smmu 0x1c0 0x0>; + clocks = <&gcc 213>, + <&gcc 214>, + <&audiocorecc 15>, + <&audiocorecc 17>, + <&audiocorecc 18>, + <&audiocorecc 5>, + <&audiocorecc 8>, + <&audiocorecc 11>, + <&audiocorecc 14>; + clock-names = "lpass_config", + "lpass_core_axim", + "aud_dma", + "aud_dma_mem", + "bus", + "aif_if0_ibit", + "aif_if1_ibit", + "aif_if2_ibit", + "aif_if3_ibit"; + #sound-dai-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + + aif@b5 { + reg = <181>; + dai-format = "dsp_a"; + dai-tdm-slot-width = <32>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-rx-mask = <1 1>; + dai-tdm-slot-tx-mask = <1 1>; + qcom,qaif-aif-lane-map = <1 0>; + }; + }; + }; From 6a07ff8dd30495d7156eca72c7869669b59351f6 Mon Sep 17 00:00:00 2001 From: Harendra Gautam Date: Tue, 13 Oct 2026 11:21:25 +0530 Subject: [PATCH 774/924] ASoC: qcom: Add QAIF shared data structures and variant interface Add qaif.h to hold common definitions shared by the QAIF CPU DAI and PCM platform drivers. QAIF exposes AIF DMA paths for external audio interfaces and CIF DMA paths for the internal codec interface. The two drivers need a common place to describe DMA channel allocation, regmap fields, interrupt dispatch, SHRAM buffer tracking and per-stream runtime data. Introduce the QAIF driver data, AIF and CIF register-field descriptions, IRQ mapping helpers and SoC variant descriptor. The variant descriptor describes the register layout, DMA channel counts, clock names, DAI table and platform callbacks used by the QAIF driver code. Signed-off-by: Harendra Gautam Reviewed-by: Ajay Kumar Nandam Link: https://patch.msgid.link/20261013-b4-qaif-shikra-audio-v9-2-f3022e28f2fd@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qaif.h | 292 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 sound/soc/qcom/qaif.h diff --git a/sound/soc/qcom/qaif.h b/sound/soc/qcom/qaif.h new file mode 100644 index 000000000000..794d02ef128b --- /dev/null +++ b/sound/soc/qcom/qaif.h @@ -0,0 +1,292 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef SND_SOC_QCOM_QAIF_H +#define SND_SOC_QCOM_QAIF_H + +#include +#include +#include +#include +#include +#include +#include +#include + +struct snd_pcm_substream; +struct snd_soc_dai_driver; +struct snd_soc_dai_ops; +struct platform_device; + +#define QAIF_CIF_DMA_INTF_ONE_CHANNEL 0x01 +#define QAIF_CIF_DMA_INTF_TWO_CHANNEL 0x03 +#define QAIF_CIF_DMA_INTF_FOUR_CHANNEL 0x0f +#define QAIF_CIF_DMA_INTF_SIX_CHANNEL 0x3f +#define QAIF_CIF_DMA_INTF_EIGHT_CHANNEL 0xff + +#define QAIF_AIF_SLOT_WIDTH(bits) ((bits) - 1) +#define QAIF_AIF_SAMPLE_WIDTH(bits) ((bits) - 1) + +/* Watermark level 5 encoded as N-1: hardware field value = watermark - 1 */ +#define QAIF_DMACTL_WM_5 4 + +#define QAIF_DMA_BYTES_TO_WORDS_SHIFT 3 +#define QAIF_DMA_CLOCK_FREQ 38400000 + +#define QAIF_MAX_AIF_CFG_CNT 4 +#define QAIF_MAX_LANES 8 + +#define QAIF_MAX_MI2S_PORTS 8 +#define QAIF_MI2S_SLOTS 2 +#define QAIF_MAX_AIF_DMA_IDX 8 +#define QAIF_MAX_CIF_DMA_IDX 8 + +/* SID map register CSID field (WRDMA_SID/RDDMA_SID), bits [4:0] */ +#define QAIF_CSID_MASK GENMASK(4, 0) + +static inline bool qaif_is_aif_mi2s_port(unsigned int dai_id) +{ + return (dai_id >= AIF_MI2S_RX_0 && dai_id <= AIF_MI2S_RX_12) || + (dai_id >= AIF_MI2S_TX_0 && dai_id <= AIF_MI2S_TX_12); +} + +static inline bool qaif_is_aif_tdm_port(unsigned int dai_id) +{ + return (dai_id >= AIF_TDM_RX_0 && dai_id <= AIF_TDM_RX_12) || + (dai_id >= AIF_TDM_TX_0 && dai_id <= AIF_TDM_TX_12); +} + +static inline bool qaif_is_aif_port(unsigned int dai_id) +{ + return qaif_is_aif_mi2s_port(dai_id) || qaif_is_aif_tdm_port(dai_id); +} + +static inline bool qaif_is_cif_port(unsigned int dai_id) +{ + return (dai_id >= WSA_CODEC_DMA_RX_0 && dai_id <= RX_CODEC_DMA_RX_7); +} + +enum qxm_sel { + QAIF_QXM0 = 0, + QAIF_QXM1 = 1, +}; + +enum qaif_irq_type { + QAIF_AIF_IRQ = 0, + QAIF_CIF_IRQ = 1, + QAIF_IRQ_TYPE_MAX, +}; + +enum qaif_dma_type { + QAIF_AIF_DMA = 0, + QAIF_CIF_DMA = 1, +}; + +struct qaif_aif_config { + u32 sync_mode; + u32 sync_src; + bool invert_sync; + u32 sync_delay; + u32 slot_width; + u32 slot_en_rx_mask; + u32 slot_en_tx_mask; + bool loopback_en; + bool ctrl_data_oe; + u32 lane_en_mask; + u32 lane_dir_mask; + bool full_cycle_en; + u32 slot_num; +}; + +struct qaif_pcm_data { + /* Flat slot in aif_substream[] (or cif_substream[]) used to track + * this stream. Direction-encoded: playback uses the raw DMA channel + * number (0..num_rddma-1), capture adds wrdma_start so playback and + * capture of the same DAI occupy distinct slots. + * Set by alloc_stream_dma_idx(); bounded by QAIF_MAX_AIF/CIF_DMA_IDX. + */ + int stream_dma_idx; + /* Hardware channel number (0-based within its DMA bank) passed to + * register address helpers (e.g. QAIF_RDDMA_CFG_REG(v, chan)). + * Direction-agnostic: the register helpers select RDDMA or WRDMA bank + * based on stream direction, so both directions of the same DAI share + * the same value. Set by get_dma_idx(). + */ + int dma_reg_idx; +}; + +/* Named 0-based DMA channel indices used as the dma_idx of a + * qaif_dmaidx_dai_map entry, so board tables reference channels by name. + */ +enum qaif_dma_idx { + QAIF_DMA_IDX0 = 0, + QAIF_DMA_IDX1, + QAIF_DMA_IDX2, + QAIF_DMA_IDX3, + QAIF_DMA_IDX4, + QAIF_DMA_IDX5, + QAIF_DMA_IDX6, + QAIF_DMA_IDX7, +}; + +/* Associates a DAI ID with its physical DMA channel index on a given + * board/variant. @dai_id is the DAI to match; @dma_idx (a qaif_dma_idx value) + * is the 0-based hardware channel that get_dma_idx() returns for it. + */ +struct qaif_dmaidx_dai_map { + unsigned int dai_id; + int dma_idx; +}; + +struct qaif_drv_data { + struct clk *mi2s_bit_clk[QAIF_MAX_MI2S_PORTS]; + struct clk_bulk_data *clks; + int num_clks; + struct clk *aud_dma_clk; + struct clk *aud_dma_mem_clk; + void __iomem *audio_qaif; + struct regmap *audio_qaif_map; + int audio_qaif_irq; + struct mutex stream_lock; /* serializes stream open/close */ + bool qaif_hw_configured; + const struct qaif_variant *variant; + struct qaif_aif_config aif_intf_cfg[QAIF_MAX_AIF_CFG_CNT]; + unsigned long aif_dma_idx_bit_map; + unsigned long cif_dma_idx_bit_map; + struct snd_pcm_substream *aif_substream[QAIF_MAX_AIF_DMA_IDX]; + struct snd_pcm_substream *cif_substream[QAIF_MAX_CIF_DMA_IDX]; + u32 smmu_csid_bits; /* SMMU stream ID bits from DT iommus cell */ +}; + +enum qaif_summary_irq_bitmask { + QAIF_SUMMARY_BITMASK_AIF_PERIOD_RDDMA = BIT(0), + QAIF_SUMMARY_BITMASK_AIF_UNDERFLOW_RDDMA = BIT(1), + QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_RDDMA = BIT(2), + QAIF_SUMMARY_BITMASK_AIF_PERIOD_WRDMA = BIT(3), + QAIF_SUMMARY_BITMASK_AIF_OVERFLOW_WRDMA = BIT(4), + QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_WRDMA = BIT(5), + QAIF_SUMMARY_BITMASK_CIF_PERIOD_RDDMA = BIT(18), + QAIF_SUMMARY_BITMASK_CIF_UNDERFLOW_RDDMA = BIT(19), + QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_RDDMA = BIT(20), + QAIF_SUMMARY_BITMASK_CIF_PERIOD_WRDMA = BIT(24), + QAIF_SUMMARY_BITMASK_CIF_OVERFLOW_WRDMA = BIT(25), + QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_WRDMA = BIT(26), +}; + +enum qaif_client_status_register_bitmask_info { + QAIF_BITMASK_AIF_RDDMA_WRDMA = GENMASK(5, 0), + QAIF_BITMASK_CIF_RDDMA_WRDMA = GENMASK(26, 18), +}; + +struct qaif_irq_map { + u32 client_id; + u32 mask; + /* + * Called from the hard IRQ handler (asoc_platform_qaif_irq()) with + * @status holding the summary interrupt status. Must not sleep. + */ + irqreturn_t (*client_irq_handler)(struct qaif_drv_data *drvdata, u32 status); +}; + +enum qaif_dma_dir { + QAIF_DMA_RDDMA = 0, + QAIF_DMA_WRDMA = 1, +}; + +enum qaif_irq { + QAIF_IRQ_PERIOD = 0, + QAIF_IRQ_OVERFLOW, + QAIF_IRQ_UNDERFLOW, + QAIF_IRQ_ERROR, +}; + +enum qaif_irq_op { + QAIF_IRQ_CLEAR = 0, + QAIF_IRQ_ENABLE, + QAIF_IRQ_DISABLE, +}; + +enum qaif_client_info { + QAIF_CLIENT_ID_AIF_DMA = 0, + QAIF_CLIENT_ID_CIF_DMA, +}; + +struct qaif_variant { + u32 ee; /* Hardware EE index assigned to this driver instance */ + + u32 num_rddma; + u32 num_wrdma; + u32 wrdma_start; + u32 num_codec_rddma; + u32 num_codec_wrdma; + u32 codec_wrdma_start; + u32 num_intf; + + u32 rddma_reg_base; + u32 rddma_stride; + u32 codec_rddma_reg_base; + u32 codec_rddma_stride; + u32 wrdma_reg_base; + u32 wrdma_stride; + u32 codec_wrdma_reg_base; + u32 codec_wrdma_stride; + + u32 rddma_irq_reg_base; + u32 rddma_irq_stride; + u32 codec_rddma_irq_reg_base; + u32 codec_rddma_irq_stride; + u32 wrdma_irq_reg_base; + u32 wrdma_irq_stride; + u32 codec_wrdma_irq_reg_base; + u32 codec_wrdma_irq_stride; + + /* QSB master port selector; only QAIF_QXM0 is supported today */ + enum qxm_sel qxm_type; + /* SHRAM sizes in 64-bit words */ + u32 rddma_shram_len; + /* SHRAM start offsets (in 64-bit words), indexed by enum qaif_dma_type */ + u32 rddma_shram_start_addr[2]; + u32 wrdma_shram_len; + u32 wrdma_shram_start_addr[2]; + + const char * const *clk_name; + int num_clks; + struct snd_soc_dai_driver *dai_driver; + int num_dai; + const char * const *dai_bit_clk_names; + /* + * Optional per-AIF configuration tables, indexed by AIF interface + * index (0..num_intf-1). NULL if the variant does not use them. + */ + const bool *aif_full_cycle_en; + const bool *aif_ctrl_data_oe; + const bool *aif_loopback_en; + + /* Returns a direction-encoded flat slot index for use in + * aif_substream[]/cif_substream[] and the dma_idx_bit_map. + * Playback returns the raw channel number; capture adds wrdma_start (or + * codec_wrdma_start) so both directions of the same DAI get distinct + * slots. Sets the corresponding bit atomically; returns -EBUSY if + * already open, -EINVAL if @dai_id or the computed index is out of range. + */ + int (*alloc_stream_dma_idx)(struct qaif_drv_data *data, int direction, + unsigned int dai_id); + /* Clears the bit set by alloc_stream_dma_idx. */ + int (*free_stream_dma_idx)(struct qaif_drv_data *data, int chan, + unsigned int dai_id); + /* Returns the hardware channel number (0-based within its DMA bank) + * for @dai_id, direction-agnostic. Used as dma_reg_idx. + */ + int (*get_dma_idx)(unsigned int dai_id); +}; + +extern const struct snd_soc_dai_ops asoc_qcom_qaif_cif_dai_ops; +extern const struct snd_soc_dai_ops asoc_qcom_qaif_aif_cpu_dai_ops; + +int asoc_qcom_qaif_cpu_platform_probe(struct platform_device *pdev); +int asoc_qcom_qaif_platform_register(struct platform_device *pdev); +extern const struct dev_pm_ops asoc_qcom_qaif_pm_ops; + +#endif /* SND_SOC_QCOM_QAIF_H */ From 6a0d1c361ec57c690158e86e505f1f6de58c0ef7 Mon Sep 17 00:00:00 2001 From: Harendra Gautam Date: Tue, 13 Oct 2026 11:21:26 +0530 Subject: [PATCH 775/924] ASoC: qcom: Add QAIF hardware register map Add qaif-reg.h to define the MMIO register offsets for the Qualcomm Audio Interface (QAIF) hardware block. The QAIF driver needs a common register map to program the core registers, audio interfaces, AIF/CIF DMA channels, interrupt registers, SHRAM/QXM routing, and SID maps. Add accessor helpers so the driver can select the proper AIF or CIF DMA register set based on the DAI ID. Signed-off-by: Harendra Gautam Reviewed-by: Ajay Kumar Nandam Link: https://patch.msgid.link/20261013-b4-qaif-shikra-audio-v9-3-f3022e28f2fd@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qaif-reg.h | 470 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 470 insertions(+) create mode 100644 sound/soc/qcom/qaif-reg.h diff --git a/sound/soc/qcom/qaif-reg.h b/sound/soc/qcom/qaif-reg.h new file mode 100644 index 000000000000..2ae26cf24c59 --- /dev/null +++ b/sound/soc/qcom/qaif-reg.h @@ -0,0 +1,470 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * qaif-reg.h -- QAIF ALSA SoC CPU-Platform DAI driver register header + */ +#ifndef SND_SOC_QCOM_QAIF_REG_H +#define SND_SOC_QCOM_QAIF_REG_H + +#include "qaif.h" + +#define QAIF_SUMMARY_IRQSTAT_REG(v) (0x19188 + (0x1000 * ((v)->ee))) + +/* Core HW info */ +#define QAIF_HW_VERSION_REG (0x0000) +#define QAIF_HW_INFO_REG (0x0004) +#define QAIF_HW_INFO2_REG (0x0008) + +/* Interface lane and channel info */ +#define QAIF_AUD_INTF_LANE_INFO_REG (0x0020) +#define QAIF_AUD_INTF_LANE_INFO2_REG (0x0024) +#define QAIF_CDC_TX_INTF_CH_INFO_REG(n) (0x0028 + (0x4 * (n))) +#define QAIF_CDC_RX_INTF_CH_INFO_REG(n) (0x0068 + (0x4 * (n))) +#define QAIF_QXM1_SHRAM_LENGTH_INFO_REG (0x0088) +#define QAIF_QXM0_SHRAM_LENGTH_INFO_REG (0x008C) +#define QAIF_NUM_AUD_INTF_TO_RAIL_INFO_REG (0x0090) + +/* Debug/control and status */ +#define QAIF_DEBUG_CTL_REG (0x0200) +#define QAIF_WRDMA_LOOPBACK_EN_REG (0x0204) +#define QAIF_WRDMA_LOOPBACK_SEL_REG (0x0208) +#define QAIF_SHRAM_DYNAMIC_CLK_GATING_EN_REG (0x0300) +#define QAIF_AXI_STATUS_REG (0x0304) +#define QAIF_QSB_DYNAMIC_CLK_GATING_EN_REG (0x0308) +#define QAIF_START_STOP_CTRL_BYPASS_EN_REG (0x030C) +#define QAIF_QXM0_AXI_ATTR_CFG_REG (0x040C) + +/* QXM request/grant debug */ +#define QAIF_QXM0_AUD_WR_REQ_GNT_DBG_STAT_REG (0x0500) +#define QAIF_QXM1_AUD_WR_REQ_GNT_DBG_STAT_REG (0x0504) +#define QAIF_QXM0_CODEC_RX_WR_REQ_DBG_STAT_REG (0x0508) +#define QAIF_QXM0_CODEC_RX_WR_GNT_DBG_STAT_REG (0x050C) +#define QAIF_QXM1_CODEC_RX_WR_REQ_DBG_STAT_REG (0x0510) +#define QAIF_QXM1_CODEC_RX_WR_GNT_DBG_STAT_REG (0x0514) +#define QAIF_QXM0_AUD_RD_REQ_GNT_DBG_STAT_REG (0x0518) +#define QAIF_QXM1_AUD_RD_REQ_GNT_DBG_STAT_REG (0x051C) +#define QAIF_QXM0_CODEC_TX_RD_REQ_DBG_STAT_REG (0x0520) +#define QAIF_QXM0_CODEC_TX_RD_GNT_DBG_STAT_REG (0x0524) +#define QAIF_QXM1_CODEC_TX_RD_REQ_DBG_STAT_REG (0x0528) +#define QAIF_QXM1_CODEC_TX_RD_GNT_DBG_STAT_REG (0x052C) +#define QAIF_QXM0_EXT_RDDMA_RD_REQ_GNT_DBG_STAT_REG (0x0530) +#define QAIF_QXM1_EXT_RDDMA_RD_REQ_GNT_DBG_STAT_REG (0x0534) + +/* QSB transaction debug */ +#define QAIF_QSB_AUD_WR_TXN_DBG_STAT_REG (0x0538) +#define QAIF_QSB_CODEC_RX_WR_TXN_ERR_DBG_STAT_REG (0x053C) +#define QAIF_QSB_CODEC_RX_WR_TXN_OKAY_DBG_STAT_REG (0x0540) +#define QAIF_QSB_AUD_ADDR_SENT_DBG_STAT_REG (0x0544) +#define QAIF_QSB_CODEC_TX_RD_ADDR_SENT_DBG_STAT_REG (0x0548) +#define QAIF_QSB_EXT_RDDMA_RD_ADDR_SENT_DBG_STAT_REG (0x054C) +#define QAIF_QSB_CODEC_RX_WR_ADDR_SENT_DBG_STAT_REG (0x0550) +#define QAIF_QSB_AUD_RD_TXN_DBG_STAT_REG (0x0554) +#define QAIF_QSB_CODEC_TX_RD_TXN_ERR_DBG_STAT_REG (0x0558) +#define QAIF_QSB_CODEC_TX_RD_TXN_RCVD_DBG_STAT_REG (0x055C) +#define QAIF_QSB_EXT_RDDMA_RD_TXN_DBG_STAT_REG (0x0560) +#define QAIF_QSB_MISC_DBG_STATUS_REG (0x0564) + +/* Global spare and HWE */ +#define QAIF_GLOBAL_SPARE_IN_REG (0x0B00) +#define QAIF_GLOBAL_SPARE_OUT_REG (0x0B04) +#define QAIF_HWE_CFG_REG (0x0B08) + +/* SID maps */ +#define QAIF_WRDMA_SID_MAP_REG (0x1B00) +#define QAIF_CDC_WRDMA_SID_MAP_REG (0x1B40) +#define QAIF_RDDMA_SID_MAP_REG (0x1C00) +#define QAIF_CDC_RDDMA_SID_MAP_REG (0x1C40) + +/* EE overlap interrupts */ +#define QAIF_EE_OVERLAP_IRQ_EN_REG (0x1D00) +#define QAIF_EE_OVERLAP_IRQ_RAW_STATUS_REG (0x1D04) +#define QAIF_EE_OVERLAP_IRQ_CLEAR_REG (0x1D08) +#define QAIF_EE_OVERLAP_IRQ_FORCE_REG (0x1D0C) + +/* + * EE (Execution Engine) assignment and map registers. + * + * The EE offset (v->ee) is an address/index offset relative to the + * platform-defined QAIF/EE base and lets the hardware route the AIF + * operation to the correct EE context. It is hardware/platform-specific and + * must match the mapping defined for the selected QAIF AIF/lane in the + * hardware programming guide. It is not a runtime data offset and must not + * be changed based on the stream configuration. + */ +#define QAIF_EE_RDDMA_ASSIGNMENT_REG(v) (0x19148 + (0x1000 * ((v)->ee))) +#define QAIF_EE_WRDMA_ASSIGNMENT_REG(v) (0x19150 + (0x1000 * ((v)->ee))) +#define QAIF_EE_INTF_ASSIGNMENT_REG(v) (0x19158 + (0x1000 * ((v)->ee))) +#define QAIF_EE_CODEC_RDDMA_ASSIGN_REG(v) (0x19308 + (0x1000 * ((v)->ee))) +#define QAIF_EE_CODEC_WRDMA_ASSIGN_REG(v) (0x19318 + (0x1000 * ((v)->ee))) +#define QAIF_EE_RDDMA_MAP_REG(v) (0x1920 + (0x1000 * ((v)->ee))) +#define QAIF_EE_WRDMA_MAP_REG(v) (0x1940 + (0x1000 * ((v)->ee))) +#define QAIF_EE_INTF_MAP_REG(v) (0x1960 + (0x1000 * ((v)->ee))) +#define QAIF_EE_CODEC_RDDMA_MAP_REG(v) (0x1980 + (0x1000 * ((v)->ee))) +#define QAIF_EE_CODEC_WRDMA_MAP_REG(v) (0x1A00 + (0x1000 * ((v)->ee))) + +/* EE rate-detection and VFR interrupts */ +#define QAIF_EE_RATE_DET_IRQ_EN_REG(v) (0x190F0 + (0x1000 * ((v)->ee))) +#define QAIF_EE_RATE_DET_IRQ_STAT_REG(v) (0x190F4 + (0x1000 * ((v)->ee))) +#define QAIF_EE_RATE_DET_IRQ_RAW_STAT_REG(v) (0x190F8 + (0x1000 * ((v)->ee))) +#define QAIF_EE_RATE_DET_IRQ_CLEAR_REG(v) (0x190FC + (0x1000 * ((v)->ee))) +#define QAIF_EE_RATE_DET_IRQ_FORCE_REG(v) (0x19100 + (0x1000 * ((v)->ee))) + +#define QAIF_EE_VFR_IRQ_EN_REG(v) (0x19104 + (0x1000 * ((v)->ee))) +#define QAIF_EE_VFR_IRQ_STATUS_REG(v) (0x19108 + (0x1000 * ((v)->ee))) +#define QAIF_EE_VFR_IRQ_RAW_STATUS_REG(v) (0x1910C + (0x1000 * ((v)->ee))) +#define QAIF_EE_VFR_IRQ_CLEAR_REG(v) (0x19110 + (0x1000 * ((v)->ee))) +#define QAIF_EE_VFR_IRQ_FORCE_REG(v) (0x19114 + (0x1000 * ((v)->ee))) + +/* EE AUD_INTF underflow/overflow interrupts */ +#define QAIF_EE_AUD_INTF_UF_IRQ_EN_REG(v) (0x19160 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_UF_IRQ_STAT_REG(v) (0x19164 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_UF_IRQ_RAW_STAT_REG(v) (0x19168 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_UF_IRQ_CLEAR_REG(v) (0x1916C + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_UF_IRQ_FORCE_REG(v) (0x19170 + (0x1000 * ((v)->ee))) + +#define QAIF_EE_AUD_INTF_OF_IRQ_EN_REG(v) (0x19174 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_OF_IRQ_STAT_REG(v) (0x19178 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_OF_IRQ_RAW_STAT_REG(v) (0x1917C + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_OF_IRQ_CLEAR_REG(v) (0x19180 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_OF_IRQ_FORCE_REG(v) (0x19184 + (0x1000 * ((v)->ee))) + +/* EE L2 Period IRQ mux selection */ +#define QAIF_EE_L2_PERIOD_IRQ_0_3_MUX_SEL_REG(v) (0x19F00 + (0x1000 * ((v)->ee))) +#define QAIF_EE_L2_PERIOD_IRQ_4_7_MUX_SEL_REG(v) (0x19F04 + (0x1000 * ((v)->ee))) + +/* AUD_INTF block (per interface, stride 0x1000 starting at 0x4000) */ +#define QAIF_AUD_INTF_REG_ADDR(offset, i) (0x4000 + (offset) + (0x1000 * (i))) + +#define QAIF_AUD_INTF_CTL_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0000, (i)) +#define QAIF_AUD_INTF_SYNC_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0004, (i)) +#define QAIF_AUD_INTF_BIT_WIDTH_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0008, (i)) +#define QAIF_AUD_INTF_FRAME_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x000C, (i)) +#define QAIF_AUD_INTF_ACTV_SLOT_EN_TX_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0010, (i)) +#define QAIF_AUD_INTF_ACTV_SLOT_EN_RX_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0030, (i)) +#define QAIF_AUD_INTF_LANE_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0050, (i)) +#define QAIF_AUD_INTF_MI2S_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0054, (i)) +#define QAIF_AUD_INTF_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0058, (i)) +#define QAIF_AUD_INTF_CHAR_CTL_REG(i) QAIF_AUD_INTF_REG_ADDR(0x005C, (i)) +#define QAIF_AUD_INTF_CHAR_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0060, (i)) +#define QAIF_AUD_INTF_CHAR_DATA_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0064, (i)) +#define QAIF_AUD_INTF_CHAR_DATA_EXT_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0068, (i)) +#define QAIF_AUD_INTF_CHAR_SYNC_REG(i) QAIF_AUD_INTF_REG_ADDR(0x006C, (i)) +#define QAIF_AUD_INTF_INIT_DBG_STATUS_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0FF0, (i)) +#define QAIF_AUD_INTF_TX_DBG_STATUS_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0FF4, (i)) +#define QAIF_AUD_INTF_RX_DBG_STATUS_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0FF8, (i)) + +/* RATE_DET block (per detector, stride 0x1000 starting at 0x1E000) */ +#define QAIF_RATE_DET_REG_ADDR(offset, det) (0x1E000 + (offset) + (0x1000 * (det))) + +#define QAIF_RATE_DET_CONFIG_REG(det) QAIF_RATE_DET_REG_ADDR(0x0000, (det)) +#define QAIF_RATE_DET_TARGET1_CONF_REG(det) QAIF_RATE_DET_REG_ADDR(0x0004, (det)) +#define QAIF_RATE_DET_TARGET2_CONF_REG(det) QAIF_RATE_DET_REG_ADDR(0x0008, (det)) +#define QAIF_RATE_DET_BIN_REG(det) QAIF_RATE_DET_REG_ADDR(0x000C, (det)) +#define QAIF_RATE_DET_STC_DIFF_REG(det) QAIF_RATE_DET_REG_ADDR(0x0010, (det)) +#define QAIF_RATE_DET_SEL_REG(det) QAIF_RATE_DET_REG_ADDR(0x0014, (det)) +#define QAIF_RATE_DET_TIMEOUT_CFG_REG(det) QAIF_RATE_DET_REG_ADDR(0x0018, (det)) + +#define QAIF_WRDMA_MAP_QXM (0x1000) +#define QAIF_CDC_WRDMA_MAP_QXM (0x1004) +#define QAIF_RDDMA_MAP_QXM (0x1010) +#define QAIF_CDC_RDDMA_MAP_QXM (0x1014) +#define QAIF_RDDMA_QXM1_SHRAM_ST_ADDR(i) (0x1100 + (0x4 * (i))) +#define QAIF_CDC_RDDMA_QXM1_SHRAM_ST_ADDR(i) (0x1140 + (0x4 * (i))) +#define QAIF_RDDMA_QXM0_SHRAM_ST_ADDR(i) (0x1200 + (0x4 * (i))) +#define QAIF_CDC_RDDMA_QXM0_SHRAM_ST_ADDR(i) (0x1240 + (0x4 * (i))) +#define QAIF_RDDMA_QXM1_SHRAM_LEN(i) (0x1300 + (0x4 * (i))) +#define QAIF_CDC_RDDMA_QXM1_SHRAM_LEN(i) (0x1340 + (0x4 * (i))) +#define QAIF_RDDMA_QXM0_SHRAM_LEN(i) (0x1400 + (0x4 * (i))) +#define QAIF_CDC_RDDMA_QXM0_SHRAM_LEN(i) (0x1440 + (0x4 * (i))) +#define QAIF_WRDMA_QXM1_SHRAM_ST_ADDR(i) (0x1500 + (0x4 * (i))) +#define QAIF_CDC_WRDMA_QXM1_SHRAM_ST_ADDR(i) (0x1540 + (0x4 * (i))) +#define QAIF_WRDMA_QXM0_SHRAM_ST_ADDR(i) (0x1600 + (0x4 * (i))) +#define QAIF_CDC_WRDMA_QXM0_SHRAM_ST_ADDR(i) (0x1640 + (0x4 * (i))) +#define QAIF_WRDMA_QXM1_SHRAM_LEN(i) (0x1700 + (0x4 * (i))) +#define QAIF_CDC_WRDMA_QXM1_SHRAM_LEN(i) (0x1740 + (0x4 * (i))) +#define QAIF_WRDMA_QXM0_SHRAM_LEN(i) (0x1800 + (0x4 * (i))) +#define QAIF_CDC_WRDMA_QXM0_SHRAM_LEN(i) (0x1840 + (0x4 * (i))) + +/* RDDMA */ +static inline u32 qaif_rddma_reg_addr(const struct qaif_variant *v, + u32 offset, u32 i) +{ + return v->rddma_reg_base + offset + v->rddma_stride * i; +} + +#define QAIF_RDDMA_CTL_REG(v, i) qaif_rddma_reg_addr(v, 0x00, (i)) +#define QAIF_RDDMA_CFG_REG(v, i) qaif_rddma_reg_addr(v, 0x04, (i)) +#define QAIF_RDDMA_BASE_ADDR_REG(v, i) qaif_rddma_reg_addr(v, 0x08, (i)) +#define QAIF_RDDMA_BUFF_LEN_REG(v, i) qaif_rddma_reg_addr(v, 0x10, (i)) +#define QAIF_RDDMA_CURR_ADDR_REG(v, i) qaif_rddma_reg_addr(v, 0x14, (i)) +#define QAIF_RDDMA_PERIOD_LEN_REG(v, i) qaif_rddma_reg_addr(v, 0x1C, (i)) +#define QAIF_RDDMA_PERIOD_CNT_REG(v, i) qaif_rddma_reg_addr(v, 0x20, (i)) +#define QAIF_RDDMA_SHRAM_WORDCNT_REG(v, i) qaif_rddma_reg_addr(v, 0x24, (i)) +#define QAIF_RDDMA_FRAME_STATUS_REG(v, i) qaif_rddma_reg_addr(v, 0x28, (i)) +#define QAIF_RDDMA_FRAME_STATUS_EXTN_REG(v, i) qaif_rddma_reg_addr(v, 0x2C, (i)) +#define QAIF_RDDMA_FRAME_STATUS_CLR_REG(v, i) qaif_rddma_reg_addr(v, 0x30, (i)) +#define QAIF_RDDMA_SET_BUFF_CNT_REG(v, i) qaif_rddma_reg_addr(v, 0x34, (i)) +#define QAIF_RDDMA_SET_PERIOD_CNT_REG(v, i) qaif_rddma_reg_addr(v, 0x38, (i)) +#define QAIF_RDDMA_STC_LSB_REG(v, i) qaif_rddma_reg_addr(v, 0x3C, (i)) +#define QAIF_RDDMA_STC_MSB_REG(v, i) qaif_rddma_reg_addr(v, 0x40, (i)) +#define QAIF_RDDMA_PERIOD_DET_STAT_REG(v, i) qaif_rddma_reg_addr(v, 0x44, (i)) +#define QAIF_RDDMA_PERIOD_DET_CLR_REG(v, i) qaif_rddma_reg_addr(v, 0x48, (i)) +#define QAIF_RDDMA_FORMAT_ERR_REG(v, i) qaif_rddma_reg_addr(v, 0x4C, (i)) +#define QAIF_RDDMA_AHB_BYPASS_REG(v, i) qaif_rddma_reg_addr(v, 0x50, (i)) +#define QAIF_RDDMA_SHUTDOWN_STAT_REG(v, i) qaif_rddma_reg_addr(v, 0x54, (i)) +#define QAIF_RDDMA_PADDING_CFG_REG(v, i) qaif_rddma_reg_addr(v, 0x58, (i)) +#define QAIF_RDDMA_STATUS_REG(v, i) qaif_rddma_reg_addr(v, 0x60, (i)) +#define QAIF_RDDMA_DBG_STATUS_REG(v, i) qaif_rddma_reg_addr(v, 0xFF0, (i)) + +static inline u32 qaif_codec_rddma_reg_addr(const struct qaif_variant *v, + u32 offset, u32 i) +{ + return v->codec_rddma_reg_base + offset + v->codec_rddma_stride * i; +} + +#define QAIF_CDC_RDDMA_CTL_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x00, (i)) +#define QAIF_CDC_RDDMA_CFG_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x04, (i)) +#define QAIF_CDC_RDDMA_BASE_ADDR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x08, (i)) +#define QAIF_CDC_RDDMA_BUFF_LEN_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x10, (i)) +#define QAIF_CDC_RDDMA_CURR_ADDR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x14, (i)) +#define QAIF_CDC_RDDMA_PERIOD_LEN_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x1C, (i)) +#define QAIF_CDC_RDDMA_PERIOD_CNT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x20, (i)) +#define QAIF_CDC_RDDMA_SHRAM_WORDCNT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x24, (i)) +#define QAIF_CDC_RDDMA_FRAME_STAT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x28, (i)) +#define QAIF_CDC_RDDMA_FRAME_STAT_EXTN_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x2C, (i)) +#define QAIF_CDC_RDDMA_FRAME_STAT_CLR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x30, (i)) +#define QAIF_CDC_RDDMA_SET_BUFF_CNT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x34, (i)) +#define QAIF_CDC_RDDMA_SET_PERIOD_CNT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x38, (i)) +#define QAIF_CDC_RDDMA_STC_LSB_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x3C, (i)) +#define QAIF_CDC_RDDMA_STC_MSB_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x40, (i)) +#define QAIF_CDC_RDDMA_PERIOD_DET_STAT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x44, (i)) +#define QAIF_CDC_RDDMA_PERIOD_DET_CLR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x48, (i)) +#define QAIF_CDC_RDDMA_FORMAT_ERR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x4C, (i)) +#define QAIF_CDC_RDDMA_AHB_BYPASS_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x50, (i)) +#define QAIF_CDC_RDDMA_SHUTDOWN_STAT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x54, (i)) +#define QAIF_CDC_RDDMA_PADDING_CFG_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x58, (i)) +#define QAIF_CDC_RDDMA_INTF_CFG_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x5C, (i)) +#define QAIF_CDC_RDDMA_STATUS_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x60, (i)) +#define QAIF_CDC_RDDMA_DBG_STATUS_REG(v, i) qaif_codec_rddma_reg_addr(v, 0xFF0, (i)) +#define QAIF_CDC_RDDMA_INTF_DBG_STATUS_REG(v, i) qaif_codec_rddma_reg_addr(v, 0xFF4, (i)) + +/* WRDMA */ +static inline u32 qaif_wrdma_reg_addr(const struct qaif_variant *v, + u32 offset, u32 i) +{ + return v->wrdma_reg_base + offset + v->wrdma_stride * i; +} + +#define QAIF_WRDMA_CTL_REG(v, i) qaif_wrdma_reg_addr(v, 0x00, (i)) +#define QAIF_WRDMA_CFG_REG(v, i) qaif_wrdma_reg_addr(v, 0x04, (i)) +#define QAIF_WRDMA_BASE_ADDR_REG(v, i) qaif_wrdma_reg_addr(v, 0x08, (i)) +#define QAIF_WRDMA_BUFF_LEN_REG(v, i) qaif_wrdma_reg_addr(v, 0x10, (i)) +#define QAIF_WRDMA_CURR_ADDR_REG(v, i) qaif_wrdma_reg_addr(v, 0x14, (i)) +#define QAIF_WRDMA_PERIOD_LEN_REG(v, i) qaif_wrdma_reg_addr(v, 0x1C, (i)) +#define QAIF_WRDMA_PERIOD_CNT_REG(v, i) qaif_wrdma_reg_addr(v, 0x20, (i)) +#define QAIF_WRDMA_SHRAM_WORDCNT_REG(v, i) qaif_wrdma_reg_addr(v, 0x24, (i)) +#define QAIF_WRDMA_FRAME_STATUS_REG(v, i) qaif_wrdma_reg_addr(v, 0x28, (i)) +#define QAIF_WRDMA_FRAME_STATUS_EXTN_REG(v, i) qaif_wrdma_reg_addr(v, 0x2C, (i)) +#define QAIF_WRDMA_FRAME_STATUS_CLR_REG(v, i) qaif_wrdma_reg_addr(v, 0x30, (i)) +#define QAIF_WRDMA_SET_BUFF_CNT_REG(v, i) qaif_wrdma_reg_addr(v, 0x34, (i)) +#define QAIF_WRDMA_SET_PERIOD_CNT_REG(v, i) qaif_wrdma_reg_addr(v, 0x38, (i)) +#define QAIF_WRDMA_STC_LSB_REG(v, i) qaif_wrdma_reg_addr(v, 0x3C, (i)) +#define QAIF_WRDMA_STC_MSB_REG(v, i) qaif_wrdma_reg_addr(v, 0x40, (i)) +#define QAIF_WRDMA_PERIOD_DET_STAT_REG(v, i) qaif_wrdma_reg_addr(v, 0x44, (i)) +#define QAIF_WRDMA_PERIOD_DET_CLR_REG(v, i) qaif_wrdma_reg_addr(v, 0x48, (i)) +#define QAIF_WRDMA_FORMAT_ERR_REG(v, i) qaif_wrdma_reg_addr(v, 0x4C, (i)) +#define QAIF_WRDMA_AHB_BYPASS_REG(v, i) qaif_wrdma_reg_addr(v, 0x50, (i)) +#define QAIF_WRDMA_SHUTDOWN_STAT_REG(v, i) qaif_wrdma_reg_addr(v, 0x54, (i)) +#define QAIF_WRDMA_DBG_STATUS_REG(v, i) qaif_wrdma_reg_addr(v, 0xFF0, (i)) + +static inline u32 qaif_codec_wrdma_reg_addr(const struct qaif_variant *v, + u32 offset, u32 i) +{ + return v->codec_wrdma_reg_base + offset + v->codec_wrdma_stride * i; +} + +#define QAIF_CDC_WRDMA_CTL_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x00, (i)) +#define QAIF_CDC_WRDMA_CFG_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x04, (i)) +#define QAIF_CDC_WRDMA_BASE_ADDR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x08, (i)) +#define QAIF_CDC_WRDMA_BUFF_LEN_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x10, (i)) +#define QAIF_CDC_WRDMA_CURR_ADDR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x14, (i)) +#define QAIF_CDC_WRDMA_PERIOD_LEN_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x1C, (i)) +#define QAIF_CDC_WRDMA_PERIOD_CNT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x20, (i)) +#define QAIF_CDC_WRDMA_SHRAM_WORDCNT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x24, (i)) +#define QAIF_CDC_WRDMA_FRAME_STAT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x28, (i)) +#define QAIF_CDC_WRDMA_FRAME_STAT_EXTN_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x2C, (i)) +#define QAIF_CDC_WRDMA_FRAME_STAT_CLR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x30, (i)) +#define QAIF_CDC_WRDMA_SET_BUFF_CNT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x34, (i)) +#define QAIF_CDC_WRDMA_SET_PERIOD_CNT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x38, (i)) +#define QAIF_CDC_WRDMA_STC_LSB_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x3C, (i)) +#define QAIF_CDC_WRDMA_STC_MSB_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x40, (i)) +#define QAIF_CDC_WRDMA_PERIOD_DET_STAT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x44, (i)) +#define QAIF_CDC_WRDMA_PERIOD_DET_CLR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x48, (i)) +#define QAIF_CDC_WRDMA_FORMAT_ERR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x4C, (i)) +#define QAIF_CDC_WRDMA_AHB_BYPASS_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x50, (i)) +#define QAIF_CDC_WRDMA_SHUTDOWN_STAT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x54, (i)) +#define QAIF_CDC_WRDMA_INTF_CFG_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x58, (i)) +#define QAIF_CDC_WRDMA_DBG_STATUS_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0xFF0, (i)) +#define QAIF_CDC_WRDMA_INTF_DBG_STATUS_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0xFF4, (i)) + +static inline u32 qaif_rddma_irq_reg_addr(const struct qaif_variant *v, + enum qaif_irq_type dma_type, + u32 offset) +{ + if (dma_type == QAIF_AIF_IRQ) + return v->rddma_irq_reg_base + offset + + v->rddma_irq_stride * v->ee; + return v->codec_rddma_irq_reg_base + offset + + v->codec_rddma_irq_stride * v->ee; +} + +/* RDDMA Period Interrupts */ +#define QAIF_EE_RDDMA_PERIOD_IRQ_EN_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x00) +#define QAIF_EE_RDDMA_PERIOD_IRQ_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x08) +#define QAIF_EE_RDDMA_PERIOD_IRQ_RAW_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x10) +#define QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x18) +#define QAIF_EE_RDDMA_PERIOD_IRQ_FORCE_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x20) +/* RDDMA Underflow Interrupts */ +#define QAIF_EE_RDDMA_UF_IRQ_EN_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x28) +#define QAIF_EE_RDDMA_UF_IRQ_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x30) +#define QAIF_EE_RDDMA_UF_IRQ_RAW_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x38) +#define QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x40) +#define QAIF_EE_RDDMA_UF_IRQ_FORCE_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x48) +/* RDDMA Error Response Interrupts */ +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_EN_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x50) +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x58) +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_RAW_STAT(v, i) qaif_rddma_irq_reg_addr(v, i, 0x60) +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x68) +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_FORCE_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x70) + +static inline u32 qaif_wrdma_irq_reg_addr(const struct qaif_variant *v, + enum qaif_irq_type dma_type, + u32 offset) +{ + if (dma_type == QAIF_AIF_IRQ) + return v->wrdma_irq_reg_base + offset + + v->wrdma_irq_stride * v->ee; + return v->codec_wrdma_irq_reg_base + offset + + v->codec_wrdma_irq_stride * v->ee; +} + +/* WRDMA Period Interrupts */ +#define QAIF_EE_WRDMA_PERIOD_IRQ_EN_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x00) +#define QAIF_EE_WRDMA_PERIOD_IRQ_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x08) +#define QAIF_EE_WRDMA_PERIOD_IRQ_RAW_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x10) +#define QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x18) +#define QAIF_EE_WRDMA_PERIOD_IRQ_FORCE_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x20) +/* WRDMA Overflow Interrupts */ +#define QAIF_EE_WRDMA_OF_IRQ_EN_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x28) +#define QAIF_EE_WRDMA_OF_IRQ_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x30) +#define QAIF_EE_WRDMA_OF_IRQ_RAW_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x38) +#define QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x40) +#define QAIF_EE_WRDMA_OF_IRQ_FORCE_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x48) +/* WRDMA Error Response Interrupts */ +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_EN_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x50) +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x58) +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_RAW_STAT(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x60) +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x68) +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_FORCE_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x70) + +static inline u32 qaif_dmacfg_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_CFG_REG(v, i) : + QAIF_CDC_WRDMA_CFG_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_CFG_REG(v, i) : QAIF_WRDMA_CFG_REG(v, i); +} + +static inline u32 qaif_dmactl_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_CTL_REG(v, i) : + QAIF_CDC_WRDMA_CTL_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_CTL_REG(v, i) : QAIF_WRDMA_CTL_REG(v, i); +} + +static inline u32 qaif_dma_shutdown_stat_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_SHUTDOWN_STAT_REG(v, i) : + QAIF_CDC_WRDMA_SHUTDOWN_STAT_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_SHUTDOWN_STAT_REG(v, i) : + QAIF_WRDMA_SHUTDOWN_STAT_REG(v, i); +} + +static inline u32 qaif_dmabuff_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_BUFF_LEN_REG(v, i) : + QAIF_CDC_WRDMA_BUFF_LEN_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_BUFF_LEN_REG(v, i) : QAIF_WRDMA_BUFF_LEN_REG(v, i); +} + +static inline u32 qaif_dmacurr_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_CURR_ADDR_REG(v, i) : + QAIF_CDC_WRDMA_CURR_ADDR_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_CURR_ADDR_REG(v, i) : QAIF_WRDMA_CURR_ADDR_REG(v, i); +} + +static inline u32 qaif_dmaper_cnt_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_PERIOD_CNT_REG(v, i) : + QAIF_CDC_WRDMA_PERIOD_CNT_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_PERIOD_CNT_REG(v, i) : QAIF_WRDMA_PERIOD_CNT_REG(v, i); +} + +static inline u32 qaif_dmaper_len_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_PERIOD_LEN_REG(v, i) : + QAIF_CDC_WRDMA_PERIOD_LEN_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_PERIOD_LEN_REG(v, i) : QAIF_WRDMA_PERIOD_LEN_REG(v, i); +} + +static inline u32 qaif_dmabase_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_BASE_ADDR_REG(v, i) : + QAIF_CDC_WRDMA_BASE_ADDR_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_BASE_ADDR_REG(v, i) : QAIF_WRDMA_BASE_ADDR_REG(v, i); +} + +static inline u32 qaif_sid_map_reg(int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_SID_MAP_REG : + QAIF_CDC_WRDMA_SID_MAP_REG; + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_SID_MAP_REG : QAIF_WRDMA_SID_MAP_REG; +} + +#endif /* SND_SOC_QCOM_QAIF_REG_H */ From dd77abdd3496557240c06a29240666d1e33a0831 Mon Sep 17 00:00:00 2001 From: Harendra Gautam Date: Tue, 13 Oct 2026 11:21:27 +0530 Subject: [PATCH 776/924] ASoC: qcom: Add QAIF CPU DAI ops, regmap, DT parsing and platform init Add the QAIF CPU DAI driver to support Qualcomm audio interface paths used for CDC DMA and serial AIF audio links. Implement CIF DAI operations for CDC DMA paths, including channel mask programming and dynamic clock control. Implement AIF DAI operations for MI2S, TDM and PCM paths, including format setup, slot and lane configuration, bit clock management and stream enable handling. Also add the QAIF MMIO regmap setup, parse per-interface configuration from devicetree, manage required clocks and register the CPU DAI component. Signed-off-by: Harendra Gautam Reviewed-by: Ajay Kumar Nandam Link: https://patch.msgid.link/20261013-b4-qaif-shikra-audio-v9-4-f3022e28f2fd@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qaif-cpu.c | 886 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 886 insertions(+) create mode 100644 sound/soc/qcom/qaif-cpu.c diff --git a/sound/soc/qcom/qaif-cpu.c b/sound/soc/qcom/qaif-cpu.c new file mode 100644 index 000000000000..ad7268e2ace0 --- /dev/null +++ b/sound/soc/qcom/qaif-cpu.c @@ -0,0 +1,886 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * qaif-cpu.c -- ALSA SoC CPU-Platform DAI driver for QTi QAIF + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qaif-reg.h" +#include "qaif.h" + +/* CIF INTF CFG register bit positions */ +#define QAIF_CIF_INTF_16BIT_UNPACK_BIT BIT(0) +#define QAIF_CIF_INTF_DYNCLK_BIT BIT(2) +#define QAIF_CIF_INTF_FS_SEL_MASK GENMASK(7, 4) +#define QAIF_CIF_INTF_ACTIVE_CH_MASK GENMASK(27, 12) +#define QAIF_CIF_INTF_ACTIVE_CH_SHIFT 12 + +/* AIF CTL register (QAIF_AUD_INTF_CTL_REG) bit positions */ +#define QAIF_AIF_CTL_ENABLE_BIT BIT(0) +#define QAIF_AIF_CTL_ENABLE_TX_BIT BIT(4) +#define QAIF_AIF_CTL_ENABLE_RX_BIT BIT(8) + +/* AIF SYNC CFG register (QAIF_AUD_INTF_SYNC_CFG_REG) bit positions */ +#define QAIF_AIF_SYNC_SRC_BIT BIT(0) +#define QAIF_AIF_SYNC_MODE_MASK GENMASK(5, 4) +#define QAIF_AIF_SYNC_MODE_SHIFT 4 +#define QAIF_AIF_SYNC_DELAY_MASK GENMASK(9, 8) +#define QAIF_AIF_SYNC_DELAY_SHIFT 8 +#define QAIF_AIF_SYNC_INV_BIT BIT(12) + +/* SYNC_MODE field values */ +#define QAIF_AIF_SYNC_MODE_SHORT 0 +#define QAIF_AIF_SYNC_MODE_ONE_SLOT 1 +#define QAIF_AIF_SYNC_MODE_LONG 2 + +/* SYNC_DELAY field values */ +#define QAIF_AIF_SYNC_DELAY_SAME 0 +#define QAIF_AIF_SYNC_DELAY_ONE 1 + +/* AIF BIT WIDTH CFG register (QAIF_AUD_INTF_BIT_WIDTH_CFG_REG) bit positions */ +#define QAIF_AIF_SLOT_WIDTH_TX_MASK GENMASK(4, 0) +#define QAIF_AIF_SLOT_WIDTH_RX_MASK GENMASK(12, 8) +#define QAIF_AIF_SLOT_WIDTH_RX_SHIFT 8 +#define QAIF_AIF_SAMPLE_WIDTH_TX_MASK GENMASK(20, 16) +#define QAIF_AIF_SAMPLE_WIDTH_TX_SHIFT 16 +#define QAIF_AIF_SAMPLE_WIDTH_RX_MASK GENMASK(28, 24) +#define QAIF_AIF_SAMPLE_WIDTH_RX_SHIFT 24 + +/* + * slot_width is programmed 0-based into the 5-bit SLOT_WIDTH field and the + * frame length (slots * slot_width) 0-based into the 10-bit BITS_PER_LANE + * field, so these are the largest values the registers can represent. + */ +#define QAIF_AIF_SLOT_WIDTH_MAX 32 +#define QAIF_AIF_FRAME_BITS_MAX 1024 + +/* AIF FRAME CFG register (QAIF_AUD_INTF_FRAME_CFG_REG) bit positions */ +#define QAIF_AIF_BITS_PER_LANE_MASK GENMASK(9, 0) + +/* AIF LANE CFG register (QAIF_AUD_INTF_LANE_CFG_REG) bit positions */ +#define QAIF_AIF_LANE_DIR_MASK GENMASK(7, 0) +#define QAIF_AIF_LANE_EN_MASK GENMASK(15, 8) +#define QAIF_AIF_LANE_EN_SHIFT 8 +#define QAIF_AIF_CTRL_DATA_OE_BIT BIT(16) +#define QAIF_AIF_LOOPBACK_EN_BIT BIT(31) + +/* AIF MI2S CFG register (QAIF_AUD_INTF_MI2S_CFG_REG) bit positions */ +#define QAIF_AIF_MONO_MODE_TX_BIT BIT(0) +#define QAIF_AIF_MONO_MODE_RX_BIT BIT(1) + +/* AIF CFG register (QAIF_AUD_INTF_CFG_REG) bit positions */ +#define QAIF_AIF_FULL_CYCLE_EN_BIT BIT(0) + +static int qaif_cif_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + unsigned int dai_id = dai->driver->id; + unsigned int regval; + u32 reg; + int idx; + + switch (params_channels(params)) { + case 1: + regval = QAIF_CIF_DMA_INTF_ONE_CHANNEL; + break; + case 2: + regval = QAIF_CIF_DMA_INTF_TWO_CHANNEL; + break; + case 4: + regval = QAIF_CIF_DMA_INTF_FOUR_CHANNEL; + break; + case 6: + regval = QAIF_CIF_DMA_INTF_SIX_CHANNEL; + break; + case 8: + regval = QAIF_CIF_DMA_INTF_EIGHT_CHANNEL; + break; + default: + dev_dbg(rtd->dev, "unsupported channel count %u\n", + params_channels(params)); + return -EINVAL; + } + + idx = v->get_dma_idx(dai_id); + if (idx < 0) { + dev_err(rtd->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + reg = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_INTF_CFG_REG(v, idx) : + QAIF_CDC_WRDMA_INTF_CFG_REG(v, idx); + + return regmap_update_bits(drvdata->audio_qaif_map, reg, + QAIF_CIF_INTF_ACTIVE_CH_MASK, + regval << QAIF_CIF_INTF_ACTIVE_CH_SHIFT); +} + +static int qaif_cif_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + unsigned int dai_id = dai->driver->id; + struct regmap *map = drvdata->audio_qaif_map; + u32 reg; + int idx, ret; + + idx = v->get_dma_idx(dai_id); + if (idx < 0) { + dev_err(rtd->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + reg = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_INTF_CFG_REG(v, idx) : + QAIF_CDC_WRDMA_INTF_CFG_REG(v, idx); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = regmap_update_bits(map, reg, + QAIF_CIF_INTF_DYNCLK_BIT, + QAIF_CIF_INTF_DYNCLK_BIT); + if (ret) + return ret; + ret = regmap_update_bits(map, reg, QAIF_CIF_INTF_FS_SEL_MASK, 0); + if (ret) + return ret; + return regmap_update_bits(map, reg, + QAIF_CIF_INTF_16BIT_UNPACK_BIT, + QAIF_CIF_INTF_16BIT_UNPACK_BIT); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(map, reg, QAIF_CIF_INTF_DYNCLK_BIT, 0); + if (ret) + return ret; + return regmap_update_bits(map, reg, + QAIF_CIF_INTF_16BIT_UNPACK_BIT, 0); + default: + dev_err(rtd->dev, "invalid trigger cmd %d\n", cmd); + return -EINVAL; + } +} + +static int qaif_cif_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = clk_prepare_enable(drvdata->aud_dma_clk); + if (ret) { + dev_err(dai->dev, "error enabling aud_dma clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(drvdata->aud_dma_mem_clk); + if (ret) { + dev_err(dai->dev, "error enabling aud_dma_mem clk: %d\n", ret); + clk_disable_unprepare(drvdata->aud_dma_clk); + return ret; + } + return 0; +} + +static void qaif_cif_daiops_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(drvdata->aud_dma_mem_clk); + clk_disable_unprepare(drvdata->aud_dma_clk); +} + +/* + * asoc_qcom_qaif_cif_dai_ops - DAI ops for QAIF CIF (CDC DMA) paths + * + * Provides startup, shutdown, hw_params and trigger callbacks for the + * Codec Interface DMA (CIF) DAI. startup/shutdown enable and disable the + * audio DMA clocks. Used by Shikra machine drivers for Bolero CDC DMA + * RX, TX and VA TX paths. + */ +const struct snd_soc_dai_ops asoc_qcom_qaif_cif_dai_ops = { + .startup = qaif_cif_daiops_startup, + .shutdown = qaif_cif_daiops_shutdown, + .hw_params = qaif_cif_daiops_hw_params, + .trigger = qaif_cif_daiops_trigger, +}; +EXPORT_SYMBOL_GPL(asoc_qcom_qaif_cif_dai_ops); + +static int qaif_aif_cpu_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + int idx, ret; + + idx = v->get_dma_idx(dai->driver->id); + if (idx < 0) { + dev_err(dai->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + ret = clk_prepare_enable(drvdata->aud_dma_clk); + if (ret) { + dev_err(dai->dev, "error enabling aud_dma clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(drvdata->aud_dma_mem_clk); + if (ret) { + dev_err(dai->dev, "error enabling aud_dma_mem clk: %d\n", ret); + clk_disable_unprepare(drvdata->aud_dma_clk); + return ret; + } + + ret = clk_prepare_enable(drvdata->mi2s_bit_clk[idx]); + if (ret) { + dev_err(dai->dev, "error enabling mi2s bit clk: %d\n", ret); + clk_disable_unprepare(drvdata->aud_dma_mem_clk); + clk_disable_unprepare(drvdata->aud_dma_clk); + } + return ret; +} + +static void qaif_aif_cpu_daiops_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + struct qaif_aif_config *cfg; + unsigned int enable_bit; + int idx = v->get_dma_idx(dai->driver->id); + int ret; + + if (idx < 0) { + dev_err(dai->dev, "Invalid DMA index: %d\n", idx); + return; + } + + cfg = &drvdata->aif_intf_cfg[idx]; + if (cfg->loopback_en) + enable_bit = QAIF_AIF_CTL_ENABLE_BIT; + else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + enable_bit = QAIF_AIF_CTL_ENABLE_TX_BIT; + else + enable_bit = QAIF_AIF_CTL_ENABLE_RX_BIT; + + ret = regmap_update_bits(drvdata->audio_qaif_map, + QAIF_AUD_INTF_CTL_REG(idx), enable_bit, 0); + if (ret) + dev_err(dai->dev, "error clearing AIF enable bit: %d\n", ret); + + clk_disable_unprepare(drvdata->mi2s_bit_clk[idx]); + clk_disable_unprepare(drvdata->aud_dma_mem_clk); + clk_disable_unprepare(drvdata->aud_dma_clk); +} + +static int qaif_aif_cpu_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + const struct qaif_aif_config *cfg; + struct regmap *map = drvdata->audio_qaif_map; + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + unsigned int slot_width; + int bitwidth, idx, ret; + + idx = v->get_dma_idx(dai->driver->id); + if (idx < 0) { + dev_err(dai->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + cfg = &drvdata->aif_intf_cfg[idx]; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(dai->dev, "invalid bit width given: %d\n", bitwidth); + return bitwidth; + } + + /* + * The sync, lane and frame configuration are shared by both directions + * of an AIF and are taken from aif_intf_cfg[idx], which is fixed at + * probe and via set_fmt()/set_tdm_slot(). Both directions program + * identical values here, so concurrent playback and capture hw_params() + * calls are idempotent. + */ + ret = regmap_update_bits(map, QAIF_AUD_INTF_SYNC_CFG_REG(idx), + QAIF_AIF_SYNC_SRC_BIT | + QAIF_AIF_SYNC_MODE_MASK | + QAIF_AIF_SYNC_DELAY_MASK | + QAIF_AIF_SYNC_INV_BIT, + (cfg->sync_src ? QAIF_AIF_SYNC_SRC_BIT : 0) | + (cfg->sync_mode << QAIF_AIF_SYNC_MODE_SHIFT) | + (cfg->sync_delay << QAIF_AIF_SYNC_DELAY_SHIFT) | + (cfg->invert_sync ? QAIF_AIF_SYNC_INV_BIT : 0)); + if (ret) + return ret; + + ret = regmap_update_bits(map, QAIF_AUD_INTF_LANE_CFG_REG(idx), + QAIF_AIF_LANE_DIR_MASK | + QAIF_AIF_LANE_EN_MASK | + QAIF_AIF_CTRL_DATA_OE_BIT | + QAIF_AIF_LOOPBACK_EN_BIT, + cfg->lane_dir_mask | + (cfg->lane_en_mask << QAIF_AIF_LANE_EN_SHIFT) | + (cfg->ctrl_data_oe ? QAIF_AIF_CTRL_DATA_OE_BIT : 0) | + (cfg->loopback_en ? QAIF_AIF_LOOPBACK_EN_BIT : 0)); + if (ret) + return ret; + + ret = regmap_update_bits(map, QAIF_AUD_INTF_CFG_REG(idx), + QAIF_AIF_FULL_CYCLE_EN_BIT, + cfg->full_cycle_en ? QAIF_AIF_FULL_CYCLE_EN_BIT : 0); + if (ret) + return ret; + + if (qaif_is_aif_mi2s_port(dai->driver->id) && !cfg->slot_width) { + slot_width = bitwidth; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + drvdata->aif_intf_cfg[idx].slot_en_tx_mask = + GENMASK(channels - 1, 0); + else + drvdata->aif_intf_cfg[idx].slot_en_rx_mask = + GENMASK(channels - 1, 0); + drvdata->aif_intf_cfg[idx].slot_num = QAIF_MI2S_SLOTS; + } else { + slot_width = cfg->slot_width; + if (!slot_width || !cfg->slot_num) { + dev_err(dai->dev, "invalid TDM slot config: width=%u num=%u\n", + slot_width, cfg->slot_num); + return -EINVAL; + } + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = regmap_update_bits(map, QAIF_AUD_INTF_BIT_WIDTH_CFG_REG(idx), + QAIF_AIF_SLOT_WIDTH_TX_MASK | + QAIF_AIF_SAMPLE_WIDTH_TX_MASK, + QAIF_AIF_SLOT_WIDTH(slot_width) | + (QAIF_AIF_SAMPLE_WIDTH(bitwidth) << + QAIF_AIF_SAMPLE_WIDTH_TX_SHIFT)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_AUD_INTF_ACTV_SLOT_EN_TX_REG(idx), + cfg->slot_en_tx_mask); + if (ret) + return ret; + ret = regmap_update_bits(map, QAIF_AUD_INTF_MI2S_CFG_REG(idx), + QAIF_AIF_MONO_MODE_TX_BIT, + (channels < 2) ? QAIF_AIF_MONO_MODE_TX_BIT : 0); + } else { + ret = regmap_update_bits(map, QAIF_AUD_INTF_BIT_WIDTH_CFG_REG(idx), + QAIF_AIF_SLOT_WIDTH_RX_MASK | + QAIF_AIF_SAMPLE_WIDTH_RX_MASK, + (QAIF_AIF_SLOT_WIDTH(slot_width) << + QAIF_AIF_SLOT_WIDTH_RX_SHIFT) | + (QAIF_AIF_SAMPLE_WIDTH(bitwidth) << + QAIF_AIF_SAMPLE_WIDTH_RX_SHIFT)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_AUD_INTF_ACTV_SLOT_EN_RX_REG(idx), + cfg->slot_en_rx_mask); + if (ret) + return ret; + ret = regmap_update_bits(map, QAIF_AUD_INTF_MI2S_CFG_REG(idx), + QAIF_AIF_MONO_MODE_RX_BIT, + (channels < 2) ? QAIF_AIF_MONO_MODE_RX_BIT : 0); + } + if (ret) + return ret; + + ret = regmap_update_bits(map, QAIF_AUD_INTF_FRAME_CFG_REG(idx), + QAIF_AIF_BITS_PER_LANE_MASK, + (slot_width * cfg->slot_num) - 1); + if (ret) + return ret; + + return 0; +} + +static int qaif_aif_cpu_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + const struct qaif_aif_config *cfg; + unsigned int enable_bit; + int idx, ret; + + idx = v->get_dma_idx(dai->driver->id); + if (idx < 0) { + dev_err(dai->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + cfg = &drvdata->aif_intf_cfg[idx]; + if (cfg->loopback_en) + enable_bit = QAIF_AIF_CTL_ENABLE_BIT; + else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + enable_bit = QAIF_AIF_CTL_ENABLE_TX_BIT; + else + enable_bit = QAIF_AIF_CTL_ENABLE_RX_BIT; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = regmap_update_bits(drvdata->audio_qaif_map, + QAIF_AUD_INTF_CTL_REG(idx), enable_bit, enable_bit); + if (ret) + dev_err(dai->dev, "error setting AIF enable bit: %d\n", ret); + return ret; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(drvdata->audio_qaif_map, + QAIF_AUD_INTF_CTL_REG(idx), enable_bit, 0); + if (ret) + dev_err(dai->dev, "error clearing AIF enable bit: %d\n", ret); + return ret; + default: + dev_err(dai->dev, "invalid trigger cmd %d\n", cmd); + return -EINVAL; + } +} + +static int qaif_aif_cpu_daiops_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, int slots, + int slot_width) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + int idx = v->get_dma_idx(dai->driver->id); + struct qaif_aif_config *cfg; + + if (idx < 0) + return -EINVAL; + + if (slots <= 0 || slot_width <= 0) + return -EINVAL; + + if (slot_width > QAIF_AIF_SLOT_WIDTH_MAX || + slots * slot_width > QAIF_AIF_FRAME_BITS_MAX) + return -EINVAL; + + cfg = &drvdata->aif_intf_cfg[idx]; + cfg->slot_num = slots; + cfg->slot_width = slot_width; + cfg->slot_en_tx_mask = tx_mask; + cfg->slot_en_rx_mask = rx_mask; + + return 0; +} + +static int qaif_aif_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, + int dir) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + int idx = v->get_dma_idx(dai->driver->id); + + if (idx < 0) + return -EINVAL; + + /* + * The bit clock is prepared and enabled for the lifetime of the + * stream by startup()/shutdown(); only the rate is set here. + */ + return clk_set_rate(drvdata->mi2s_bit_clk[idx], freq); +} + +static int qaif_aif_cpu_daiops_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + int idx = v->get_dma_idx(dai->driver->id); + struct qaif_aif_config *cfg; + + if (idx < 0) + return -EINVAL; + + cfg = &drvdata->aif_intf_cfg[idx]; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_LONG; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_ONE; + cfg->invert_sync = true; + break; + case SND_SOC_DAIFMT_DSP_A: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_ONE_SLOT; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_ONE; + break; + case SND_SOC_DAIFMT_DSP_B: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_SHORT; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_SAME; + break; + default: + dev_err(dai->dev, "unsupported format 0x%x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + cfg->sync_src = 1; + break; + case SND_SOC_DAIFMT_CBC_CFC: + cfg->sync_src = 0; + break; + default: + dev_err(dai->dev, "unsupported clock provider mode\n"); + return -EINVAL; + } + + /* + * For DSP_A/DSP_B (TDM/PCM) modes, sync inversion is board-configurable + * via the NB_IF flag. For I2S, invert_sync is fixed above and must + * not be overridden here. + */ + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S) { + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + cfg->invert_sync = (fmt & SND_SOC_DAIFMT_INV_MASK) == + SND_SOC_DAIFMT_NB_IF; + break; + default: + dev_err(dai->dev, "bit clock inversion is not supported\n"); + return -EINVAL; + } + } + + return 0; +} + +/* + * asoc_qcom_qaif_aif_cpu_dai_ops - DAI ops for QAIF AIF (MI2S/TDM/PCM) paths + * + * Provides startup, shutdown, hw_params and trigger callbacks + * for the Unified Audio Interface (AIF) DAI. Used by Shikra machine + * drivers for MI2S, TDM and PCM serial audio paths. + */ +const struct snd_soc_dai_ops asoc_qcom_qaif_aif_cpu_dai_ops = { + .set_fmt = qaif_aif_cpu_daiops_set_fmt, + .set_tdm_slot = qaif_aif_cpu_daiops_set_tdm_slot, + .set_sysclk = qaif_aif_cpu_daiops_set_sysclk, + .startup = qaif_aif_cpu_daiops_startup, + .shutdown = qaif_aif_cpu_daiops_shutdown, + .hw_params = qaif_aif_cpu_daiops_hw_params, + .trigger = qaif_aif_cpu_daiops_trigger, +}; +EXPORT_SYMBOL_GPL(asoc_qcom_qaif_aif_cpu_dai_ops); + +static const struct snd_soc_component_driver qaif_cpu_comp_driver = { + .name = "qaif-cpu", +}; + +static const struct regmap_config audio_qaif_regmap_config = { + .name = "audio_qaif_cpu", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .cache_type = REGCACHE_NONE, +}; + +static int of_qaif_parse_aif_intf_cfg(struct device *dev, + struct qaif_drv_data *data) +{ + const struct qaif_variant *v = data->variant; + struct device_node *np = dev->of_node; + struct device_node *intf_np; + struct qaif_aif_config *cfg; + int ret; + u32 dai_id; + int intf_idx; + int num_interfaces = 0; + u32 val_buf[QAIF_MAX_LANES]; + int j, n; + unsigned long configured_intf_mask = 0; + + for_each_child_of_node(np, intf_np) { + if (!of_property_present(intf_np, "reg")) + continue; + + if (num_interfaces >= QAIF_MAX_AIF_CFG_CNT) { + dev_warn(dev, "Too many AIF interfaces, limiting to %d\n", + QAIF_MAX_AIF_CFG_CNT); + of_node_put(intf_np); + break; + } + + ret = of_property_read_u32(intf_np, "reg", &dai_id); + if (ret) { + dev_err(dev, "Missing reg for interface %pOFn\n", intf_np); + of_node_put(intf_np); + return ret; + } + + intf_idx = v->get_dma_idx(dai_id); + if (intf_idx < 0) { + dev_err(dev, "Invalid DAI ID %d for interface %pOFn\n", + dai_id, intf_np); + of_node_put(intf_np); + return -EINVAL; + } + if (intf_idx >= ARRAY_SIZE(data->aif_intf_cfg)) { + dev_err(dev, "DAI ID %d maps to out-of-range intf_idx %d\n", + dai_id, intf_idx); + of_node_put(intf_np); + return -EINVAL; + } + + if (configured_intf_mask & BIT(intf_idx)) { + dev_err(dev, "Duplicate reg %d for interface %pOFn\n", + dai_id, intf_np); + of_node_put(intf_np); + return -EINVAL; + } + configured_intf_mask |= BIT(intf_idx); + + cfg = &data->aif_intf_cfg[intf_idx]; + if (v->aif_full_cycle_en) + cfg->full_cycle_en = v->aif_full_cycle_en[intf_idx]; + if (v->aif_ctrl_data_oe) + cfg->ctrl_data_oe = v->aif_ctrl_data_oe[intf_idx]; + if (v->aif_loopback_en) + cfg->loopback_en = v->aif_loopback_en[intf_idx]; + + switch (snd_soc_daifmt_parse_format(intf_np, NULL) & + SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_LONG; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_ONE; + cfg->invert_sync = true; + break; + case SND_SOC_DAIFMT_DSP_A: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_ONE_SLOT; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_ONE; + break; + case SND_SOC_DAIFMT_DSP_B: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_SHORT; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_SAME; + break; + default: + break; + } + + if (!qaif_is_aif_mi2s_port(dai_id)) { + ret = of_property_read_u32(intf_np, "dai-tdm-slot-width", + &cfg->slot_width); + if (ret || !cfg->slot_width) { + dev_err(dev, "Missing/invalid dai-tdm-slot-width for %pOFn\n", + intf_np); + of_node_put(intf_np); + return ret ? ret : -EINVAL; + } + ret = of_property_read_u32(intf_np, "dai-tdm-slot-num", + &cfg->slot_num); + if (ret || !cfg->slot_num) { + dev_err(dev, "Missing/invalid dai-tdm-slot-num for %pOFn\n", + intf_np); + of_node_put(intf_np); + return ret ? ret : -EINVAL; + } + snd_soc_of_get_slot_mask(intf_np, "dai-tdm-slot-rx-mask", + &cfg->slot_en_rx_mask); + snd_soc_of_get_slot_mask(intf_np, "dai-tdm-slot-tx-mask", + &cfg->slot_en_tx_mask); + if (!cfg->slot_en_rx_mask && !cfg->slot_en_tx_mask) { + dev_err(dev, "No active TDM slots for %pOFn\n", + intf_np); + of_node_put(intf_np); + return -EINVAL; + } + } + + n = of_property_read_variable_u32_array(intf_np, + "qcom,qaif-aif-lane-map", + val_buf, 1, QAIF_MAX_LANES); + if (n < 0) { + dev_err(dev, "Missing qcom,qaif-aif-lane-map for %pOFn\n", + intf_np); + of_node_put(intf_np); + return n; + } + for (j = 0; j < n; j++) { + if (val_buf[j] > 1) { + dev_err(dev, "Invalid lane-map value %u for %pOFn\n", + val_buf[j], intf_np); + of_node_put(intf_np); + return -EINVAL; + } + cfg->lane_en_mask |= BIT(j); + if (val_buf[j]) + cfg->lane_dir_mask |= BIT(j); + } + + num_interfaces++; + } + + return 0; +} + +/** + * asoc_qcom_qaif_cpu_platform_probe - Probe the QAIF CPU and platform driver + * @pdev: Platform device + * + * Initialises the QAIF regmap, parses DT, sets up clocks and registers + * the CPU DAI component and PCM platform. + * + * Return: 0 on success, negative error code on failure. + */ +int asoc_qcom_qaif_cpu_platform_probe(struct platform_device *pdev) +{ + struct regmap_config regmap_cfg = audio_qaif_regmap_config; + struct qaif_drv_data *drvdata; + struct resource *res; + const struct qaif_variant *variant; + struct device *dev = &pdev->dev; + const struct of_device_id *match; + int ret, i, dai_id, idx; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return dev_err_probe(dev, -ENOMEM, "Failed to allocate driver data\n"); + platform_set_drvdata(pdev, drvdata); + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) + return dev_err_probe(dev, -EINVAL, "No matching device data\n"); + + drvdata->variant = match->data; + variant = drvdata->variant; + + if (!variant->get_dma_idx) + return dev_err_probe(dev, -EINVAL, + "variant is missing get_dma_idx callback\n"); + + ret = of_qaif_parse_aif_intf_cfg(dev, drvdata); + if (ret) + return dev_err_probe(dev, ret, "Failed to parse aif interfaces\n"); + + drvdata->audio_qaif = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(drvdata->audio_qaif)) + return dev_err_probe(dev, PTR_ERR(drvdata->audio_qaif), + "Failed to ioremap QAIF registers\n"); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return dev_err_probe(dev, -EINVAL, "Failed to get MMIO resource\n"); + + regmap_cfg.max_register = resource_size(res) - regmap_cfg.reg_stride; + drvdata->audio_qaif_map = devm_regmap_init_mmio(dev, drvdata->audio_qaif, + ®map_cfg); + if (IS_ERR(drvdata->audio_qaif_map)) + return dev_err_probe(dev, PTR_ERR(drvdata->audio_qaif_map), + "Failed to init regmap\n"); + + drvdata->aud_dma_clk = devm_clk_get(dev, "aud_dma"); + if (IS_ERR(drvdata->aud_dma_clk)) + return dev_err_probe(dev, PTR_ERR(drvdata->aud_dma_clk), + "Failed to get aud_dma clock\n"); + + drvdata->aud_dma_mem_clk = devm_clk_get(dev, "aud_dma_mem"); + if (IS_ERR(drvdata->aud_dma_mem_clk)) + return dev_err_probe(dev, PTR_ERR(drvdata->aud_dma_mem_clk), + "Failed to get aud_dma_mem clock\n"); + + for (i = 0; i < variant->num_dai; i++) { + dai_id = variant->dai_driver[i].id; + if (qaif_is_cif_port(dai_id)) + continue; + idx = variant->get_dma_idx(dai_id); + if (idx < 0) + continue; + + if (drvdata->mi2s_bit_clk[idx]) + continue; + + drvdata->mi2s_bit_clk[idx] = + devm_clk_get(dev, variant->dai_bit_clk_names[idx]); + if (IS_ERR(drvdata->mi2s_bit_clk[idx])) + return dev_err_probe(dev, PTR_ERR(drvdata->mi2s_bit_clk[idx]), + "Failed to get bit clock %d\n", idx); + } + + drvdata->num_clks = variant->num_clks; + drvdata->clks = devm_kcalloc(dev, drvdata->num_clks, + sizeof(*drvdata->clks), GFP_KERNEL); + if (!drvdata->clks) + return dev_err_probe(dev, -ENOMEM, "Failed to allocate clocks\n"); + + 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) + return dev_err_probe(dev, ret, "Failed to get clocks\n"); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + ret = devm_snd_soc_register_component(dev, &qaif_cpu_comp_driver, + variant->dai_driver, variant->num_dai); + if (ret) + return dev_err_probe(dev, ret, "error registering cpu driver\n"); + + return asoc_qcom_qaif_platform_register(pdev); +} +EXPORT_SYMBOL_GPL(asoc_qcom_qaif_cpu_platform_probe); + +static int asoc_qcom_qaif_runtime_suspend(struct device *dev) +{ + struct qaif_drv_data *drvdata = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks); + return 0; +} + +static int asoc_qcom_qaif_runtime_resume(struct device *dev) +{ + struct qaif_drv_data *drvdata = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(drvdata->num_clks, drvdata->clks); +} + +EXPORT_GPL_DEV_PM_OPS(asoc_qcom_qaif_pm_ops) = { + RUNTIME_PM_OPS(asoc_qcom_qaif_runtime_suspend, + asoc_qcom_qaif_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +MODULE_DESCRIPTION("Qualcomm Audio Interface (QAIF) CPU DAI driver"); +MODULE_AUTHOR("Harendra Gautam "); +MODULE_LICENSE("GPL"); From 02298cc296e640c2ebc49407a76bb02c0df488f4 Mon Sep 17 00:00:00 2001 From: Harendra Gautam Date: Tue, 13 Oct 2026 11:21:28 +0530 Subject: [PATCH 777/924] ASoC: soc-core: Add snd_soc_of_xlate_dai_name() generic helper Some ASoC CPU DAI drivers need to resolve a sound-dai phandle argument to a DAI name by matching the argument against the component's DAI IDs. lpass-cpu carries a local implementation of this lookup. Add snd_soc_of_xlate_dai_name() to soc-core.c as a generic helper that can be assigned directly to the .of_xlate_dai_name component driver callback. It iterates the component's DAI list using for_each_component_dais() and matches by DAI ID. Signed-off-by: Harendra Gautam Link: https://patch.msgid.link/20261013-b4-qaif-shikra-audio-v9-5-f3022e28f2fd@oss.qualcomm.com Signed-off-by: Mark Brown --- include/sound/soc.h | 3 +++ sound/soc/soc-core.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/include/sound/soc.h b/include/sound/soc.h index 08fdee15dba7..19d780bb5c0b 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1323,6 +1323,9 @@ int snd_soc_of_parse_tdm_slot(struct device_node *np, unsigned int *rx_mask, unsigned int *slots, unsigned int *slot_width); +int snd_soc_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name); void snd_soc_of_parse_node_prefix(struct device_node *np, struct snd_soc_codec_conf *codec_conf, struct device_node *of_node, diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 69181cc0258b..8e35d6816189 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2899,6 +2899,39 @@ int snd_soc_of_parse_tdm_slot(struct device_node *np, } EXPORT_SYMBOL_GPL(snd_soc_of_parse_tdm_slot); +/** + * snd_soc_of_xlate_dai_name - Resolve a sound-dai phandle argument to a + * DAI name by searching the component DAI list. + * @component: ASoC component to search. + * @args: Phandle arguments from the sound-dai property; args[0] is the + * DAI ID. + * @dai_name: Output pointer set to the matched DAI name on success. + * + * Return: 0 on success, -EINVAL if args_count != 1 or no match is found. + */ +int snd_soc_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name) +{ + struct snd_soc_dai *dai; + int id; + + if (args->args_count != 1) + return -EINVAL; + + id = args->args[0]; + + for_each_component_dais(component, dai) { + if (dai->driver->id == id) { + *dai_name = snd_soc_dai_name(dai); + return 0; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_of_xlate_dai_name); + void snd_soc_dlc_use_cpu_as_platform(struct snd_soc_dai_link_component *platforms, struct snd_soc_dai_link_component *cpus) { From 76c5ecd4cafea41498c194d64089ad58b7cddf0f Mon Sep 17 00:00:00 2001 From: Harendra Gautam Date: Tue, 13 Oct 2026 11:21:29 +0530 Subject: [PATCH 778/924] ASoC: qcom: Switch lpass-cpu and qaif-cpu to snd_soc_of_xlate_dai_name() Use the new generic snd_soc_of_xlate_dai_name() helper in lpass-cpu.c and qaif-cpu.c, and remove the local lpass implementation. Signed-off-by: Harendra Gautam Reviewed-by: Ajay Kumar Nandam Link: https://patch.msgid.link/20261013-b4-qaif-shikra-audio-v9-6-f3022e28f2fd@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/lpass-cpu.c | 23 +---------------------- sound/soc/qcom/qaif-cpu.c | 1 + 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c index 079177a6d81e..ee187eeb7392 100644 --- a/sound/soc/qcom/lpass-cpu.c +++ b/sound/soc/qcom/lpass-cpu.c @@ -458,30 +458,9 @@ const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops2 = { }; EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_ops2); -static int asoc_qcom_of_xlate_dai_name(struct snd_soc_component *component, - const struct of_phandle_args *args, - const char **dai_name) -{ - struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); - const struct lpass_variant *variant = drvdata->variant; - int id = args->args[0]; - int ret = -EINVAL; - int i; - - for (i = 0; i < variant->num_dai; i++) { - if (variant->dai_driver[i].id == id) { - *dai_name = variant->dai_driver[i].name; - ret = 0; - break; - } - } - - return ret; -} - static const struct snd_soc_component_driver lpass_cpu_comp_driver = { .name = "lpass-cpu", - .of_xlate_dai_name = asoc_qcom_of_xlate_dai_name, + .of_xlate_dai_name = snd_soc_of_xlate_dai_name, .legacy_dai_naming = 1, }; diff --git a/sound/soc/qcom/qaif-cpu.c b/sound/soc/qcom/qaif-cpu.c index ad7268e2ace0..d34b8df83f94 100644 --- a/sound/soc/qcom/qaif-cpu.c +++ b/sound/soc/qcom/qaif-cpu.c @@ -603,6 +603,7 @@ EXPORT_SYMBOL_GPL(asoc_qcom_qaif_aif_cpu_dai_ops); static const struct snd_soc_component_driver qaif_cpu_comp_driver = { .name = "qaif-cpu", + .of_xlate_dai_name = snd_soc_of_xlate_dai_name, }; static const struct regmap_config audio_qaif_regmap_config = { From 6381a4f9c6339bd7b24bea04057fff8ff6dee107 Mon Sep 17 00:00:00 2001 From: Harendra Gautam Date: Tue, 13 Oct 2026 11:21:30 +0530 Subject: [PATCH 779/924] ASoC: qcom: Add QAIF PCM operations Add the PCM platform callbacks needed to use QAIF DAIs for playback and capture. Pre-allocate the DMA buffer via snd_pcm_set_fixed_buffer_all() in the .pcm_new callback. In open(), reserve a stream DMA index and perform the one-time QAIF hardware initialization. Program the DMA configuration in hw_params(), set the DMA clock rate and program the DMA base, buffer and period registers in prepare(), and report the current DMA position from pointer(). Release all resources in close(). The DMA clocks themselves are prepared and enabled by the CPU DAI driver's startup and shutdown callbacks. Support mmap() for CIF and AIF paths. Signed-off-by: Harendra Gautam Reviewed-by: Ajay Kumar Nandam Link: https://patch.msgid.link/20261013-b4-qaif-shikra-audio-v9-7-f3022e28f2fd@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qaif-platform.c | 464 +++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 sound/soc/qcom/qaif-platform.c diff --git a/sound/soc/qcom/qaif-platform.c b/sound/soc/qcom/qaif-platform.c new file mode 100644 index 000000000000..e24c4252825e --- /dev/null +++ b/sound/soc/qcom/qaif-platform.c @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * qaif-platform.c -- ALSA SoC PCM platform driver for the Qualcomm Audio Interface (QAIF) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qaif-reg.h" +#include "qaif.h" + +#define DRV_NAME "qaif-platform" + +/* period: 5ms @ 48kHz/1ch/S16 min, 20ms @ 48kHz/8ch/S32 max */ +#define QAIF_PLATFORM_PERIOD_BYTES_MIN (48 * 5 * 1 * 2) /* 480 B */ +#define QAIF_PLATFORM_PERIOD_BYTES_MAX (48 * 20 * 8 * 4) /* 30720 B */ +#define QAIF_PLATFORM_PERIODS_MIN 2 +#define QAIF_PLATFORM_PERIODS_MAX 4 +/* QAIF_PLATFORM_PERIODS_MAX * QAIF_PLATFORM_PERIOD_BYTES_MAX = 122880 B */ +#define QAIF_PLATFORM_BUFFER_BYTES_MAX \ + (QAIF_PLATFORM_PERIODS_MAX * QAIF_PLATFORM_PERIOD_BYTES_MAX) + +/* DMA CFG register bit positions */ +#define QAIF_DMACFG_SHRAM_WM_MASK GENMASK(11, 0) +#define QAIF_DMACFG_BURST4_BIT BIT(18) +#define QAIF_DMACFG_DYNCLK_BIT BIT(24) + +/* DMA CTL register bit positions */ +#define QAIF_DMACTL_ENABLE_BIT BIT(0) +#define QAIF_ALL_CLIENTS_MASK (QAIF_BITMASK_AIF_RDDMA_WRDMA | \ + QAIF_BITMASK_CIF_RDDMA_WRDMA) + +static const struct snd_pcm_hardware qaif_platform_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 = QAIF_PLATFORM_BUFFER_BYTES_MAX, + .period_bytes_min = QAIF_PLATFORM_PERIOD_BYTES_MIN, + .period_bytes_max = QAIF_PLATFORM_PERIOD_BYTES_MAX, + .periods_min = QAIF_PLATFORM_PERIODS_MIN, + .periods_max = QAIF_PLATFORM_PERIODS_MAX, + .fifo_size = 0, +}; + +static int qaif_map_ee_resource(struct qaif_drv_data *drvdata) +{ + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + int ret; + + ret = regmap_write(map, QAIF_EE_RDDMA_MAP_REG(v), GENMASK(v->num_rddma - 1, 0)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_EE_WRDMA_MAP_REG(v), GENMASK(v->num_wrdma - 1, 0)); + if (ret) + return ret; + if (v->num_intf > 0) { + ret = regmap_write(map, QAIF_EE_INTF_MAP_REG(v), GENMASK(v->num_intf - 1, 0)); + if (ret) + return ret; + } + ret = regmap_write(map, QAIF_EE_CODEC_RDDMA_MAP_REG(v), + GENMASK(v->num_codec_rddma - 1, 0)); + if (ret) + return ret; + return regmap_write(map, QAIF_EE_CODEC_WRDMA_MAP_REG(v), + GENMASK(v->num_codec_wrdma - 1, 0)); +} + +static int qaif_map_dma_path(struct qaif_drv_data *drvdata) +{ + struct regmap *map = drvdata->audio_qaif_map; + int ret; + + if (drvdata->variant->qxm_type != QAIF_QXM0) { + dev_err(regmap_get_device(map), + "only QXM0 is supported, qxm_type=%d\n", + drvdata->variant->qxm_type); + return -EINVAL; + } + + ret = regmap_write(map, QAIF_RDDMA_MAP_QXM, QAIF_QXM0); + if (ret) + return ret; + ret = regmap_write(map, QAIF_WRDMA_MAP_QXM, QAIF_QXM0); + if (ret) + return ret; + ret = regmap_write(map, QAIF_CDC_RDDMA_MAP_QXM, QAIF_QXM0); + if (ret) + return ret; + return regmap_write(map, QAIF_CDC_WRDMA_MAP_QXM, QAIF_QXM0); +} + +static int qaif_config_shram(struct qaif_drv_data *drvdata) +{ + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + u32 start_addr, shram_len; + int ret, i; + + if (v->qxm_type != QAIF_QXM0) { + dev_err(regmap_get_device(map), + "only QXM0 is supported, qxm_type=%d\n", + v->qxm_type); + return -EINVAL; + } + + start_addr = v->rddma_shram_start_addr[QAIF_AIF_DMA]; + shram_len = v->rddma_shram_len; + for (i = 0; i < v->num_rddma; i++) { + ret = regmap_write(map, QAIF_RDDMA_QXM0_SHRAM_ST_ADDR(i), + start_addr + (shram_len * i)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_RDDMA_QXM0_SHRAM_LEN(i), shram_len); + if (ret) + return ret; + } + + start_addr = v->wrdma_shram_start_addr[QAIF_AIF_DMA]; + shram_len = v->wrdma_shram_len; + for (i = 0; i < v->num_wrdma; i++) { + ret = regmap_write(map, QAIF_WRDMA_QXM0_SHRAM_ST_ADDR(i), + start_addr + (shram_len * i)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_WRDMA_QXM0_SHRAM_LEN(i), shram_len); + if (ret) + return ret; + } + + start_addr = v->rddma_shram_start_addr[QAIF_CIF_DMA]; + shram_len = v->rddma_shram_len; + for (i = 0; i < v->num_codec_rddma; i++) { + ret = regmap_write(map, QAIF_CDC_RDDMA_QXM0_SHRAM_ST_ADDR(i), + start_addr + (shram_len * i)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_CDC_RDDMA_QXM0_SHRAM_LEN(i), shram_len); + if (ret) + return ret; + } + + start_addr = v->wrdma_shram_start_addr[QAIF_CIF_DMA]; + shram_len = v->wrdma_shram_len; + for (i = 0; i < v->num_codec_wrdma; i++) { + ret = regmap_write(map, QAIF_CDC_WRDMA_QXM0_SHRAM_ST_ADDR(i), + start_addr + (shram_len * i)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_CDC_WRDMA_QXM0_SHRAM_LEN(i), shram_len); + if (ret) + return ret; + } + return 0; +} + +static int qaif_init(struct snd_soc_component *component) +{ + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + int ret; + + if (drvdata->qaif_hw_configured) + return 0; + + ret = qaif_config_shram(drvdata); + if (ret) { + dev_err(component->dev, "failed to config shram: %d\n", ret); + return ret; + } + + ret = qaif_map_ee_resource(drvdata); + if (ret) { + dev_err(component->dev, "failed to map EE resources: %d\n", ret); + return ret; + } + + ret = qaif_map_dma_path(drvdata); + if (ret) + return ret; + + drvdata->qaif_hw_configured = true; + return 0; +} + +static int qaif_platform_pcmops_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + unsigned int dai_id = cpu_dai->driver->id; + struct qaif_pcm_data *data; + int ret, stream_dma_idx, dma_reg_idx, dir = substream->stream; + + dma_reg_idx = v->get_dma_idx(dai_id); + if (dma_reg_idx < 0) { + dev_err(component->dev, "invalid DMA register index for DAI %u: %d\n", + dai_id, dma_reg_idx); + return dma_reg_idx; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + runtime->private_data = data; + + mutex_lock(&drvdata->stream_lock); + stream_dma_idx = v->alloc_stream_dma_idx(drvdata, dir, dai_id); + mutex_unlock(&drvdata->stream_lock); + if (stream_dma_idx < 0) { + ret = stream_dma_idx; + goto err_free_data; + } + + data->stream_dma_idx = stream_dma_idx; + data->dma_reg_idx = dma_reg_idx; + + if (qaif_is_cif_port(dai_id)) + WRITE_ONCE(drvdata->cif_substream[stream_dma_idx], substream); + else + WRITE_ONCE(drvdata->aif_substream[stream_dma_idx], substream); + + snd_soc_set_runtime_hwparams(substream, &qaif_platform_hardware); + + /* + * The DMA buffer/period length registers are programmed in 64-bit + * (8-byte) words, so constrain buffer and period sizes to that step + * to keep the ALSA and hardware sizes identical. + */ + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + BIT(QAIF_DMA_BYTES_TO_WORDS_SHIFT)); + if (ret >= 0) + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + BIT(QAIF_DMA_BYTES_TO_WORDS_SHIFT)); + if (ret >= 0) + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(soc_runtime->dev, "setting constraints failed: %d\n", ret); + if (qaif_is_cif_port(dai_id)) + WRITE_ONCE(drvdata->cif_substream[stream_dma_idx], NULL); + else + WRITE_ONCE(drvdata->aif_substream[stream_dma_idx], NULL); + mutex_lock(&drvdata->stream_lock); + v->free_stream_dma_idx(drvdata, stream_dma_idx, dai_id); + mutex_unlock(&drvdata->stream_lock); + goto err_free_data; + } + return 0; + +err_free_data: + kfree(data); + return ret; +} + +static int qaif_platform_pcmops_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + + if (qaif_is_cif_port(dai_id)) + WRITE_ONCE(drvdata->cif_substream[data->stream_dma_idx], NULL); + else + WRITE_ONCE(drvdata->aif_substream[data->stream_dma_idx], NULL); + + mutex_lock(&drvdata->stream_lock); + v->free_stream_dma_idx(drvdata, data->stream_dma_idx, dai_id); + mutex_unlock(&drvdata->stream_lock); + + substream->runtime->private_data = NULL; + kfree(data); + return 0; +} + +static int qaif_platform_pcmops_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + int idx = data->dma_reg_idx; + int ret; + + mutex_lock(&drvdata->stream_lock); + ret = qaif_init(component); + mutex_unlock(&drvdata->stream_lock); + if (ret) { + dev_err(soc_runtime->dev, "qaif_init failed: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(drvdata->audio_qaif_map, + qaif_dmacfg_reg(v, idx, substream->stream, dai_id), + QAIF_DMACFG_BURST4_BIT | QAIF_DMACFG_SHRAM_WM_MASK, + QAIF_DMACFG_BURST4_BIT | QAIF_DMACTL_WM_5); + if (ret) + dev_err(soc_runtime->dev, "error updating DMA CFG: %d\n", ret); + return ret; +} + +static int qaif_platform_pcmops_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + int idx = data->dma_reg_idx; + int ret; + + ret = regmap_update_bits(drvdata->audio_qaif_map, + qaif_dmactl_reg(v, idx, substream->stream, dai_id), + QAIF_DMACTL_ENABLE_BIT, 0); + if (ret) { + dev_err(soc_runtime->dev, "error disabling DMA: %d\n", ret); + return ret; + } + + ret = regmap_write(drvdata->audio_qaif_map, + qaif_dmacfg_reg(v, idx, substream->stream, dai_id), 0); + if (ret) + dev_err(soc_runtime->dev, "error clearing DMA CFG: %d\n", ret); + return ret; +} + +static int qaif_platform_pcmops_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + struct qaif_pcm_data *data = runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + int idx = data->dma_reg_idx; + int ret, dir = substream->stream; + + ret = clk_set_rate(drvdata->aud_dma_clk, QAIF_DMA_CLOCK_FREQ); + if (ret) { + dev_err(soc_runtime->dev, "error setting aud_dma_clk rate: %d\n", ret); + return ret; + } + ret = clk_set_rate(drvdata->aud_dma_mem_clk, QAIF_DMA_CLOCK_FREQ); + if (ret) { + dev_err(soc_runtime->dev, "error setting aud_dma_mem_clk rate: %d\n", ret); + return ret; + } + + ret = regmap_write(map, qaif_sid_map_reg(dir, dai_id), drvdata->smmu_csid_bits); + if (ret) { + dev_err(soc_runtime->dev, "error writing to SID MAP reg: %d\n", ret); + return ret; + } + + ret = regmap_write(map, qaif_dmabase_reg(v, idx, dir, dai_id), + lower_32_bits(runtime->dma_addr)); + if (ret) { + dev_err(soc_runtime->dev, "error writing to dma base reg: %d\n", ret); + return ret; + } + + ret = regmap_write(map, qaif_dmabuff_reg(v, idx, dir, dai_id), + (snd_pcm_lib_buffer_bytes(substream) >> + QAIF_DMA_BYTES_TO_WORDS_SHIFT) - 1); + if (ret) { + dev_err(soc_runtime->dev, "error writing to dma buf reg: %d\n", ret); + return ret; + } + + ret = regmap_write(map, qaif_dmaper_len_reg(v, idx, dir, dai_id), + (snd_pcm_lib_period_bytes(substream) >> + QAIF_DMA_BYTES_TO_WORDS_SHIFT) - 1); + if (ret) + dev_err(soc_runtime->dev, "error writing to dma period reg: %d\n", ret); + return ret; +} + +static snd_pcm_uframes_t qaif_platform_pcmops_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + unsigned int base_addr, curr_addr; + int idx = data->dma_reg_idx; + int ret, dir = substream->stream; + + ret = regmap_read(map, qaif_dmabase_reg(v, idx, dir, dai_id), &base_addr); + if (ret) { + dev_err(soc_runtime->dev, "error reading from rdmabase reg: %d\n", ret); + return SNDRV_PCM_POS_XRUN; + } + + ret = regmap_read(map, qaif_dmacurr_reg(v, idx, dir, dai_id), &curr_addr); + if (ret) { + dev_err(soc_runtime->dev, "error reading from rdmacurr reg: %d\n", ret); + return SNDRV_PCM_POS_XRUN; + } + + return bytes_to_frames(substream->runtime, + (curr_addr - base_addr) % + snd_pcm_lib_buffer_bytes(substream)); +} + +static int qaif_platform_pcmops_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_coherent(component->dev, vma, + runtime->dma_area, runtime->dma_addr, + runtime->dma_bytes); +} + +static int qaif_platform_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + + return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + component->dev, + QAIF_PLATFORM_BUFFER_BYTES_MAX); +} From abe10d2756602092a020df134d3d5c76528f4fb0 Mon Sep 17 00:00:00 2001 From: Harendra Gautam Date: Tue, 13 Oct 2026 11:21:31 +0530 Subject: [PATCH 780/924] ASoC: qcom: Add QAIF IRQ handling and platform register Finish the QAIF PCM platform support so streams can be registered, started, stopped and serviced by DMA interrupts. Register the ASoC component, request the QAIF interrupt and add trigger handling to enable or disable DMA and its interrupt sources. Dispatch the top-level IRQ status to the AIF and CIF DMA handlers, report period elapsed events to ALSA, warn on xrun conditions and stop the stream on bus errors. Signed-off-by: Harendra Gautam Link: https://patch.msgid.link/20261013-b4-qaif-shikra-audio-v9-8-f3022e28f2fd@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qaif-platform.c | 421 +++++++++++++++++++++++++++++++++ 1 file changed, 421 insertions(+) diff --git a/sound/soc/qcom/qaif-platform.c b/sound/soc/qcom/qaif-platform.c index e24c4252825e..facba3be2d8e 100644 --- a/sound/soc/qcom/qaif-platform.c +++ b/sound/soc/qcom/qaif-platform.c @@ -35,6 +35,14 @@ /* DMA CTL register bit positions */ #define QAIF_DMACTL_ENABLE_BIT BIT(0) + +/* DMA SHUTDOWN_STATUS register bit positions */ +#define QAIF_DMA_SHUTDOWN_IN_PROGRESS_BIT BIT(0) + +/* DMA shutdown takes ~10 bit clocks (~100us worst case at 8 kHz). */ +#define QAIF_DMA_SHUTDOWN_POLL_US 10 +#define QAIF_DMA_SHUTDOWN_TIMEOUT_US 1000 + #define QAIF_ALL_CLIENTS_MASK (QAIF_BITMASK_AIF_RDDMA_WRDMA | \ QAIF_BITMASK_CIF_RDDMA_WRDMA) @@ -462,3 +470,416 @@ static int qaif_platform_pcm_new(struct snd_soc_component *component, component->dev, QAIF_PLATFORM_BUFFER_BYTES_MAX); } + +static int qaif_platform_irq_op(struct qaif_drv_data *drvdata, int dir, + enum qaif_irq_type irq_type, int idx, + enum qaif_irq_op op) +{ + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + unsigned int bit = BIT(idx); + unsigned int val = op == QAIF_IRQ_ENABLE ? bit : 0; + u32 per_reg, err_reg, xrun_reg; + int ret; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + per_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_RDDMA_PERIOD_IRQ_EN_REG(v, irq_type); + xrun_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_RDDMA_UF_IRQ_EN_REG(v, irq_type); + err_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_RDDMA_ERR_RSP_IRQ_EN_REG(v, irq_type); + } else { + per_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_WRDMA_PERIOD_IRQ_EN_REG(v, irq_type); + xrun_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_WRDMA_OF_IRQ_EN_REG(v, irq_type); + err_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_WRDMA_ERR_RSP_IRQ_EN_REG(v, irq_type); + } + + if (op == QAIF_IRQ_CLEAR) { + ret = regmap_write(map, per_reg, bit); + if (ret) + return ret; + ret = regmap_write(map, xrun_reg, bit); + if (ret) + return ret; + return regmap_write(map, err_reg, bit); + } + + ret = regmap_write_bits(map, per_reg, bit, val); + if (ret) + return ret; + ret = regmap_write_bits(map, xrun_reg, bit, val); + if (ret) + return ret; + return regmap_write_bits(map, err_reg, bit, val); +} + +static int qaif_platform_pcmops_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + enum qaif_irq_type irq_type = qaif_is_cif_port(dai_id) ? QAIF_CIF_IRQ : QAIF_AIF_IRQ; + struct regmap *map = drvdata->audio_qaif_map; + int idx = data->dma_reg_idx; + int ret = 0, irq_op_err = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + irq_op_err = qaif_platform_irq_op(drvdata, substream->stream, irq_type, + idx, QAIF_IRQ_CLEAR); + if (irq_op_err) { + dev_err(soc_runtime->dev, "error clearing irq regs: %d\n", irq_op_err); + break; + } + ret = regmap_update_bits(map, + qaif_dmacfg_reg(v, idx, substream->stream, dai_id), + QAIF_DMACFG_DYNCLK_BIT, QAIF_DMACFG_DYNCLK_BIT); + if (ret) { + dev_err(soc_runtime->dev, "error enabling dma_dynclk: %d\n", ret); + break; + } + ret = regmap_update_bits(map, + qaif_dmactl_reg(v, idx, substream->stream, dai_id), + QAIF_DMACTL_ENABLE_BIT, QAIF_DMACTL_ENABLE_BIT); + if (ret) { + dev_err(soc_runtime->dev, "error enabling DMA: %d\n", ret); + break; + } + irq_op_err = qaif_platform_irq_op(drvdata, substream->stream, irq_type, + idx, QAIF_IRQ_ENABLE); + if (irq_op_err) + dev_err(soc_runtime->dev, "error enabling irq regs: %d\n", irq_op_err); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(map, + qaif_dmactl_reg(v, idx, substream->stream, dai_id), + QAIF_DMACTL_ENABLE_BIT, 0); + if (ret) { + dev_err(soc_runtime->dev, "error disabling DMA: %d\n", ret); + break; + } + ret = regmap_update_bits(map, + qaif_dmacfg_reg(v, idx, substream->stream, dai_id), + QAIF_DMACFG_DYNCLK_BIT, 0); + if (ret) + dev_err(soc_runtime->dev, "error disabling dma_dynclk: %d\n", ret); + irq_op_err = qaif_platform_irq_op(drvdata, substream->stream, irq_type, + idx, QAIF_IRQ_DISABLE); + if (irq_op_err) + dev_err(soc_runtime->dev, "error disabling irq regs: %d\n", irq_op_err); + break; + default: + ret = -EINVAL; + break; + } + + return ret ? : irq_op_err; +} + +static int qaif_platform_pcmops_sync_stop(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + struct qaif_pcm_data *data = substream->runtime->private_data; + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + unsigned int dai_id = cpu_dai->driver->id; + enum qaif_irq_type irq_type = qaif_is_cif_port(dai_id) ? QAIF_CIF_IRQ : QAIF_AIF_IRQ; + int idx = data->dma_reg_idx; + unsigned int val; + int ret; + + /* + * Wait for the DMA channel to report shutdown complete before + * clearing its interrupts, so a fast STOP->START or suspend/resume + * does not race a channel still shutting down. + */ + ret = regmap_read_poll_timeout(map, + qaif_dma_shutdown_stat_reg(v, idx, + substream->stream, + dai_id), + val, + !(val & QAIF_DMA_SHUTDOWN_IN_PROGRESS_BIT), + QAIF_DMA_SHUTDOWN_POLL_US, + QAIF_DMA_SHUTDOWN_TIMEOUT_US); + if (ret) + dev_err(soc_runtime->dev, "timeout waiting for DMA shutdown: %d\n", ret); + + synchronize_irq(drvdata->audio_qaif_irq); + ret = qaif_platform_irq_op(drvdata, substream->stream, irq_type, + idx, QAIF_IRQ_CLEAR); + if (ret) + dev_err(soc_runtime->dev, "error clearing irq regs: %d\n", ret); + return ret; +} + +static irqreturn_t qaif_process_dma_irq(struct qaif_drv_data *drvdata, + u32 stat_reg_addr, + u32 clr_reg_addr, + enum qaif_irq_type irq_type, + enum qaif_dma_dir dma_type, + enum qaif_irq irq, + struct snd_pcm_substream **substream) +{ + const struct qaif_variant *v = drvdata->variant; + unsigned int reg = 0; + int dma_idx, stream_dma_idx, rv, num_dma, stream_offset, array_size; + u32 mask; + struct snd_pcm_substream *ss; + + stream_offset = (dma_type == QAIF_DMA_WRDMA) ? + ((irq_type == QAIF_AIF_IRQ) ? v->wrdma_start : v->codec_wrdma_start) : 0; + num_dma = (dma_type == QAIF_DMA_WRDMA) ? + ((irq_type == QAIF_AIF_IRQ) ? v->num_wrdma : v->num_codec_wrdma) : + ((irq_type == QAIF_AIF_IRQ) ? v->num_rddma : v->num_codec_rddma); + if (!num_dma) + return IRQ_NONE; + + array_size = (irq_type == QAIF_AIF_IRQ) ? + QAIF_MAX_AIF_DMA_IDX : QAIF_MAX_CIF_DMA_IDX; + if (stream_offset + num_dma > array_size) { + dev_err(regmap_get_device(drvdata->audio_qaif_map), + "DMA index range %d+%d exceeds substream table %d\n", + stream_offset, num_dma, array_size); + return IRQ_NONE; + } + mask = GENMASK(num_dma - 1, 0); + + rv = regmap_read(drvdata->audio_qaif_map, stat_reg_addr, ®); + if (rv) { + dev_err_ratelimited(regmap_get_device(drvdata->audio_qaif_map), + "error reading stat reg 0x%x: %d\n", stat_reg_addr, rv); + return IRQ_NONE; + } + + if (!(reg & mask)) { + dev_warn_ratelimited(regmap_get_device(drvdata->audio_qaif_map), + "spurious IRQ: stat reg 0x%x status 0x%x mask 0x%x\n", + stat_reg_addr, reg, mask); + return IRQ_NONE; + } + + if (regmap_write(drvdata->audio_qaif_map, clr_reg_addr, reg & mask)) + return IRQ_NONE; + + for (dma_idx = 0; dma_idx < num_dma; dma_idx++) { + stream_dma_idx = dma_idx + stream_offset; + ss = READ_ONCE(substream[stream_dma_idx]); + if (!(reg & BIT(dma_idx)) || !ss) + continue; + switch (irq) { + case QAIF_IRQ_PERIOD: + snd_pcm_period_elapsed(ss); + break; + case QAIF_IRQ_OVERFLOW: + case QAIF_IRQ_UNDERFLOW: + dev_warn_ratelimited(regmap_get_device(drvdata->audio_qaif_map), + "QAIF DMA xRun\n"); + snd_pcm_stop_xrun(ss); + break; + case QAIF_IRQ_ERROR: + snd_pcm_stop_xrun(ss); + dev_err_ratelimited(regmap_get_device(drvdata->audio_qaif_map), + "QAIF bus error\n"); + break; + } + } + return IRQ_HANDLED; +} + +static irqreturn_t qaif_aif_irq_handler(struct qaif_drv_data *drvdata, u32 status) +{ + const struct qaif_variant *v = drvdata->variant; + struct snd_pcm_substream **ss = drvdata->aif_substream; + irqreturn_t ret = IRQ_NONE; + + if (status & QAIF_SUMMARY_BITMASK_AIF_PERIOD_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_PERIOD_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_PERIOD, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_PERIOD_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_PERIOD_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_PERIOD, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_OVERFLOW_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_OF_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_OVERFLOW, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_UNDERFLOW_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_UF_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_UNDERFLOW, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_ERROR, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_ERROR, ss); + return ret; +} + +static irqreturn_t qaif_cif_irq_handler(struct qaif_drv_data *drvdata, u32 status) +{ + const struct qaif_variant *v = drvdata->variant; + struct snd_pcm_substream **ss = drvdata->cif_substream; + irqreturn_t ret = IRQ_NONE; + + if (status & QAIF_SUMMARY_BITMASK_CIF_PERIOD_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_PERIOD_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_PERIOD, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_PERIOD_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_PERIOD_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_PERIOD, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_OVERFLOW_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_OF_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_OVERFLOW, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_UNDERFLOW_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_UF_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_UNDERFLOW, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_ERROR, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_ERROR, ss); + return ret; +} + +static const struct qaif_irq_map qaif_irq_clients[] = { + { QAIF_CLIENT_ID_AIF_DMA, QAIF_BITMASK_AIF_RDDMA_WRDMA, qaif_aif_irq_handler }, + { QAIF_CLIENT_ID_CIF_DMA, QAIF_BITMASK_CIF_RDDMA_WRDMA, qaif_cif_irq_handler }, +}; + +static irqreturn_t asoc_platform_qaif_irq(int irq, void *data) +{ + struct qaif_drv_data *drvdata = data; + const struct qaif_variant *v = drvdata->variant; + u32 summary_irq_status; + int rv, client; + irqreturn_t ret = IRQ_NONE; + + rv = regmap_read(drvdata->audio_qaif_map, QAIF_SUMMARY_IRQSTAT_REG(v), + &summary_irq_status); + if (rv) { + dev_err(regmap_get_device(drvdata->audio_qaif_map), + "error reading from irqstat reg: %d\n", rv); + return IRQ_NONE; + } + + if (!(summary_irq_status & QAIF_ALL_CLIENTS_MASK)) + return IRQ_NONE; + + for (client = 0; client < ARRAY_SIZE(qaif_irq_clients); client++) { + if (summary_irq_status & qaif_irq_clients[client].mask) + ret |= qaif_irq_clients[client].client_irq_handler(drvdata, + summary_irq_status); + } + return ret; +} + +static const struct snd_soc_component_driver qaif_component_driver = { + .name = DRV_NAME, + .pcm_new = qaif_platform_pcm_new, + .open = qaif_platform_pcmops_open, + .close = qaif_platform_pcmops_close, + .hw_params = qaif_platform_pcmops_hw_params, + .hw_free = qaif_platform_pcmops_hw_free, + .prepare = qaif_platform_pcmops_prepare, + .trigger = qaif_platform_pcmops_trigger, + .sync_stop = qaif_platform_pcmops_sync_stop, + .pointer = qaif_platform_pcmops_pointer, + .mmap = qaif_platform_pcmops_mmap, +}; + +int asoc_qcom_qaif_platform_register(struct platform_device *pdev) +{ + struct qaif_drv_data *drvdata = platform_get_drvdata(pdev); + struct of_phandle_args iommu_spec; + u32 sid; + int ret; + + if (!drvdata->variant->alloc_stream_dma_idx || + !drvdata->variant->free_stream_dma_idx) + return dev_err_probe(&pdev->dev, -EINVAL, + "Variant missing mandatory callbacks\n"); + + ret = of_parse_phandle_with_args(pdev->dev.of_node, + "iommus", "#iommu-cells", 0, + &iommu_spec); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to parse iommus property\n"); + sid = iommu_spec.args[0]; + of_node_put(iommu_spec.np); + drvdata->smmu_csid_bits = sid & QAIF_CSID_MASK; + + /* + * The DMA base register only holds the low 32 bits of the buffer + * address, so the coherent buffer must come from the low 32-bit + * address space. + */ + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to set 32-bit DMA mask\n"); + + drvdata->audio_qaif_irq = platform_get_irq(pdev, 0); + if (drvdata->audio_qaif_irq < 0) + return dev_err_probe(&pdev->dev, drvdata->audio_qaif_irq, + "Failed to get IRQ\n"); + + drvdata->qaif_hw_configured = false; + mutex_init(&drvdata->stream_lock); + + ret = devm_request_irq(&pdev->dev, drvdata->audio_qaif_irq, + asoc_platform_qaif_irq, 0, + "qaif-irq-audio-core", drvdata); + if (ret) + return dev_err_probe(&pdev->dev, ret, "irq request failed\n"); + + ret = devm_snd_soc_register_component(&pdev->dev, &qaif_component_driver, NULL, 0); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register platform component\n"); + return 0; +} +EXPORT_SYMBOL_GPL(asoc_qcom_qaif_platform_register); + +MODULE_DESCRIPTION("Qualcomm Audio Interface (QAIF) PCM platform driver"); +MODULE_AUTHOR("Harendra Gautam "); +MODULE_LICENSE("GPL"); From 871b3a7574f956a2c94f739de5e11ee673cd5a39 Mon Sep 17 00:00:00 2001 From: Harendra Gautam Date: Tue, 13 Oct 2026 11:21:32 +0530 Subject: [PATCH 781/924] ASoC: qcom: Add Shikra QAIF support Add the Shikra variant data for the Qualcomm Audio Interface driver so the common QAIF code can bind to the Shikra QAIF device and expose the SoC-specific DAIs. Provide the Shikra DMA-to-DAI mappings, DMA and SHRAM layout, clock names, DAI driver data and power-management hooks required by the QAIF driver. Add the Kconfig symbol and Makefile entries needed to build the new variant. Signed-off-by: Harendra Gautam Reviewed-by: Ajay Kumar Nandam Link: https://patch.msgid.link/20261013-b4-qaif-shikra-audio-v9-9-f3022e28f2fd@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/Kconfig | 10 + sound/soc/qcom/Makefile | 3 + sound/soc/qcom/qaif-shikra.c | 445 +++++++++++++++++++++++++++++++++++ 3 files changed, 458 insertions(+) create mode 100644 sound/soc/qcom/qaif-shikra.c diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 991feb317940..f6d810931a9a 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -71,6 +71,16 @@ config SND_SOC_QCOM_COMMON config SND_SOC_QCOM_SDW tristate +config SND_SOC_QCOM_QAIF + tristate "Qualcomm QAIF audio interface support" + depends on COMMON_CLK + select REGMAP_MMIO + help + Say Y or M to enable the Qualcomm Audio Interface (QAIF) driver + for the Shikra audio platform. QAIF is a DMA-based audio + subsystem that moves PCM data between memory and external serial + audio interfaces (AIF) and internal codec interfaces (CIF). + config SND_SOC_QDSP6_COMMON tristate diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index 985ce2ae286b..cf50b7a24bcf 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -47,3 +47,6 @@ obj-$(CONFIG_SND_SOC_QCOM_OFFLOAD_UTILS) += snd-soc-qcom-offload-utils.o #DSP lib obj-$(CONFIG_SND_SOC_QDSP6) += qdsp6/ + +snd-soc-qcom-qaif-y := qaif-cpu.o qaif-platform.o qaif-shikra.o +obj-$(CONFIG_SND_SOC_QCOM_QAIF) += snd-soc-qcom-qaif.o diff --git a/sound/soc/qcom/qaif-shikra.c b/sound/soc/qcom/qaif-shikra.c new file mode 100644 index 000000000000..2db3995a2de0 --- /dev/null +++ b/sound/soc/qcom/qaif-shikra.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * qaif-shikra.c -- ALSA SoC CPU-Platform DAI driver for QTi QAIF + */ + +#include +#include +#include +#include +#include "qaif.h" + +static const struct qaif_dmaidx_dai_map shikra_mi2s_dma_dai_map[] = { + { AIF_MI2S_RX_0, QAIF_DMA_IDX0 }, + { AIF_MI2S_RX_1, QAIF_DMA_IDX1 }, + { AIF_MI2S_RX_2, QAIF_DMA_IDX2 }, + { AIF_MI2S_RX_3, QAIF_DMA_IDX3 }, + { AIF_MI2S_TX_0, QAIF_DMA_IDX0 }, + { AIF_MI2S_TX_1, QAIF_DMA_IDX1 }, + { AIF_MI2S_TX_2, QAIF_DMA_IDX2 }, + { AIF_MI2S_TX_3, QAIF_DMA_IDX3 }, +}; + +static const struct qaif_dmaidx_dai_map shikra_tdm_dma_dai_map[] = { + { AIF_TDM_RX_0, QAIF_DMA_IDX0 }, + { AIF_TDM_RX_1, QAIF_DMA_IDX1 }, + { AIF_TDM_RX_2, QAIF_DMA_IDX2 }, + { AIF_TDM_RX_3, QAIF_DMA_IDX3 }, + { AIF_TDM_TX_0, QAIF_DMA_IDX0 }, + { AIF_TDM_TX_1, QAIF_DMA_IDX1 }, + { AIF_TDM_TX_2, QAIF_DMA_IDX2 }, + { AIF_TDM_TX_3, QAIF_DMA_IDX3 }, +}; + +static const struct qaif_dmaidx_dai_map shikra_cif_dma_dai_map[] = { + { RX_CODEC_DMA_RX_0, QAIF_DMA_IDX0 }, + { RX_CODEC_DMA_RX_1, QAIF_DMA_IDX1 }, + { VA_CODEC_DMA_TX_0, QAIF_DMA_IDX0 }, + { VA_CODEC_DMA_TX_1, QAIF_DMA_IDX1 }, +}; + +static struct snd_soc_dai_driver shikra_qaif_cpu_dai_driver[] = { + { + .id = AIF_MI2S_RX_0, + .name = "MI2S AIF RX Zero", + .playback = { + .stream_name = "MI2S AIF RX Zero Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_TX_0, + .name = "MI2S AIF TX Zero", + .capture = { + .stream_name = "MI2S AIF TX Zero Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_RX_0, + .name = "TDM AIF RX Zero", + .playback = { + .stream_name = "TDM AIF RX Zero Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_TX_0, + .name = "TDM AIF TX Zero", + .capture = { + .stream_name = "TDM AIF TX Zero Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_RX_1, + .name = "MI2S AIF RX One", + .playback = { + .stream_name = "MI2S AIF RX One Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_TX_1, + .name = "MI2S AIF TX One", + .capture = { + .stream_name = "MI2S AIF TX One Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_RX_1, + .name = "TDM AIF RX One", + .playback = { + .stream_name = "TDM AIF RX One Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_TX_1, + .name = "TDM AIF TX One", + .capture = { + .stream_name = "TDM AIF TX One Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_RX_2, + .name = "MI2S AIF RX Two", + .playback = { + .stream_name = "MI2S AIF RX Two Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_TX_2, + .name = "MI2S AIF TX Two", + .capture = { + .stream_name = "MI2S AIF TX Two Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_RX_2, + .name = "TDM AIF RX Two", + .playback = { + .stream_name = "TDM AIF RX Two Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_TX_2, + .name = "TDM AIF TX Two", + .capture = { + .stream_name = "TDM AIF TX Two Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_RX_3, + .name = "MI2S AIF RX Three", + .playback = { + .stream_name = "MI2S AIF RX Three Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_TX_3, + .name = "MI2S AIF TX Three", + .capture = { + .stream_name = "MI2S AIF TX Three Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_RX_3, + .name = "TDM AIF RX Three", + .playback = { + .stream_name = "TDM AIF RX Three Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_TX_3, + .name = "TDM AIF TX Three", + .capture = { + .stream_name = "TDM AIF TX Three Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = RX_CODEC_DMA_RX_0, + .name = "RX CDC DMA RX0", + .playback = { + .stream_name = "RX CDC DMA RX0 Playback", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_cif_dai_ops, + }, { + .id = RX_CODEC_DMA_RX_1, + .name = "RX CDC DMA RX1", + .playback = { + .stream_name = "RX CDC DMA RX1 Playback", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_cif_dai_ops, + }, { + .id = VA_CODEC_DMA_TX_0, + .name = "VA CDC DMA TX0", + .capture = { + .stream_name = "VA CDC DMA TX0 Capture", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &asoc_qcom_qaif_cif_dai_ops, + }, { + .id = VA_CODEC_DMA_TX_1, + .name = "VA CDC DMA TX1", + .capture = { + .stream_name = "VA CDC DMA TX1 Capture", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &asoc_qcom_qaif_cif_dai_ops, + }, +}; + +static int shikra_qaif_get_dma_idx(unsigned int dai_id) +{ + const struct qaif_dmaidx_dai_map *map; + int i, size; + + if (dai_id >= AIF_MI2S_RX_0 && dai_id <= AIF_MI2S_TX_12) { + map = shikra_mi2s_dma_dai_map; + size = ARRAY_SIZE(shikra_mi2s_dma_dai_map); + } else if (dai_id >= AIF_TDM_RX_0 && dai_id <= AIF_TDM_TX_12) { + map = shikra_tdm_dma_dai_map; + size = ARRAY_SIZE(shikra_tdm_dma_dai_map); + } else { + map = shikra_cif_dma_dai_map; + size = ARRAY_SIZE(shikra_cif_dma_dai_map); + } + + for (i = 0; i < size; i++) { + if (map[i].dai_id == dai_id) + return map[i].dma_idx; + } + return -EINVAL; +} + +static int shikra_qaif_alloc_stream_dma_idx(struct qaif_drv_data *drvdata, + int direction, + unsigned int dai_id) +{ + const struct qaif_variant *v = drvdata->variant; + int dma_idx, index; + + dma_idx = shikra_qaif_get_dma_idx(dai_id); + if (dma_idx < 0) + return dma_idx; + + if (qaif_is_aif_port(dai_id)) { + index = (direction == SNDRV_PCM_STREAM_PLAYBACK) ? + dma_idx : v->wrdma_start + dma_idx; + if (index >= QAIF_MAX_AIF_DMA_IDX) + return -EINVAL; + if (test_and_set_bit(index, &drvdata->aif_dma_idx_bit_map)) + return -EBUSY; + } else if (qaif_is_cif_port(dai_id)) { + index = (direction == SNDRV_PCM_STREAM_PLAYBACK) ? + dma_idx : v->codec_wrdma_start + dma_idx; + if (index >= QAIF_MAX_CIF_DMA_IDX) + return -EINVAL; + if (test_and_set_bit(index, &drvdata->cif_dma_idx_bit_map)) + return -EBUSY; + } else { + return -EINVAL; + } + + return index; +} + +static int shikra_qaif_free_stream_dma_idx(struct qaif_drv_data *drvdata, + int index, + unsigned int dai_id) +{ + if (qaif_is_aif_port(dai_id)) + clear_bit(index, &drvdata->aif_dma_idx_bit_map); + else + clear_bit(index, &drvdata->cif_dma_idx_bit_map); + return 0; +} + +static const struct qaif_variant shikra_qaif_data = { + .ee = 0, + + .num_rddma = 4, + .num_wrdma = 4, + .wrdma_start = 4, + + .num_codec_rddma = 4, + .num_codec_wrdma = 4, + .codec_wrdma_start = 4, + .num_intf = 4, + + .rddma_reg_base = 0x8000, + .rddma_stride = 0x1000, + .codec_rddma_reg_base = 0xc000, + .codec_rddma_stride = 0x1000, + + .wrdma_reg_base = 0x11000, + .wrdma_stride = 0x1000, + .codec_wrdma_reg_base = 0x15000, + .codec_wrdma_stride = 0x1000, + + .rddma_irq_reg_base = 0x19000, + .rddma_irq_stride = 0x1000, + .codec_rddma_irq_reg_base = 0x191A0, + .codec_rddma_irq_stride = 0x1000, + + .wrdma_irq_reg_base = 0x19078, + .wrdma_irq_stride = 0x1000, + .codec_wrdma_irq_reg_base = 0x19290, + .codec_wrdma_irq_stride = 0x1000, + + .qxm_type = QAIF_QXM0, + .rddma_shram_len = 64, + .rddma_shram_start_addr = {0, 256}, + .wrdma_shram_len = 64, + .wrdma_shram_start_addr = {0, 256}, + + .clk_name = (const char * const []) { + "lpass_config", + "lpass_core_axim", + "bus", + }, + .num_clks = 3, + + .dai_driver = shikra_qaif_cpu_dai_driver, + .num_dai = ARRAY_SIZE(shikra_qaif_cpu_dai_driver), + + .dai_bit_clk_names = (const char * const []) { + "aif_if0_ibit", + "aif_if1_ibit", + "aif_if2_ibit", + "aif_if3_ibit", + }, + .aif_full_cycle_en = (const bool []) { false, false, false, false }, + .aif_ctrl_data_oe = (const bool []) { false, false, false, false }, + .aif_loopback_en = (const bool []) { false, false, false, false }, + .alloc_stream_dma_idx = shikra_qaif_alloc_stream_dma_idx, + .free_stream_dma_idx = shikra_qaif_free_stream_dma_idx, + .get_dma_idx = shikra_qaif_get_dma_idx, +}; + +static const struct of_device_id shikra_qaif_cpu_device_id[] = { + { .compatible = "qcom,shikra-qaif-cpu", .data = &shikra_qaif_data }, + {} +}; +MODULE_DEVICE_TABLE(of, shikra_qaif_cpu_device_id); + +static struct platform_driver shikra_qaif_cpu_platform_driver = { + .driver = { + .name = "shikra-qaif-cpu", + .of_match_table = shikra_qaif_cpu_device_id, + .pm = pm_ptr(&asoc_qcom_qaif_pm_ops), + }, + .probe = asoc_qcom_qaif_cpu_platform_probe, +}; +module_platform_driver(shikra_qaif_cpu_platform_driver); + +MODULE_DESCRIPTION("Qualcomm Audio Interface (QAIF) Shikra variant driver"); +MODULE_AUTHOR("Harendra Gautam "); +MODULE_LICENSE("GPL"); From 9a810aa9f105114b0f0760e292a11e5a28b2e488 Mon Sep 17 00:00:00 2001 From: Harendra Gautam Date: Tue, 13 Oct 2026 11:21:33 +0530 Subject: [PATCH 782/924] MAINTAINERS: Add Qualcomm QAIF driver entry Add a dedicated MAINTAINERS entry for the Qualcomm Audio Interface (QAIF) driver so changes to its devicetree binding, dt-bindings header, and ASoC driver files are routed to the Qualcomm ASoC maintainers and mailing lists. Signed-off-by: Harendra Gautam Link: https://patch.msgid.link/20261013-b4-qaif-shikra-audio-v9-10-f3022e28f2fd@oss.qualcomm.com Signed-off-by: Mark Brown --- MAINTAINERS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 351f8b426a9f..2530bcd2fd64 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22152,6 +22152,15 @@ F: sound/soc/codecs/wcd93*.* F: sound/soc/codecs/wsa88*.* F: sound/soc/qcom/ +QCOM AUDIO INTERFACE (QAIF) DRIVER +M: Harendra Gautam +M: Srinivas Kandagatla +L: linux-sound@vger.kernel.org +L: linux-arm-msm@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/sound/qcom,shikra-qaif-cpu.yaml +F: sound/soc/qcom/qaif* + QCOM EMBEDDED USB DEBUGGER (EUD) M: Souradeep Chowdhury L: linux-arm-msm@vger.kernel.org From 0ea3e0b196f9dcc17b40ac881a6efef06459975b Mon Sep 17 00:00:00 2001 From: Vladyslav Tymoshchuk Date: Wed, 14 Oct 2026 19:27:00 +0300 Subject: [PATCH 783/924] ASoC: qcom: q6apm-dai: wrap the position of a full ring to 0 q6apm_dai_pointer() turns the number of period buffers the DSP has returned in the current lap, 1..periods, into a position and reports one frame less than that. Commit a93dad6f4e6a ("ASoC: q6apm-dai: make use of q6apm_get_hw_pointer") added the "- 1" so that the end of the ring is not reported as a position equal to the buffer size. One frame short has a cost once the application stops feeding the stream. The DSP returns the last queued buffer and the position stops at appl_ptr - 1, so avail stays at buffer_size - 1, below the default stop_threshold. The DSP sends nothing more as its queue is empty, so no period interrupt comes either: ALSA never detects the underrun and the stream stays RUNNING. A poll-driven application that missed its last wakeup waits forever, and drain only ends on its timeout, with -EIO. On capture, the last frame of each period is held back until the next one. Before that commit the position wrapped to 0 at the end of the ring. Do that again. A full lap is the start of the ring, and snd_pcm_update_hw_ptr0() accounts for the wrap: the position cannot move by more than one lap between two updates, as each returned buffer calls snd_pcm_period_elapsed() and the DSP holds at most periods buffers. Seen on a OnePlus Pad 3 (SM8750) with PipeWire in interrupt-driven scheduling: after a stall the PCM sat at hw_ptr 78239, appl_ptr 78240 (avail 3839 of 3840) for two hours. Fixes: a93dad6f4e6a ("ASoC: q6apm-dai: make use of q6apm_get_hw_pointer") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Vladyslav Tymoshchuk Reviewed-by: Srinivas Kandagatla Tested-by: Srinivas Kandagatla Link: https://patch.msgid.link/20261014162703.10172-2-tvlad744@gmail.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-dai.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index b0b10ba51c99..58beaf906b9f 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -613,11 +613,15 @@ static snd_pcm_uframes_t q6apm_dai_pointer(struct snd_soc_component *component, ptr = bytes_to_frames(runtime, index); return ptr; } - ptr = q6apm_get_hw_pointer(prtd->graph, substream->stream) * runtime->period_size; - if (ptr) - return ptr - 1; + /* + * The hw pointer counts the period buffers the DSP has returned in + * this lap, 1..periods. A full lap is the start of the ring again: + * report 0 rather than one frame less, so that the position of a + * drained buffer reaches appl_ptr and ALSA sees the underrun. + */ + ptr = q6apm_get_hw_pointer(prtd->graph, substream->stream) % runtime->periods; - return 0; + return ptr * runtime->period_size; } static int q6apm_dai_hw_params(struct snd_soc_component *component, From 0337b8129cf676b8eb767b2a1f312955760a5460 Mon Sep 17 00:00:00 2001 From: Vladyslav Tymoshchuk Date: Wed, 14 Oct 2026 19:27:01 +0300 Subject: [PATCH 784/924] ASoC: qcom: q6apm: reset the hw pointer with the fragment queue On every prepare in queue mode, q6apm_dai_prepare() frees and allocates the fragments again, and q6apm_alloc_fragments() resets dsp_buf, so the first buffer queued after it is the first one of the ring. The hw pointer that q6apm_dai_pointer() reports is left as it was. q6apm-dai used to keep its own position and cleared it in prepare. Since commit a93dad6f4e6a ("ASoC: q6apm-dai: make use of q6apm_get_hw_pointer") the position comes from the hw pointer, so a stream that is prepared again, e.g. to recover from an xrun, reports the position of the previous run until the DSP returns its first buffer, while ALSA has reset its own pointer to 0. Reset the hw pointer together with dsp_buf. Fixes: a93dad6f4e6a ("ASoC: q6apm-dai: make use of q6apm_get_hw_pointer") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Vladyslav Tymoshchuk Reviewed-by: Srinivas Kandagatla Tested-by: Srinivas Kandagatla Link: https://patch.msgid.link/20261014162703.10172-3-tvlad744@gmail.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index ea6680925f21..b6b1ef6833b3 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -285,6 +285,7 @@ int q6apm_alloc_fragments(struct q6apm_graph *graph, unsigned int dir, phys_addr mutex_lock(&graph->lock); data->dsp_buf = 0; + atomic_set(&data->hw_ptr, 0); if (data->buf) { mutex_unlock(&graph->lock); From 222d04cf55bc5b4b63602454014619c78deda89a Mon Sep 17 00:00:00 2001 From: Vladyslav Tymoshchuk Date: Wed, 14 Oct 2026 19:27:02 +0300 Subject: [PATCH 785/924] ASoC: qcom: q6asm-dai: wrap the position of a full ring to 0 Commit 44432315444c ("ASoC: qcom: q6asm-dai: use q6asm_get_hw_pointer") took the position from the number of period buffers the DSP has returned in the current lap, 1..periods, and reports one frame less than that, as q6apm-dai does. One frame short, a playback stream whose application stops feeding it never reaches the stop threshold: the DSP returns the last queued buffer, the position stops at appl_ptr - 1 and no period interrupt comes any more, so ALSA never detects the underrun, the stream stays RUNNING and drain only ends on its timeout. On capture, the last frame of each period is held back until the next one. Before that commit the position wrapped to 0 at the end of the ring. Do that again, as for q6apm-dai. Fixes: 44432315444c ("ASoC: qcom: q6asm-dai: use q6asm_get_hw_pointer") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Vladyslav Tymoshchuk Reviewed-by: Srinivas Kandagatla Tested-by: Srinivas Kandagatla Link: https://patch.msgid.link/20261014162703.10172-4-tvlad744@gmail.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6asm-dai.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c index cd4f82bb8cc7..4261c3048c36 100644 --- a/sound/soc/qcom/qdsp6/q6asm-dai.c +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -485,11 +485,10 @@ static snd_pcm_uframes_t q6asm_dai_pointer(struct snd_soc_component *component, struct q6asm_dai_rtd *prtd = runtime->private_data; snd_pcm_uframes_t ptr; - ptr = q6asm_get_hw_pointer(prtd->audio_client, substream->stream) * runtime->period_size; - if (ptr) - return ptr - 1; + /* a full lap of period buffers is the start of the ring again */ + ptr = q6asm_get_hw_pointer(prtd->audio_client, substream->stream) % runtime->periods; - return 0; + return ptr * runtime->period_size; } static int q6asm_dai_hw_params(struct snd_soc_component *component, From 6904743ab77e0684dff319f3806ef982e5eea6cf Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 15 Oct 2026 13:22:41 +0200 Subject: [PATCH 786/924] ALSA: core: Serialize snd_refcount put and sync When a thread tries to sync-and-destroy the resource with snd_refcount_sync(), it might lead to a UAF in a very slight time window. Namely, snd_refount_put() calls wake_up() after atomic_dec_and_test(), but wait_event() in snd_refcount_sync() checks the condition via atomic_test() without serialization, hence the thread that called snd_refcount_sync() can continue immediately to release the resource before another thread calls wake_up(). For avoiding the race above, introduce the spinlock and take it at snd_refcount_put() and snd_refcount_sync() in order to serialize the operations. Fixes: dcca9b6064c3 ("ALSA: Add simple refcount helper functions") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20261015071838.31082-1-tiwai%40suse.de Reviewed-by: Cen Zhang Link: https://patch.msgid.link/20261015112256.3902-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/core.h | 1 + sound/core/misc.c | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/sound/core.h b/include/sound/core.h index daa91a46a16b..657b12e1e95b 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -84,6 +84,7 @@ struct snd_device { struct snd_refcount { atomic_t count; wait_queue_head_t waiter; + spinlock_t lock; }; void snd_refcount_init(struct snd_refcount *ref); diff --git a/sound/core/misc.c b/sound/core/misc.c index 066fb9ecdcdc..6fe199e76e83 100644 --- a/sound/core/misc.c +++ b/sound/core/misc.c @@ -183,11 +183,13 @@ void snd_refcount_init(struct snd_refcount *ref) { atomic_set(&ref->count, 0); init_waitqueue_head(&ref->waiter); + spin_lock_init(&ref->lock); } EXPORT_SYMBOL_GPL(snd_refcount_init); void snd_refcount_put(struct snd_refcount *ref) { + guard(spinlock_irqsave)(&ref->lock); if (atomic_dec_and_test(&ref->count)) wake_up(&ref->waiter); } @@ -195,6 +197,7 @@ EXPORT_SYMBOL_GPL(snd_refcount_put); void snd_refcount_sync(struct snd_refcount *ref) { - wait_event(ref->waiter, !atomic_read(&ref->count)); + guard(spinlock_irq)(&ref->lock); + wait_event_lock_irq(ref->waiter, !atomic_read(&ref->count), ref->lock); } EXPORT_SYMBOL_GPL(snd_refcount_sync); From 85bc213f96ac03d74a3e531968c68861e5598281 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Thu, 15 Oct 2026 21:02:38 +0200 Subject: [PATCH 787/924] ASoC: Intel: avs: Unprepare a stream when recovery occurs Recovery on alsa-lib side is handled with either: 1) snd_pcm_prepare(state=XRUN) 2) snd_pcm_drop/drain() -> snd_pcm_prepare(state=SETUP) before starting the PCM anew. Relying solely on state=XRUN leaves 2) unaccounted for. sound/core/pcm_native.c ensures correctness of the state machine so plain state!=PREPARED check suffices. This is the BE equivalent of job done previously for FE side only with commit cfca1637bc2b ("ASoC: Intel: avs: Unprepare a stream when XRUN occurs") and its follow up titled: ("ASoC: Intel: avs: Reset the stream unless it is already prepared"). Link: https://lore.kernel.org/linux-sound/20261010103753.16969-1-stiraspo@gmail.com/T/ Fixes: d070002a20fc ("ASoC: Intel: avs: HDA PCM BE operations") Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20261015190240.729793-2-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/pcm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c index 880cd7a9fb34..13a38ec8c917 100644 --- a/sound/soc/intel/avs/pcm.c +++ b/sound/soc/intel/avs/pcm.c @@ -461,6 +461,8 @@ static int avs_dai_hda_be_prepare(struct snd_pcm_substream *substream, struct sn link_stream = data->link_stream; p = &be->dpcm[substream->stream].hw_params; + if (substream->runtime->state != SNDRV_PCM_STATE_PREPARED) + link_stream->link_prepared = false; if (link_stream->link_prepared) return 0; From 6e6aedb1f8767e343707d89651a2051bff28be07 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Thu, 15 Oct 2026 21:02:39 +0200 Subject: [PATCH 788/924] ASoC: Intel: avs: Verify sizes of configs coming from topology Before operating on the init_config or NHLT-blob entries check whether their sizes are within bounds of the block being parsed. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20261015190240.729793-3-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/topology.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c index 5d70be63a4a7..3134a331e23f 100644 --- a/sound/soc/intel/avs/topology.c +++ b/sound/soc/intel/avs/topology.c @@ -1720,6 +1720,10 @@ static int avs_tplg_parse_initial_configs(struct snd_soc_component *comp, AVS_TKN_INIT_CONFIG_ID_U32, mod_init_config_parsers, ARRAY_SIZE(mod_init_config_parsers)); + if (ret) + return ret; + if (config->length > block_size) + return -EINVAL; block_size -= esize; @@ -1790,6 +1794,8 @@ static int avs_tplg_parse_nhlt_configs(struct snd_soc_component *comp, /* With tuples parsed, the blob shall be allocated. */ if (!config->blob) return -EINVAL; + if (config->blob->capabilities_size > block_size) + return -EINVAL; /* Consume the raw data and move to the next entry. */ memcpy(config->blob->capabilities, (u8 *)tuples + esize, From 05c2975b4267246219e7aead0467626107e204e9 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Thu, 15 Oct 2026 21:02:40 +0200 Subject: [PATCH 789/924] ASoC: Intel: avs: Prevent module info underflow Typically modules information is first initialized when loading the base AudioDSP base firmware and then expanded when loadable DSP modules are requested by platform-components, see avs_component_load_libraries(). While theoretically not possible in a production scenario, there is no reason to continue the IDAs preparation if an event caused the newly obtained module information to be smaller or equal than the existing information. Fixes: c1a427e8da93 ("ASoC: Intel: avs: Firmware resources management utilities") Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20261015190240.729793-4-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/utils.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/soc/intel/avs/utils.c b/sound/soc/intel/avs/utils.c index ea14ec173855..f19a62e7e8b5 100644 --- a/sound/soc/intel/avs/utils.c +++ b/sound/soc/intel/avs/utils.c @@ -116,9 +116,11 @@ avs_module_ida_alloc(struct avs_dev *adev, struct avs_mods_info *newinfo, bool p int i; if (!purge && oldinfo) { - if (oldinfo->count >= newinfo->count) - dev_warn(adev->dev, "refreshing %d modules info with %d\n", + if (oldinfo->count >= newinfo->count) { + dev_warn(adev->dev, "modules info underflow, old: %d, new: %d\n", oldinfo->count, newinfo->count); + return -EPROTO; + } tocopy_count = oldinfo->count; } From 4b963ac3e207aed376811245c091be62e26ee470 Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Fri, 9 Oct 2026 16:47:18 +0530 Subject: [PATCH 790/924] ASoC: codecs: wsa885x: Reset registers on teardown for B2B playback During back-to-back speaker playback the PA sometimes fails to enable because a few I2S/TDM and clock source registers retain their previous state and are not reprogrammed on the next stream. Reset the relevant I2S/TDM, clock source and bandgap override registers in hw_free() so the codec is torn down cleanly and the speaker amplifier comes up reliably on back-to-back use cases. Move the bandgap override reset out of set_sysclk() into the teardown path where it belongs. Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20261009111719.2609060-2-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/wsa885x.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/wsa885x.c b/sound/soc/codecs/wsa885x.c index 1faa8541a872..5ffbf9e89fd9 100644 --- a/sound/soc/codecs/wsa885x.c +++ b/sound/soc/codecs/wsa885x.c @@ -896,7 +896,6 @@ static int wsa885x_codec_set_sysclk(struct snd_soc_dai *dai, int clk_id, regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00); regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x01); - regmap_write(wsa885x->regmap, WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00); return 0; } @@ -1006,6 +1005,16 @@ static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { static const struct reg_sequence regs[] = { + { WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_CTL0, 0x06 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, 0x08 }, + { WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 }, + { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 }, { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, }; struct wsa885x_priv *wsa885x; From d1e5723b5bc258a0329ddcdac6ddf05b471d1c78 Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Fri, 9 Oct 2026 16:47:19 +0530 Subject: [PATCH 791/924] ASoC: qcom: sc8280xp: add Maili sound card configuration Maili ships its own firmware, so its topology file has to live in a separate maili/ firmware directory. The topology load path is derived from the sound card driver_name, so reusing the Hawi data would look up the topology under hawi/ instead. Add a dedicated maili_priv_data with driver_name "maili" for the qcom,maili-sndcard compatible so the topology is loaded from the maili/ directory. It keeps the same DAPM widgets and enables codec sysclk, MI2S BCLK and WCD jack detection for the Maili audio pipeline. Reviewed-by: Ajay Kumar Nandam Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20261009111719.2609060-3-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/sc8280xp.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index b5d367486d56..b97cafeb8e78 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -552,6 +552,15 @@ static const struct qcom_snd_soc_common kaanapali_priv_data = { .wcd_jack = true, }; +static const struct qcom_snd_soc_common maili_priv_data = { + .driver_name = "maili", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .codec_sysclk_set = true, + .mi2s_bclk_enable = true, + .wcd_jack = true, +}; + static const struct qcom_snd_soc_common qcs9100_priv_data = { .driver_name = "sa8775p", .dapm_widgets = sc8280xp_dapm_widgets, @@ -676,7 +685,7 @@ static const struct of_device_id snd_sc8280xp_dt_match[] = { { .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data }, { .compatible = "qcom,hawi-sndcard", .data = &hawi_priv_data }, { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data }, - { .compatible = "qcom,maili-sndcard", .data = &hawi_priv_data }, + { .compatible = "qcom,maili-sndcard", .data = &maili_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 }, From cef5d087d6813d4c15847d0b71ecbd6c47c5f1d4 Mon Sep 17 00:00:00 2001 From: Michael Auer Date: Tue, 13 Oct 2026 22:45:49 +0200 Subject: [PATCH 792/924] ASoC: qcom: sdm845: Disable the quaternary MI2S bit clock on shutdown sdm845_snd_startup() enables the quaternary MI2S bit clock in the ADSP, but sdm845_snd_shutdown() never disables it. After the first stream on this port the clock stays on. On the OnePlus 6 the speaker amplifier is on this port. After the first playback the ADSP no longer power collapses: it slept under 9 s in 5 hours. With this change it sleeps again when audio is idle (about 40 s of 45 s). Refcount the clock like the other MI2S and TDM ports and disable it when the last user is gone. Fixes: 5caf64c633a3 ("ASoC: qcom: sdm845: add support to DB845c and Lenovo Yoga") Assisted-by: LLM Signed-off-by: Michael Auer Link: https://patch.msgid.link/20261013204549.3115830-1-mick@yoxcu.de Signed-off-by: Mark Brown --- sound/soc/qcom/sdm845.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c index f0fbec9c8df2..85951397cb2b 100644 --- a/sound/soc/qcom/sdm845.c +++ b/sound/soc/qcom/sdm845.c @@ -43,6 +43,7 @@ struct sdm845_snd_data { bool stream_prepared[AFE_PORT_MAX]; uint32_t pri_mi2s_clk_count; uint32_t sec_mi2s_clk_count; + uint32_t quat_mi2s_clk_count; uint32_t quat_tdm_clk_count; }; @@ -372,9 +373,11 @@ static int sdm845_snd_startup(struct snd_pcm_substream *substream) break; case QUATERNARY_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_QUAD_MI2S_IBIT, - MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + if (++(data->quat_mi2s_clk_count) == 1) { + 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, fmt); snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); break; @@ -483,8 +486,16 @@ static void sdm845_snd_shutdown(struct snd_pcm_substream *substream) 0, SNDRV_PCM_STREAM_PLAYBACK); } break; - case SLIMBUS_0_RX...SLIMBUS_6_TX: + case QUATERNARY_MI2S_RX: + if (--(data->quat_mi2s_clk_count) == 0) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_QUAD_MI2S_IBIT, + 0, SNDRV_PCM_STREAM_PLAYBACK); + } + break; + + case SLIMBUS_0_RX...SLIMBUS_6_TX: break; default: From 8ea1086f3ffff73262eb20ebc6d1d708b72b6b20 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 15 Oct 2026 21:01:46 +0200 Subject: [PATCH 793/924] ALSA: pcm: Call snd_pcm_sync_stop outside spinlock in snd_pcm_drop() In the recent addition of snd_pcm_sync_stop() call at snd_pcm_drop(), I overlooked the guard() line in snd_pcm_drop() (and the review overlooked, too) -- it must be called outside, of course! Let's fix it. Now guard() became scoped_guard(), and its range is shown more clearly. Fixes: fdf9a21cda78 ("ALSA: pcm: Apply sync-stop at SNDRV_PCM_IOCTL_DROP ioctl") Reported-by: Marek Szyprowski Closes: https://lore.kernel.org/c2bc1478-5999-455f-bd67-8b536c452215@samsung.com Link: https://patch.msgid.link/20261015190147.230736-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 05097f2630ad..f2e378696c7e 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2307,17 +2307,18 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream) return -ENXIO; runtime = substream->runtime; - guard(pcm_stream_lock_irq)(substream); - if (runtime->state == SNDRV_PCM_STATE_OPEN || - runtime->state == SNDRV_PCM_STATE_DISCONNECTED) - return -EBADFD; + scoped_guard(pcm_stream_lock_irq, substream) { + if (runtime->state == SNDRV_PCM_STATE_OPEN || + runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; - /* resume pause */ - if (runtime->state == SNDRV_PCM_STATE_PAUSED) - snd_pcm_pause(substream, false); + /* resume pause */ + if (runtime->state == SNDRV_PCM_STATE_PAUSED) + snd_pcm_pause(substream, false); - result = snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); - /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ + result = snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ + } snd_pcm_sync_stop(substream, true); From 7f21312381fee99f58b383fa07374da3c614aec4 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Fri, 16 Oct 2026 01:11:38 +0800 Subject: [PATCH 794/924] ALSA: mixer: oss: Serialize initial slot construction OSS mixer slot callbacks require an initialized private_data payload. However, snd_mixer_oss_notify_handler() publishes card->mixer_oss before snd_mixer_oss_build() constructs the default bindings, and snd_mixer_oss_build_input() stores get_volume before private_data. With OSS mixer emulation enabled and a native control matching a default mapping, a volume read can arrive during initial binding construction. For example, the dummy card provides the Master Volume control before registration. The following ordering lets the read use the callback before its payload is attached: Card registration OSS volume read ----------------- --------------- register the OSS mixer device publish card->mixer_oss store slot->get_volume open the mixer device lock reg_mutex invoke slot->get_volume load NULL private_data read slot->present store slot->private_data The read holds reg_mutex, but the initial builder does not acquire it. The controls_rwsem read lock protects native controls and does not serialize the builder with another reader. As a result, snd_mixer_oss_get_volume1() can dereference a NULL payload and Oops. Hold the existing reg_mutex throughout snd_mixer_oss_build(), as proc remapping already does for snd_mixer_oss_build_input(). A request can no longer invoke a callback between its installation and the payload store. This keeps the existing reg_mutex-before-controls_rwsem lock order. KASAN report as below: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] CPU: 1 UID: 0 PID: 431 Comm: alsa-oss-trigge Tainted: G O 7.2.0-rc6-pmb-bt-functional-v1+ #1 PREEMPT(lazy) Tainted: [O]=OOT_MODULE Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 RIP: 0010:snd_mixer_oss_get_volume1+0xd1/0x610 [snd_mixer_oss] Code: 00 c7 03 64 00 00 00 e9 b8 01 00 00 e8 e8 82 44 c1 49 8d 75 04 48 b8 00 00 00 00 00 fc ff df 48 89 f2 48 89 34 24 48 c1 ea 03 <0f> b6 14 02 48 89 f0 83 e0 07 83 c0 03 38 d0 7c 08 84 d2 0f 85 3f RSP: 0018:ffff888104c67cc8 EFLAGS: 00010257 RAX: dffffc0000000000 RBX: ffff888104c67d80 RCX: ffffffffc0406cb8 RDX: 0000000000000000 RSI: 0000000000000004 RDI: ffffffff862d93a4 RBP: ffff888104c67d90 R08: 0000000000000001 R09: 0000000000000001 R10: ffffffff89406e57 R11: ffff888104c9d700 R12: ffff888113c48038 R13: 0000000000000000 R14: ffff888113c48000 R15: ffff888104646820 FS: 00007f7904d29780(0000) GS:ffff8881fd81d000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f7904e388e0 CR3: 000000010662c002 CR4: 0000000000770ef0 PKRU: 55555554 Call Trace: snd_mixer_oss_ioctl1+0x25d/0x16f0 [snd_mixer_oss] ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_snd_mixer_oss_get_volume1+0x10/0x10 [snd_mixer_oss] ? __pfx_snd_mixer_oss_ioctl1+0x10/0x10 [snd_mixer_oss] ? lock_release+0xc8/0x280 ? pmbd_site_is_armed+0xa1/0xd0 ? lockdep_hardirqs_on_prepare+0xea/0x1a0 ? trace_hardirqs_on+0x18/0x160 ? srso_alias_return_thunk+0x5/0xfbef5 ? _raw_spin_unlock_irqrestore+0x44/0x60 ? srso_alias_return_thunk+0x5/0xfbef5 ? pmbd_gate_site_armed+0x31/0x1c0 ? srso_alias_return_thunk+0x5/0xfbef5 ? pmbd_probe_hit_cookie+0xee/0x1c0 ? __pfx_pmbd_probe_hit_cookie+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? selinux_file_ioctl+0xaa/0x280 snd_mixer_oss_ioctl+0x4a/0x80 [snd_mixer_oss] ? __pfx_snd_mixer_oss_ioctl+0x10/0x10 [snd_mixer_oss] __x64_sys_ioctl+0x163/0x1d0 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f7904e3891b Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00 48 89 44 24 08 48 8d 44 24 20 48 89 44 24 10 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1c 48 8b 44 24 18 64 48 2b 04 25 28 00 00 RSP: 002b:00007ffc72071e20 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f7904e3891b RDX: 00007ffc72071e84 RSI: 0000000080044d00 RDI: 0000000000000003 RBP: 0000000000000003 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffc72072e12 R13: 00007ffc72072060 R14: 00007f7904f67000 R15: 000055cd1e601d50 Modules linked in: snd_dummy(O) snd_mixer_oss(O) ---[ end trace 0000000000000000 ]--- RIP: 0010:snd_mixer_oss_get_volume1+0xd1/0x610 [snd_mixer_oss] Code: 00 c7 03 64 00 00 00 e9 b8 01 00 00 e8 e8 82 44 c1 49 8d 75 04 48 b8 00 00 00 00 00 fc ff df 48 89 f2 48 89 34 24 48 c1 ea 03 <0f> b6 14 02 48 89 f0 83 e0 07 83 c0 03 38 d0 7c 08 84 d2 0f 85 3f RSP: 0018:ffff888104c67cc8 EFLAGS: 00010257 RAX: dffffc0000000000 RBX: ffff888104c67d80 RCX: ffffffffc0406cb8 RDX: 0000000000000000 RSI: 0000000000000004 RDI: ffffffff862d93a4 RBP: ffff888104c67d90 R08: 0000000000000001 R09: 0000000000000001 R10: ffffffff89406e57 R11: ffff888104c9d700 R12: ffff888113c48038 R13: 0000000000000000 R14: ffff888113c48000 R15: ffff888104646820 FS: 00007f7904d29780(0000) GS:ffff8881fd81d000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f7904e388e0 CR3: 000000010662c002 CR4: 0000000000770ef0 PKRU: 55555554 Kernel panic - not syncing: Fatal exception Kernel Offset: disabled Rebooting in 1 seconds.. Assisted-by: LLM Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-99c1214968ae9fa4d744-v2-df0befb9a699eb50dcec@gmail.com Signed-off-by: Takashi Iwai --- sound/core/oss/mixer_oss.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c index c533d767c29a..f1fa42e634fc 100644 --- a/sound/core/oss/mixer_oss.c +++ b/sound/core/oss/mixer_oss.c @@ -1272,6 +1272,7 @@ static void snd_mixer_oss_build(struct snd_mixer_oss *mixer) }; unsigned int idx; + guard(mutex)(&mixer->reg_mutex); for (idx = 0; idx < ARRAY_SIZE(table); idx++) snd_mixer_oss_build_input(mixer, &table[idx], 0, 0); if (mixer->mask_recsrc) { From f15b2df0b940cb2c01797f78791de3279d514086 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 9 Oct 2026 02:14:59 +0800 Subject: [PATCH 795/924] ASoC: rockchip: fix device node reference leak in rk3288_hdmi_analog snd_rk_mc_probe() acquires references to the codec and CPU device nodes with of_parse_phandle() and stores them in the static rk_dailink, but the driver never calls of_node_put() on them. All error paths and the success path simply return, and the driver has no remove callback, so the references are leaked for the whole lifetime of the card and each failed or repeated probe leaks another pair. The same pattern was fixed for the sibling rockchip_rt5645.c driver. Release the codec and CPU device nodes on every probe error path and in a new remove callback, and also drop the extra reference placed in args.np by of_parse_phandle_with_fixed_args(). Fixes: eaae2ea73593 ("ASoC: rockchip: Add machine driver for RK3288 boards that use analog/HDMI") Assisted-by: DeepSeek-V4.1-Flash Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20261008181459.2762862-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/rockchip/rk3288_hdmi_analog.c | 42 +++++++++++++++++++------ 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/sound/soc/rockchip/rk3288_hdmi_analog.c b/sound/soc/rockchip/rk3288_hdmi_analog.c index 541163ed56fc..9bb02d41d901 100644 --- a/sound/soc/rockchip/rk3288_hdmi_analog.c +++ b/sound/soc/rockchip/rk3288_hdmi_analog.c @@ -201,36 +201,59 @@ static int snd_rk_mc_probe(struct platform_device *pdev) if (ret) { dev_err(&pdev->dev, "Unable to parse property 'rockchip,audio-codec'\n"); - return ret; + goto put_codec_of_node; } ret = snd_soc_get_dai_name(&args, &rk_dailink.codecs[0].dai_name); - if (ret) - return dev_err_probe(&pdev->dev, ret, - "Unable to get codec_dai_name\n"); + of_node_put(args.np); + if (ret) { + dev_err_probe(&pdev->dev, ret, + "Unable to get codec_dai_name\n"); + goto put_codec_of_node; + } rk_dailink.cpus->of_node = of_parse_phandle(np, "rockchip,i2s-controller", 0); if (!rk_dailink.cpus->of_node) { dev_err(&pdev->dev, "Property 'rockchip,i2s-controller' missing or invalid\n"); - return -EINVAL; + ret = -EINVAL; + goto put_codec_of_node; } rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; ret = snd_soc_of_parse_audio_routing(card, "rockchip,routing"); if (ret) - return ret; + goto put_cpu_of_node; snd_soc_card_set_drvdata(card, machine); ret = devm_snd_soc_register_card(&pdev->dev, card); - if (ret) - return dev_err_probe(&pdev->dev, ret, - "Soc register card failed\n"); + if (ret) { + dev_err_probe(&pdev->dev, ret, + "Soc register card failed\n"); + goto put_cpu_of_node; + } return 0; + +put_cpu_of_node: + of_node_put(rk_dailink.cpus->of_node); + rk_dailink.cpus->of_node = NULL; +put_codec_of_node: + of_node_put(rk_dailink.codecs[0].of_node); + rk_dailink.codecs[0].of_node = NULL; + + return ret; +} + +static void snd_rk_mc_remove(struct platform_device *pdev) +{ + of_node_put(rk_dailink.cpus->of_node); + rk_dailink.cpus->of_node = NULL; + of_node_put(rk_dailink.codecs[0].of_node); + rk_dailink.codecs[0].of_node = NULL; } static const struct of_device_id rockchip_sound_of_match[] = { @@ -242,6 +265,7 @@ MODULE_DEVICE_TABLE(of, rockchip_sound_of_match); static struct platform_driver rockchip_sound_driver = { .probe = snd_rk_mc_probe, + .remove = snd_rk_mc_remove, .driver = { .name = DRV_NAME, .pm = &snd_soc_pm_ops, From 7ef99fcfbbac20ebec10d21a37aeaf8321de7829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amadeusz=20S=C5=82awi=C5=84ski?= Date: Wed, 14 Oct 2026 22:06:01 +0200 Subject: [PATCH 796/924] ASoC: soc-link: Correct stream stop operations in snd_soc_link_trigger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When snd_soc_link_trigger() is called to stop, pause push or suspend stream it should call soc_link_mark_pop() on proper target to reverse operation from trigger start, pause release or resume. Fixes: 6374f493d93b ("ASoC: soc-pcm: care trigger rollback") Signed-off-by: Amadeusz SÅ‚awiÅ„ski Link: https://patch.msgid.link/20261014200608.118770-2-amade@asmblr.net Signed-off-by: Mark Brown --- sound/soc/soc-link.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/soc-link.c b/sound/soc/soc-link.c index 02fd68f2e702..50ff9ba3ac87 100644 --- a/sound/soc/soc-link.c +++ b/sound/soc/soc-link.c @@ -161,7 +161,7 @@ int snd_soc_link_trigger(struct snd_pcm_substream *substream, int cmd, break; ret = soc_link_trigger(substream, cmd); - soc_link_mark_pop(rtd, startup); + soc_link_mark_pop(rtd, trigger); } return ret; From 223ec2a93bec6c5ee859094c826fec78516a0bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amadeusz=20S=C5=82awi=C5=84ski?= Date: Wed, 14 Oct 2026 22:06:02 +0200 Subject: [PATCH 797/924] ASoC: ops: Fix return value in snd_soc_limit_volume() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As documented snd_soc_limit_volume() should return 0 on success, however at some point in time this promise was broken when snd_soc_clip_to_platform_max() was refactored, refactor it once again using __free() to automatically free struct snd_ctl_elem_value and return proper value. Fixes: 7e10d7242ea8 ("ASoC: ops: dynamically allocate struct snd_ctl_elem_value") Signed-off-by: Amadeusz SÅ‚awiÅ„ski Link: https://patch.msgid.link/20261014200608.118770-3-amade@asmblr.net Signed-off-by: Mark Brown --- sound/soc/soc-ops.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index 142f407e3939..b3a719604ec7 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -406,7 +406,7 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw_sx); static int snd_soc_clip_to_platform_max(struct snd_kcontrol *kctl) { struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value; - struct snd_ctl_elem_value *uctl; + struct snd_ctl_elem_value *uctl __free(kfree) = NULL; int ret; if (!mc->platform_max) @@ -418,7 +418,7 @@ static int snd_soc_clip_to_platform_max(struct snd_kcontrol *kctl) ret = kctl->get(kctl, uctl); if (ret < 0) - goto out; + return ret; if (uctl->value.integer.value[0] > mc->platform_max) uctl->value.integer.value[0] = mc->platform_max; @@ -428,10 +428,10 @@ static int snd_soc_clip_to_platform_max(struct snd_kcontrol *kctl) uctl->value.integer.value[1] = mc->platform_max; ret = kctl->put(kctl, uctl); + if (ret < 0) + return ret; -out: - kfree(uctl); - return ret; + return 0; } /** From 1641c1ba27abcd3ea3bba1232d9d817092f1b757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amadeusz=20S=C5=82awi=C5=84ski?= Date: Wed, 14 Oct 2026 22:06:03 +0200 Subject: [PATCH 798/924] ASoC: soc-topology-test: Check return value of kunit_device_register() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Call to kunit_device_register() can fail. Check return result of register call. While at it introduce helper variable so that if it fails put_device is called on properly initialized variable. Fixes: e57cdff0ddc4 ("ASoC: topology: Replace fake root_device with kunit_device in tests") Signed-off-by: Amadeusz SÅ‚awiÅ„ski Link: https://patch.msgid.link/20261014200608.118770-4-amade@asmblr.net Signed-off-by: Mark Brown --- sound/soc/soc-topology-test.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sound/soc/soc-topology-test.c b/sound/soc/soc-topology-test.c index bcfb52061133..8df845a196fe 100644 --- a/sound/soc/soc-topology-test.c +++ b/sound/soc/soc-topology-test.c @@ -24,8 +24,12 @@ static struct device *test_dev; static int snd_soc_tplg_test_init(struct kunit *test) { - test_dev = kunit_device_register(test, "sound-soc-topology-test"); - test_dev = get_device(test_dev); + struct device *dev; + + dev = kunit_device_register(test, "sound-soc-topology-test"); + if (IS_ERR(dev)) + return PTR_ERR(dev); + test_dev = get_device(dev); if (!test_dev) return -ENODEV; From 77dfd98b1642b9bc6c2312bd8b805c321fb878b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amadeusz=20S=C5=82awi=C5=84ski?= Date: Wed, 14 Oct 2026 22:06:04 +0200 Subject: [PATCH 799/924] ASoC: soc-card-test: Check return value of kunit_device_register() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Call to kunit_device_register() can fail. Check return result of register call. While at it introduce helper variable so that if it fails put_device is called on properly initialized variable. Fixes: ef7784e41db7 ("ASoC: soc-card: Add KUnit test case for snd_soc_card_get_kcontrol") Signed-off-by: Amadeusz SÅ‚awiÅ„ski Link: https://patch.msgid.link/20261014200608.118770-5-amade@asmblr.net Signed-off-by: Mark Brown --- sound/soc/soc-card-test.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/soc/soc-card-test.c b/sound/soc/soc-card-test.c index 47dfd8b1babc..610b8220786d 100644 --- a/sound/soc/soc-card-test.c +++ b/sound/soc/soc-card-test.c @@ -70,6 +70,7 @@ static void test_snd_soc_card_get_kcontrol(struct kunit *test) static int soc_card_test_case_init(struct kunit *test) { struct soc_card_test_priv *priv; + struct device *dev; int ret; priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); @@ -82,8 +83,10 @@ static int soc_card_test_case_init(struct kunit *test) if (!priv->card) return -ENOMEM; - priv->card_dev = kunit_device_register(test, "sound-soc-card-test"); - priv->card_dev = get_device(priv->card_dev); + dev = kunit_device_register(test, "sound-soc-card-test"); + if (IS_ERR(dev)) + return PTR_ERR(dev); + priv->card_dev = get_device(dev); if (!priv->card_dev) return -ENODEV; From fc081cdfc6b370f6b6853cdeb4313f92561770d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amadeusz=20S=C5=82awi=C5=84ski?= Date: Wed, 14 Oct 2026 22:06:05 +0200 Subject: [PATCH 800/924] ASoC: soc-ac97: Fix error handling in snd_soc_new_ac97_component() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If call to snd_soc_ac97_init_gpio() fails, proper removal procedure should mirror snd_soc_free_ac97_component() behaviour and call device_del() before put_device(). Fixes: 9bf5c3d11f1f ("ASoC: ac97: add gpio chip") Signed-off-by: Amadeusz SÅ‚awiÅ„ski Link: https://patch.msgid.link/20261014200608.118770-6-amade@asmblr.net Signed-off-by: Mark Brown --- sound/soc/soc-ac97.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/soc-ac97.c b/sound/soc/soc-ac97.c index 5f81e3606655..5c573c8dc249 100644 --- a/sound/soc/soc-ac97.c +++ b/sound/soc/soc-ac97.c @@ -241,10 +241,12 @@ struct snd_ac97 *snd_soc_new_ac97_component(struct snd_soc_component *component, ret = snd_soc_ac97_init_gpio(ac97, component); if (ret) - goto err_put_device; + goto err_del_device; return ac97; +err_del_device: + device_del(&ac97->dev); err_put_device: put_device(&ac97->dev); return ERR_PTR(ret); From 9d4bab22932fb6202a4a4e9cbe5e719ad6b3a438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amadeusz=20S=C5=82awi=C5=84ski?= Date: Wed, 14 Oct 2026 22:06:06 +0200 Subject: [PATCH 801/924] ASoC: soc-ac97: Fix debug message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seems like copy paste mistake, it should have been input, as in the name of the function. Signed-off-by: Amadeusz SÅ‚awiÅ„ski Link: https://patch.msgid.link/20261014200608.118770-7-amade@asmblr.net Signed-off-by: Mark Brown --- sound/soc/soc-ac97.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/soc-ac97.c b/sound/soc/soc-ac97.c index 5c573c8dc249..9d38b38d88df 100644 --- a/sound/soc/soc-ac97.c +++ b/sound/soc/soc-ac97.c @@ -69,7 +69,7 @@ static int snd_soc_ac97_gpio_direction_in(struct gpio_chip *chip, { struct snd_soc_component *component = gpio_to_component(chip); - dev_dbg(component->dev, "set gpio %d to output\n", offset); + dev_dbg(component->dev, "set gpio %d to input\n", offset); return snd_soc_component_update_bits(component, AC97_GPIO_CFG, 1 << offset, 1 << offset); } From 9df30adce80dd6b0646e58c12a4cf7c6c88d0778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amadeusz=20S=C5=82awi=C5=84ski?= Date: Wed, 14 Oct 2026 22:06:07 +0200 Subject: [PATCH 802/924] ASoC: soc-component: Fix return type of snd_soc_pcm_component_pointer() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both caller and pointer callback expect the return value to be snd_pcm_uframes_t, however it was defined incorrectly leading to potential truncation of returned value. Fixes: 0035e2565b93 ("ASoC: soc-component: add snd_soc_pcm_component_pointer()") Signed-off-by: Amadeusz SÅ‚awiÅ„ski Link: https://patch.msgid.link/20261014200608.118770-8-amade@asmblr.net Signed-off-by: Mark Brown --- include/sound/soc-component.h | 2 +- sound/soc/soc-component.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index ac5e9412582e..ba860e28eec1 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -432,7 +432,7 @@ int snd_soc_component_compr_set_metadata(struct snd_compr_stream *cstream, int snd_soc_component_compr_get_metadata(struct snd_compr_stream *cstream, struct snd_compr_metadata *metadata); -int snd_soc_pcm_component_pointer(struct snd_pcm_substream *substream); +snd_pcm_uframes_t snd_soc_pcm_component_pointer(struct snd_pcm_substream *substream); int snd_soc_pcm_component_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg); int snd_soc_pcm_component_sync_stop(struct snd_pcm_substream *substream); diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 34efb270ce29..746fa4f54450 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -920,7 +920,7 @@ int snd_soc_component_test_bits(struct snd_soc_component *component, } EXPORT_SYMBOL_GPL(snd_soc_component_test_bits); -int snd_soc_pcm_component_pointer(struct snd_pcm_substream *substream) +snd_pcm_uframes_t snd_soc_pcm_component_pointer(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_component *component; From 568406bc832e7e9e333e2ab05aafd0b83ffb9610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amadeusz=20S=C5=82awi=C5=84ski?= Date: Wed, 14 Oct 2026 22:06:08 +0200 Subject: [PATCH 803/924] ASoC: soc-dapm: Remove widget from cache on removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a single DAPM widget is removed, it also needs to be removed from the widget cache to prevent dapm_wcache_lookup() from using a dangling pointer when checking if a widget is in cache. Since widgets may be cached across different DAPM contexts on the card (e.g. card routes), clear any matching cache entries across all DAPM contexts on the card. Since dapm_free_widgets() calls snd_soc_dapm_free_widget() anyway, it does not need to clear cache anymore. Signed-off-by: Amadeusz SÅ‚awiÅ„ski Link: https://patch.msgid.link/20261014200608.118770-9-amade@asmblr.net Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 74be5e209493..7455160b11fe 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2869,6 +2869,7 @@ static void dapm_free_path(struct snd_soc_dapm_path *path) */ void snd_soc_dapm_free_widget(struct snd_soc_dapm_widget *w) { + struct snd_soc_dapm_context *d; struct snd_soc_dapm_path *p, *next_p; enum snd_soc_dapm_direction dir; @@ -2889,6 +2890,22 @@ void snd_soc_dapm_free_widget(struct snd_soc_dapm_widget *w) dapm_debugfs_free_widget(w); + if (w->dapm) { + if (w->dapm->card) { + for_each_card_dapms(w->dapm->card, d) { + if (d->wcache_sink == w) + d->wcache_sink = NULL; + if (d->wcache_source == w) + d->wcache_source = NULL; + } + } else { + if (w->dapm->wcache_sink == w) + w->dapm->wcache_sink = NULL; + if (w->dapm->wcache_source == w) + w->dapm->wcache_source = NULL; + } + } + kfree(w->kcontrols); kfree_const(w->name); kfree_const(w->sname); @@ -2906,9 +2923,6 @@ static void dapm_free_widgets(struct snd_soc_dapm_context *dapm) continue; snd_soc_dapm_free_widget(w); } - - dapm->wcache_sink = NULL; - dapm->wcache_source = NULL; } static struct snd_soc_dapm_widget *dapm_find_widget( From 3901d17c976fcc4cf5930ba0350b779034091b80 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:58:43 +0000 Subject: [PATCH 804/924] ASoC: soc-component: tidyup be_hw_params_fixup() define position MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit be_hw_params_fixup() is one of snd_soc_component_driver callback function, but is defined as alone in a remote location. Let's move it to the same place as other callbacks. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/87o6cxyom4.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index ac5e9412582e..61898a45d83f 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -154,6 +154,8 @@ struct snd_soc_component_driver { struct snd_pcm_substream *substream); snd_pcm_sframes_t (*delay)(struct snd_soc_component *component, struct snd_pcm_substream *substream); + int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params); const struct snd_compress_ops *compress_ops; @@ -197,8 +199,7 @@ struct snd_soc_component_driver { /* this component uses topology and ignore machine driver FEs */ const char *ignore_machine; const char *topology_name_prefix; - int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params); + bool use_dai_pcm_id; /* use DAI link PCM ID as PCM device number */ int be_pcm_base; /* base device ID for all BE PCMs */ From 0ce431a9fc6941792ccf9fdc3e7e52f9823ccade Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:58:47 +0000 Subject: [PATCH 805/924] ASoC: soc-component: replace bool member to bit field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit soc-component has meny bit fields (A), no need to have bool member (B). Let's makes it as bit field, too struct snd_soc_component_driver { ... ^ unsigned int module_get_upon_open:1; (A) unsigned int idle_bias_on:1; | unsigned int suspend_bias_off:1; | ... v unsigned int legacy_dai_naming:1; ... (B) bool use_dai_pcm_id; ... } Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/87mrshyom0.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 61898a45d83f..149c4d4f9403 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -196,11 +196,13 @@ struct snd_soc_component_driver { unsigned int endianness:1; unsigned int legacy_dai_naming:1; + /* use DAI link PCM ID as PCM device number */ + unsigned int use_dai_pcm_id:1; + /* this component uses topology and ignore machine driver FEs */ const char *ignore_machine; const char *topology_name_prefix; - bool use_dai_pcm_id; /* use DAI link PCM ID as PCM device number */ int be_pcm_base; /* base device ID for all BE PCMs */ const char *debugfs_prefix; From ed2c53c841fe2390e60f52d5a95c815d7b5c37f5 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:58:51 +0000 Subject: [PATCH 806/924] ASoC: soc-component: move snd_soc_add_component_controls() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_soc_add_component_controls() is Component related function. And its parameter will be capsuled soon, will be not enable to access from soc-core.c. Move it to soc-component.c It renames function name - snd_soc_add_component_controls() + snd_soc_component_add_controls() Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/87ld81yolw.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 3 +++ include/sound/soc.h | 3 +-- sound/soc/soc-component.c | 21 +++++++++++++++++++++ sound/soc/soc-core.c | 19 ------------------- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 149c4d4f9403..b19abc5c2633 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -358,6 +358,9 @@ void snd_soc_component_init_regmap(struct snd_soc_component *component, void snd_soc_component_exit_regmap(struct snd_soc_component *component); #endif +int snd_soc_component_add_controls(struct snd_soc_component *component, + const struct snd_kcontrol_new *controls, unsigned int num_controls); + #define snd_soc_component_module_get_when_probe(component)\ snd_soc_component_module_get(component, NULL, 0) #define snd_soc_component_module_get_when_open(component, substream) \ diff --git a/include/sound/soc.h b/include/sound/soc.h index 08fdee15dba7..89612ac0c1d9 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -545,8 +545,6 @@ static inline int snd_soc_set_ac97_ops(struct snd_ac97_bus_ops *ops) struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template, void *data, const char *long_name, const char *prefix); -int snd_soc_add_component_controls(struct snd_soc_component *component, - const struct snd_kcontrol_new *controls, unsigned int num_controls); int snd_soc_add_card_controls(struct snd_soc_card *soc_card, const struct snd_kcontrol_new *controls, int num_controls); int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, @@ -1542,5 +1540,6 @@ static inline void _snd_soc_dpcm_mutex_assert_held_r(struct snd_soc_pcm_runtime #define snd_soc_add_dai_controls snd_soc_dai_add_controls #define snd_soc_register_dai snd_soc_dai_register #define snd_soc_unregister_dai snd_soc_dai_unregister +#define snd_soc_add_component_controls snd_soc_component_add_controls #endif diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 34efb270ce29..5a9b526676f4 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -12,6 +12,7 @@ #include #include #include +#include "soc-internal.h" #define soc_component_ret(dai, ret) _soc_component_ret(dai, __func__, ret) static inline int _soc_component_ret(struct snd_soc_component *component, const char *func, int ret) @@ -320,6 +321,26 @@ int snd_soc_component_probe(struct snd_soc_component *component) return soc_component_ret(component, ret); } +/** + * snd_soc_component_add_controls - Add an array of controls to a component. + * + * @component: Component to add controls to + * @controls: Array of controls to add + * @num_controls: Number of elements in the array + * + * Return: 0 for success, else error. + */ +int snd_soc_component_add_controls(struct snd_soc_component *component, + const struct snd_kcontrol_new *controls, + unsigned int num_controls) +{ + struct snd_card *card = component->card->snd_card; + + return snd_soc_add_controls(card, component->dev, controls, + num_controls, component->name_prefix, component); +} +EXPORT_SYMBOL_GPL(snd_soc_component_add_controls); + int snd_soc_component_fixup_controls(struct snd_soc_component *component) { int ret = 0; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 69181cc0258b..06f001baf247 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2305,25 +2305,6 @@ int snd_soc_add_controls(struct snd_card *card, struct device *dev, return 0; } -/** - * snd_soc_add_component_controls - Add an array of controls to a component. - * - * @component: Component to add controls to - * @controls: Array of controls to add - * @num_controls: Number of elements in the array - * - * Return: 0 for success, else error. - */ -int snd_soc_add_component_controls(struct snd_soc_component *component, - const struct snd_kcontrol_new *controls, unsigned int num_controls) -{ - struct snd_card *card = component->card->snd_card; - - return snd_soc_add_controls(card, component->dev, controls, - num_controls, component->name_prefix, component); -} -EXPORT_SYMBOL_GPL(snd_soc_add_component_controls); - /** * snd_soc_add_card_controls - add an array of controls to a SoC card. * Convenience function to add a list of controls. From ee0daa7a3101dbefd728d19b514785b5f0b3da2c Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:58:55 +0000 Subject: [PATCH 807/924] ASoC: soc-component: move snd_soc_component_fixup_controls() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_soc_component_xxx_controls() are located between _probe() and _remove() in soc-component.c, and are located in scattered positions in soc-component.h. Fixup them. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/87jynlyolt.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 2 +- sound/soc/soc-component.c | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index b19abc5c2633..0d316c48cb42 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -358,6 +358,7 @@ void snd_soc_component_init_regmap(struct snd_soc_component *component, void snd_soc_component_exit_regmap(struct snd_soc_component *component); #endif +int snd_soc_component_fixup_controls(struct snd_soc_component *component); int snd_soc_component_add_controls(struct snd_soc_component *component, const struct snd_kcontrol_new *controls, unsigned int num_controls); @@ -407,7 +408,6 @@ void snd_soc_component_suspend(struct snd_soc_component *component); void snd_soc_component_resume(struct snd_soc_component *component); int snd_soc_component_is_suspended(struct snd_soc_component *component); int snd_soc_component_probe(struct snd_soc_component *component); -int snd_soc_component_fixup_controls(struct snd_soc_component *component); void snd_soc_component_remove(struct snd_soc_component *component); int snd_soc_component_of_xlate_dai_id(struct snd_soc_component *component, struct device_node *ep); diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 5a9b526676f4..4984cdf1f7b8 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -321,6 +321,12 @@ int snd_soc_component_probe(struct snd_soc_component *component) return soc_component_ret(component, ret); } +void snd_soc_component_remove(struct snd_soc_component *component) +{ + if (component->driver->remove) + component->driver->remove(component); +} + /** * snd_soc_component_add_controls - Add an array of controls to a component. * @@ -351,12 +357,6 @@ int snd_soc_component_fixup_controls(struct snd_soc_component *component) return soc_component_ret(component, ret); } -void snd_soc_component_remove(struct snd_soc_component *component) -{ - if (component->driver->remove) - component->driver->remove(component); -} - int snd_soc_component_of_xlate_dai_id(struct snd_soc_component *component, struct device_node *ep) { From 81db2fed151e1dcc38e5f1bc9dfb61c22d0ffb28 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:58:58 +0000 Subject: [PATCH 808/924] ASoC: soc-component: move soc_component_to_node() to soc-component.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct snd_soc_component will be capsuled soon, its member will not be able to access from non soc-component.c. Move soc_component_to_node() to soc-component.c It renames function name - soc_component_to_node() + snd_soc_component_to_node() Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/87ik35yolp.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-component.c | 11 +++++++++++ sound/soc/soc-core.c | 18 +++--------------- sound/soc/soc-internal.h | 6 ++++++ 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 4984cdf1f7b8..e431b07159eb 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -1276,3 +1276,14 @@ int snd_soc_pcm_component_ack(struct snd_pcm_substream *substream) return 0; } + +struct device_node *snd_soc_component_to_node(struct snd_soc_component *component) +{ + struct device_node *of_node; + + of_node = component->dev->of_node; + if (!of_node && component->dev->parent) + of_node = component->dev->parent->of_node; + + return of_node; +} diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 06f001baf247..e6cc2bb73fda 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -771,18 +771,6 @@ static void soc_resume_init(struct snd_soc_card *card) static inline void soc_resume_init(struct snd_soc_card *card) { } #endif -static struct device_node -*soc_component_to_node(struct snd_soc_component *component) -{ - struct device_node *of_node; - - of_node = component->dev->of_node; - if (!of_node && component->dev->parent) - of_node = component->dev->parent->of_node; - - return of_node; -} - struct of_phandle_args *snd_soc_copy_dai_args(struct device *dev, const struct of_phandle_args *args) { @@ -815,7 +803,7 @@ static int snd_soc_is_matching_component( return 0; } - component_of_node = soc_component_to_node(component); + component_of_node = snd_soc_component_to_node(component); if (dlc->of_node && component_of_node != dlc->of_node) return 0; @@ -1339,7 +1327,7 @@ static int soc_init_pcm_runtime(struct snd_soc_card *card, static void soc_set_name_prefix(struct snd_soc_card *card, struct snd_soc_component *component) { - struct device_node *of_node = soc_component_to_node(component); + struct device_node *of_node = snd_soc_component_to_node(component); const char *str; int ret, i; @@ -3268,7 +3256,7 @@ int snd_soc_get_dlc(const struct of_phandle_args *args, struct snd_soc_dai_link_ guard(mutex)(&client_mutex); for_each_component(pos) { - struct device_node *component_of_node = soc_component_to_node(pos); + struct device_node *component_of_node = snd_soc_component_to_node(pos); if (component_of_node != args->np || !pos->num_dai) continue; diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 31559918355f..a08e6e8ef17f 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -36,4 +36,10 @@ struct clk *snd_soc_dai_get_bclk(struct snd_soc_dai *dai); unsigned int snd_soc_dai_get_bclk_ratio(struct snd_soc_dai *dai); void snd_soc_dai_active_update(struct snd_soc_dai *dai, int stream, int action); +/* + * In soc-component + */ +struct device_node *snd_soc_component_to_node(struct snd_soc_component *component); + + #endif /* __SOC_INTERNAL_H */ From 78a856d727c9bf99c6f0cd643e087061b82c54ca Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:59:02 +0000 Subject: [PATCH 809/924] ASoC: soc-component: move snd_soc_is_matching_component() to soc-component.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct snd_soc_component will be capsuled soon, its member will not be able to access from non soc-component.c. Move snd_soc_is_matching_component() to soc-component.c It renames function name - snd_soc_is_matching_component() + snd_soc_component_matches_dlc() Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/87h5ipyolm.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-component.c | 27 +++++++++++++++++++++++++++ sound/soc/soc-core.c | 36 ++++-------------------------------- sound/soc/soc-internal.h | 3 ++- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index e431b07159eb..0635988b17c4 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -1287,3 +1287,30 @@ struct device_node *snd_soc_component_to_node(struct snd_soc_component *componen return of_node; } + +bool snd_soc_component_matches_dlc(struct snd_soc_component *component, + const struct snd_soc_dai_link_component *dlc) +{ + struct device_node *component_of_node; + + if (!dlc) + return false; + + if (dlc->dai_args) { + struct snd_soc_dai *dai; + + for_each_component_dais(component, dai) + if (snd_soc_dai_matches_dlc(dai, dlc)) + return true; + return false; + } + + component_of_node = snd_soc_component_to_node(component); + + if (dlc->of_node && component_of_node != dlc->of_node) + return false; + if (dlc->name && strcmp(component->name, dlc->name)) + return false; + + return true; +} diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e6cc2bb73fda..e96b86561165 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -785,34 +785,6 @@ struct of_phandle_args *snd_soc_copy_dai_args(struct device *dev, } EXPORT_SYMBOL_GPL(snd_soc_copy_dai_args); -static int snd_soc_is_matching_component( - const struct snd_soc_dai_link_component *dlc, - struct snd_soc_component *component) -{ - struct device_node *component_of_node; - - if (!dlc) - return 0; - - if (dlc->dai_args) { - struct snd_soc_dai *dai; - - for_each_component_dais(component, dai) - if (snd_soc_dai_matches_dlc(dai, dlc)) - return 1; - return 0; - } - - component_of_node = snd_soc_component_to_node(component); - - if (dlc->of_node && component_of_node != dlc->of_node) - return 0; - if (dlc->name && strcmp(component->name, dlc->name)) - return 0; - - return 1; -} - static struct snd_soc_component *soc_find_component( const struct snd_soc_dai_link_component *dlc) { @@ -829,7 +801,7 @@ static struct snd_soc_component *soc_find_component( * CPU component and generic DMAEngine component */ for_each_component(component) - if (snd_soc_is_matching_component(dlc, component)) + if (snd_soc_component_matches_dlc(component, dlc)) return component; return NULL; @@ -856,7 +828,7 @@ struct snd_soc_dai *snd_soc_find_dai( /* Find CPU DAI from registered DAIs */ for_each_component(component) - if (snd_soc_is_matching_component(dlc, component)) + if (snd_soc_component_matches_dlc(component, dlc)) for_each_component_dais(component, dai) if (snd_soc_dai_matches_dlc(dai, dlc)) return dai; @@ -1155,7 +1127,7 @@ static int snd_soc_add_pcm_runtime(struct snd_soc_card *card, /* Find PLATFORM from registered PLATFORMs */ for_each_link_platforms(dai_link, i, platform) { for_each_component(component) { - if (!snd_soc_is_matching_component(platform, component)) + if (!snd_soc_component_matches_dlc(component, platform)) continue; if (snd_soc_component_is_dummy(component) && component->num_dai) @@ -1334,7 +1306,7 @@ static void soc_set_name_prefix(struct snd_soc_card *card, for (i = 0; i < card->num_configs; i++) { struct snd_soc_codec_conf *map = &card->codec_conf[i]; - if (snd_soc_is_matching_component(&map->dlc, component) && + if (snd_soc_component_matches_dlc(component, &map->dlc) && map->name_prefix) { component->name_prefix = map->name_prefix; return; diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index a08e6e8ef17f..76353d52ce97 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -40,6 +40,7 @@ void snd_soc_dai_active_update(struct snd_soc_dai *dai, int stream, int action); * In soc-component */ struct device_node *snd_soc_component_to_node(struct snd_soc_component *component); - +bool snd_soc_component_matches_dlc(struct snd_soc_component *component, + const struct snd_soc_dai_link_component *dlc); #endif /* __SOC_INTERNAL_H */ From 8b1121c5dff073a55d6e4833c2c9cabb29f09746 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:59:05 +0000 Subject: [PATCH 810/924] ASoC: soc-component: move soc_{probe/remove}_component() to soc-component.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct snd_soc_component will be capsuled soon, its member will not be able to access from non soc-component.c. Move soc_{probe/remove}_component() to soc-component.c It renames function name - soc_{probe/remove}_component() + snd_soc_component_{probe/remove}() Then soc-component.c had original snd_soc_component_{probe/remove}() which is wrapper function. These wil be deployed internally. Releted fucntions also be moved. It renames function name. soc_set_name_prefix() -> snd_soc_component_set_name_prefix() soc_init_component_debugfs() -> snd_soc_component_debugfs_init() soc_cleanup_component_debugfs() -> snd_soc_component_debugfs_cleanup() Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/87fqy9yoli.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 2 - sound/soc/soc-component.c | 190 +++++++++++++++++++++++++++++++--- sound/soc/soc-core.c | 179 +------------------------------- sound/soc/soc-internal.h | 2 + 4 files changed, 180 insertions(+), 193 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 0d316c48cb42..8c8141fe9e77 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -407,8 +407,6 @@ int snd_soc_component_close(struct snd_soc_component *component, void snd_soc_component_suspend(struct snd_soc_component *component); void snd_soc_component_resume(struct snd_soc_component *component); int snd_soc_component_is_suspended(struct snd_soc_component *component); -int snd_soc_component_probe(struct snd_soc_component *component); -void snd_soc_component_remove(struct snd_soc_component *component); int snd_soc_component_of_xlate_dai_id(struct snd_soc_component *component, struct device_node *ep); int snd_soc_component_of_xlate_dai_name(struct snd_soc_component *component, diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 0635988b17c4..25f50041fad3 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -8,6 +8,7 @@ // Mark Brown // Kuninori Morimoto // +#include #include #include #include @@ -311,22 +312,6 @@ int snd_soc_component_is_suspended(struct snd_soc_component *component) return component->suspended; } -int snd_soc_component_probe(struct snd_soc_component *component) -{ - int ret = 0; - - if (component->driver->probe) - ret = component->driver->probe(component); - - return soc_component_ret(component, ret); -} - -void snd_soc_component_remove(struct snd_soc_component *component) -{ - if (component->driver->remove) - component->driver->remove(component); -} - /** * snd_soc_component_add_controls - Add an array of controls to a component. * @@ -1314,3 +1299,176 @@ bool snd_soc_component_matches_dlc(struct snd_soc_component *component, return true; } + +#ifdef CONFIG_DEBUG_FS +static void snd_soc_component_debugfs_init(struct snd_soc_component *component) +{ + if (!component->card->debugfs_card_root) + return; + + if (component->driver->debugfs_prefix) { + char *name; + + name = kasprintf(GFP_KERNEL, "%s:%s", + component->driver->debugfs_prefix, component->name); + if (name) { + component->debugfs_root = debugfs_create_dir(name, + component->card->debugfs_card_root); + kfree(name); + } + } else { + component->debugfs_root = debugfs_create_dir(component->name, + component->card->debugfs_card_root); + } + + snd_soc_dapm_debugfs_init(snd_soc_component_to_dapm(component), + component->debugfs_root); +} + +static void snd_soc_component_debugfs_cleanup(struct snd_soc_component *component) +{ + if (!component->debugfs_root) + return; + debugfs_remove_recursive(component->debugfs_root); + component->debugfs_root = NULL; +} +#else +static inline void snd_soc_component_debugfs_init(struct snd_soc_component *component) { } +static inline void snd_soc_component_debugfs_cleanup(struct snd_soc_component *component) { } +#endif /* CONFIG_DEBUG_FS */ + +static void snd_soc_component_set_name_prefix(struct snd_soc_component *component) +{ + struct snd_soc_card *card = component->card; + struct device_node *of_node = snd_soc_component_to_node(component); + const char *str; + int ret, i; + + for (i = 0; i < card->num_configs; i++) { + struct snd_soc_codec_conf *map = &card->codec_conf[i]; + + if (snd_soc_component_matches_dlc(component, &map->dlc) && + map->name_prefix) { + component->name_prefix = map->name_prefix; + return; + } + } + + /* + * If there is no configuration table or no match in the table, + * check if a prefix is provided in the node + */ + ret = of_property_read_string(of_node, "sound-name-prefix", &str); + if (ret < 0) + return; + + component->name_prefix = str; +} + +void snd_soc_component_remove(struct snd_soc_component *component, int probed) +{ + if (!component->card) + return; + + if (probed && component->driver->remove) + component->driver->remove(component); + + list_del_init(&component->card_list); + snd_soc_dapm_free(snd_soc_component_to_dapm(component)); + snd_soc_component_debugfs_cleanup(component); + component->card = NULL; + snd_soc_component_module_put_when_remove(component); +} + +int snd_soc_component_probe(struct snd_soc_component *component, struct snd_soc_card *card) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct snd_soc_dai *dai; + int probed = 0; + int ret; + + if (snd_soc_component_is_dummy(component)) + return 0; + + if (component->card) { + if (component->card != card) { + dev_err(component->dev, + "Trying to bind component \"%s\" to card \"%s\" but is already bound to card \"%s\"\n", + component->name, card->name, component->card->name); + return -ENODEV; + } + return 0; + } + + ret = snd_soc_component_module_get_when_probe(component); + if (ret < 0) + return ret; + + component->card = card; + snd_soc_component_set_name_prefix(component); + + snd_soc_component_debugfs_init(component); + + snd_soc_dapm_init(dapm, card, component); + + ret = snd_soc_dapm_new_controls(dapm, + component->driver->dapm_widgets, + component->driver->num_dapm_widgets); + + if (ret != 0) { + dev_err(component->dev, + "Failed to create new controls %d\n", ret); + goto err_probe; + } + + for_each_component_dais(component, dai) { + ret = snd_soc_dapm_new_dai_widgets(dapm, dai); + if (ret != 0) { + dev_err(component->dev, + "Failed to create DAI widgets %d\n", ret); + goto err_probe; + } + } + + if (component->driver->probe) { + ret = component->driver->probe(component); + if (ret < 0) + goto err_probe; + } + + WARN(!snd_soc_dapm_get_idle_bias(dapm) && + snd_soc_dapm_get_bias_level(dapm) != SND_SOC_BIAS_OFF, + "codec %s can not start from non-off bias with idle_bias_off==1\n", + component->name); + probed = 1; + + /* + * machine specific init + * see + * snd_soc_component_set_aux() + */ + ret = snd_soc_component_init(component); + if (ret < 0) + goto err_probe; + + ret = snd_soc_component_add_controls(component, + component->driver->controls, + component->driver->num_controls); + if (ret < 0) + goto err_probe; + + ret = snd_soc_dapm_add_routes(dapm, + component->driver->dapm_routes, + component->driver->num_dapm_routes); + if (ret < 0) + goto err_probe; + + /* see for_each_card_components */ + list_add(&component->card_list, &card->component_dev_list); + +err_probe: + if (ret < 0) + snd_soc_component_remove(component, probed); + + return ret; +} diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e96b86561165..cd318d232ee3 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -133,38 +133,6 @@ static const struct attribute_group *soc_dev_attr_groups[] = { struct dentry *snd_soc_debugfs_root; EXPORT_SYMBOL_GPL(snd_soc_debugfs_root); -static void soc_init_component_debugfs(struct snd_soc_component *component) -{ - if (!component->card->debugfs_card_root) - return; - - if (component->driver->debugfs_prefix) { - char *name; - - name = kasprintf(GFP_KERNEL, "%s:%s", - component->driver->debugfs_prefix, component->name); - if (name) { - component->debugfs_root = debugfs_create_dir(name, - component->card->debugfs_card_root); - kfree(name); - } - } else { - component->debugfs_root = debugfs_create_dir(component->name, - component->card->debugfs_card_root); - } - - snd_soc_dapm_debugfs_init(snd_soc_component_to_dapm(component), - component->debugfs_root); -} - -static void soc_cleanup_component_debugfs(struct snd_soc_component *component) -{ - if (!component->debugfs_root) - return; - debugfs_remove_recursive(component->debugfs_root); - component->debugfs_root = NULL; -} - static int dai_list_show(struct seq_file *m, void *v) { struct snd_soc_component *component; @@ -225,8 +193,6 @@ static void snd_soc_debugfs_exit(void) #else -static inline void soc_init_component_debugfs(struct snd_soc_component *component) { } -static inline void soc_cleanup_component_debugfs(struct snd_soc_component *component) { } static inline void soc_init_card_debugfs(struct snd_soc_card *card) { } static inline void soc_cleanup_card_debugfs(struct snd_soc_card *card) { } static inline void snd_soc_debugfs_init(void) { } @@ -1296,143 +1262,6 @@ static int soc_init_pcm_runtime(struct snd_soc_card *card, return ret; } -static void soc_set_name_prefix(struct snd_soc_card *card, - struct snd_soc_component *component) -{ - struct device_node *of_node = snd_soc_component_to_node(component); - const char *str; - int ret, i; - - for (i = 0; i < card->num_configs; i++) { - struct snd_soc_codec_conf *map = &card->codec_conf[i]; - - if (snd_soc_component_matches_dlc(component, &map->dlc) && - map->name_prefix) { - component->name_prefix = map->name_prefix; - return; - } - } - - /* - * If there is no configuration table or no match in the table, - * check if a prefix is provided in the node - */ - ret = of_property_read_string(of_node, "sound-name-prefix", &str); - if (ret < 0) - return; - - component->name_prefix = str; -} - -static void soc_remove_component(struct snd_soc_component *component, - int probed) -{ - - if (!component->card) - return; - - if (probed) - snd_soc_component_remove(component); - - list_del_init(&component->card_list); - snd_soc_dapm_free(snd_soc_component_to_dapm(component)); - soc_cleanup_component_debugfs(component); - component->card = NULL; - snd_soc_component_module_put_when_remove(component); -} - -static int soc_probe_component(struct snd_soc_card *card, - struct snd_soc_component *component) -{ - struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); - struct snd_soc_dai *dai; - int probed = 0; - int ret; - - if (snd_soc_component_is_dummy(component)) - return 0; - - if (component->card) { - if (component->card != card) { - dev_err(component->dev, - "Trying to bind component \"%s\" to card \"%s\" but is already bound to card \"%s\"\n", - component->name, card->name, component->card->name); - return -ENODEV; - } - return 0; - } - - ret = snd_soc_component_module_get_when_probe(component); - if (ret < 0) - return ret; - - component->card = card; - soc_set_name_prefix(card, component); - - soc_init_component_debugfs(component); - - snd_soc_dapm_init(dapm, card, component); - - ret = snd_soc_dapm_new_controls(dapm, - component->driver->dapm_widgets, - component->driver->num_dapm_widgets); - - if (ret != 0) { - dev_err(component->dev, - "Failed to create new controls %d\n", ret); - goto err_probe; - } - - for_each_component_dais(component, dai) { - ret = snd_soc_dapm_new_dai_widgets(dapm, dai); - if (ret != 0) { - dev_err(component->dev, - "Failed to create DAI widgets %d\n", ret); - goto err_probe; - } - } - - ret = snd_soc_component_probe(component); - if (ret < 0) - goto err_probe; - - WARN(!snd_soc_dapm_get_idle_bias(dapm) && - snd_soc_dapm_get_bias_level(dapm) != SND_SOC_BIAS_OFF, - "codec %s can not start from non-off bias with idle_bias_off==1\n", - component->name); - probed = 1; - - /* - * machine specific init - * see - * snd_soc_component_set_aux() - */ - ret = snd_soc_component_init(component); - if (ret < 0) - goto err_probe; - - ret = snd_soc_add_component_controls(component, - component->driver->controls, - component->driver->num_controls); - if (ret < 0) - goto err_probe; - - ret = snd_soc_dapm_add_routes(dapm, - component->driver->dapm_routes, - component->driver->num_dapm_routes); - if (ret < 0) - goto err_probe; - - /* see for_each_card_components */ - list_add(&component->card_list, &card->component_dev_list); - -err_probe: - if (ret < 0) - soc_remove_component(component, probed); - - return ret; -} - static void soc_remove_link_dais(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; @@ -1475,7 +1304,7 @@ static void soc_remove_link_components(struct snd_soc_card *card) if (component->driver->remove_order != order) continue; - soc_remove_component(component, 1); + snd_soc_component_remove(component, 1); } } } @@ -1493,7 +1322,7 @@ static int soc_probe_link_components(struct snd_soc_card *card) if (component->driver->probe_order != order) continue; - ret = soc_probe_component(card, component); + ret = snd_soc_component_probe(component, card); if (ret < 0) return ret; } @@ -1545,7 +1374,7 @@ static int soc_probe_aux_devices(struct snd_soc_card *card) if (component->driver->probe_order != order) continue; - ret = soc_probe_component(card, component); + ret = snd_soc_component_probe(component, card); if (ret < 0) return ret; } @@ -1562,7 +1391,7 @@ static void soc_remove_aux_devices(struct snd_soc_card *card) for_each_comp_order(order) { for_each_card_auxs_safe(card, comp, _comp) { if (comp->driver->remove_order == order) - soc_remove_component(comp, 1); + snd_soc_component_remove(comp, 1); } } } diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 76353d52ce97..17ed22c2cd50 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -42,5 +42,7 @@ void snd_soc_dai_active_update(struct snd_soc_dai *dai, int stream, int action); struct device_node *snd_soc_component_to_node(struct snd_soc_component *component); bool snd_soc_component_matches_dlc(struct snd_soc_component *component, const struct snd_soc_dai_link_component *dlc); +void snd_soc_component_remove(struct snd_soc_component *component, int probed); +int snd_soc_component_probe(struct snd_soc_component *component, struct snd_soc_card *card); #endif /* __SOC_INTERNAL_H */ From 5c904834414c9faa66d507cc3e77ed2197ba6a99 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:59:11 +0000 Subject: [PATCH 811/924] ASoC: soc-component: move snd_soc_component_to_dapm() to soc-component.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct snd_soc_component will be capsuled soon, its member will not be able to access from non soc-component.c. Move snd_soc_component_to_dapm() to soc-component.c Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/87ecdtyolc.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 11 +---------- sound/soc/soc-component.c | 6 ++++++ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 8c8141fe9e77..08cb74001fe3 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -272,16 +272,7 @@ struct snd_soc_component { snd_soc_dai_to_list(dai) != &(component)->dai_list; \ dai = _dai, _dai = snd_soc_dai_from_list(snd_soc_dai_to_list(_dai)->next)) -/** - * snd_soc_component_to_dapm() - Returns the DAPM context associated with a - * component - * @component: The component for which to get the DAPM context - */ -static inline struct snd_soc_dapm_context *snd_soc_component_to_dapm( - struct snd_soc_component *component) -{ - return component->dapm; -} +struct snd_soc_dapm_context *snd_soc_component_to_dapm(struct snd_soc_component *component); /** * snd_soc_component_cache_sync() - Sync the register cache with the hardware diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 25f50041fad3..ca776b6a7fd0 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -49,6 +49,12 @@ void *snd_soc_component_to_priv(struct snd_soc_component *component) } EXPORT_SYMBOL_GPL(snd_soc_component_to_priv); +struct snd_soc_dapm_context *snd_soc_component_to_dapm(struct snd_soc_component *component) +{ + return component->dapm; +} +EXPORT_SYMBOL_GPL(snd_soc_component_to_dapm); + /* * We might want to check substream by using list. * In such case, we can update these macros. From 3f78fa38938a972f90fef8d4d2010e5f6fa4557a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:59:14 +0000 Subject: [PATCH 812/924] ASoC: soc-component: move snd_soc_component_cache_sync() to soc-component.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct snd_soc_component will be capsuled soon, its member will not be able to access from non soc-component.c. Move snd_soc_component_cache_sync() to soc-component.c It renames function name - snd_soc_component_cache_sync() + snd_soc_component_regmap_cache_sync() Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/87cxtdyol9.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 17 ++++------------- sound/soc/soc-component.c | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 08cb74001fe3..1a5c9d0d386d 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -273,19 +273,6 @@ struct snd_soc_component { dai = _dai, _dai = snd_soc_dai_from_list(snd_soc_dai_to_list(_dai)->next)) struct snd_soc_dapm_context *snd_soc_component_to_dapm(struct snd_soc_component *component); - -/** - * snd_soc_component_cache_sync() - Sync the register cache with the hardware - * @component: COMPONENT to sync - * - * Note: This function will call regcache_sync() - */ -static inline int snd_soc_component_cache_sync( - struct snd_soc_component *component) -{ - return regcache_sync(component->regmap); -} - struct snd_soc_component *snd_soc_component_new(struct device *dev, const char *name, const struct snd_soc_component_driver *driver); int snd_soc_component_register(struct snd_soc_component *component, @@ -343,6 +330,7 @@ int snd_soc_component_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level); int snd_soc_component_regmap_val_bytes(struct snd_soc_component *component); +int snd_soc_component_regmap_cache_sync(struct snd_soc_component *component); #ifdef CONFIG_REGMAP void snd_soc_component_init_regmap(struct snd_soc_component *component, struct regmap *regmap); @@ -455,4 +443,7 @@ int snd_soc_pcm_component_ack(struct snd_pcm_substream *substream); void snd_soc_pcm_component_delay(struct snd_pcm_substream *substream, snd_pcm_sframes_t *cpu_delay, snd_pcm_sframes_t *codec_delay); +/* REMOVE ME */ +#define snd_soc_component_cache_sync snd_soc_component_regmap_cache_sync + #endif /* __SOC_COMPONENT_H */ diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index ca776b6a7fd0..737b17ed60d8 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -391,6 +391,21 @@ int snd_soc_component_regmap_val_bytes(struct snd_soc_component *component) } EXPORT_SYMBOL_GPL(snd_soc_component_regmap_val_bytes); +/** + * snd_soc_component_regmap_cache_sync() - Sync the register cache with the hardware + * @component: COMPONENT to sync + * + * Note: This function will call regcache_sync() + */ +int snd_soc_component_regmap_cache_sync(struct snd_soc_component *component) +{ + if (!component->regmap) + return 0; + + return regcache_sync(component->regmap); +} +EXPORT_SYMBOL_GPL(snd_soc_component_regmap_cache_sync); + #ifdef CONFIG_REGMAP /** From 614749f16e94a23f0c46fb04f694f9415540d394 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:59:18 +0000 Subject: [PATCH 813/924] ASoC: soc-component: move snd_soc_component_active() to soc-component.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct snd_soc_component will be capsuled soon, its member will not be able to access from non soc-component.c. Move snd_soc_component_active() to soc-component.c Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/87bj8xyol6.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 6 +----- sound/soc/soc-component.c | 6 ++++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 1a5c9d0d386d..0f955f5cbebc 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -365,11 +365,7 @@ static inline void *snd_soc_component_get_drvdata(struct snd_soc_component *c) return dev_get_drvdata(c->dev); } -static inline unsigned int -snd_soc_component_active(struct snd_soc_component *component) -{ - return component->active; -} +unsigned int snd_soc_component_active(struct snd_soc_component *component); /* component controls */ struct snd_kcontrol *snd_soc_component_get_kcontrol(struct snd_soc_component *component, diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 737b17ed60d8..962cd7ab3cc2 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -55,6 +55,12 @@ struct snd_soc_dapm_context *snd_soc_component_to_dapm(struct snd_soc_component } EXPORT_SYMBOL_GPL(snd_soc_component_to_dapm); +unsigned int snd_soc_component_active(struct snd_soc_component *component) +{ + return component->active; +} +EXPORT_SYMBOL_GPL(snd_soc_component_active); + /* * We might want to check substream by using list. * In such case, we can update these macros. From 8c186bc6b77be4913f5869a68d547dc52976fa9d Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:59:21 +0000 Subject: [PATCH 814/924] ASoC: soc-component: move component_list to soc-component.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct snd_soc_component will be capsuled soon, its member will not be able to access from non soc-component.c. Move component_list to soc-component.c Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/87a4ohyol2.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-component.c | 6 ++++++ sound/soc/soc-core.c | 7 +------ sound/soc/soc-internal.h | 4 ++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 962cd7ab3cc2..86ede607ba31 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -31,6 +31,12 @@ static inline int _soc_component_ret_reg_rw(struct snd_soc_component *component, func, component->name, reg); } +static LIST_HEAD(component_list); +struct list_head *snd_soc_component_get_list_head(void) +{ + return &component_list; +} + const char *snd_soc_component_name(struct snd_soc_component *component) { return component->name; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index cd318d232ee3..0e935de91d0a 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -47,13 +47,8 @@ #include DEFINE_MUTEX(client_mutex); - -static LIST_HEAD(component_list); static LIST_HEAD(unbind_card_list); -#define for_each_component(component) \ - list_for_each_entry(component, &component_list, list) - /* * This is used if driver don't need to have CPU/Codec/Platform * dai_link. see soc.h @@ -2396,7 +2391,7 @@ int snd_soc_component_register(struct snd_soc_component *component, } /* see for_each_component */ - list_add(&component->list, &component_list); + list_add(&component->list, snd_soc_component_get_list_head()); list_for_each_entry_safe(card, c, &unbind_card_list, list) call_soc_bind_card(card); diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 17ed22c2cd50..b844b27574c8 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -44,5 +44,9 @@ bool snd_soc_component_matches_dlc(struct snd_soc_component *component, const struct snd_soc_dai_link_component *dlc); void snd_soc_component_remove(struct snd_soc_component *component, int probed); int snd_soc_component_probe(struct snd_soc_component *component, struct snd_soc_card *card); +struct list_head *snd_soc_component_get_list_head(void); + +#define for_each_component(component) \ + list_for_each_entry(component, snd_soc_component_get_list_head(), list) #endif /* __SOC_INTERNAL_H */ From 1fec554d55aa41830d7a3c54fce904bbff7e056f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:59:24 +0000 Subject: [PATCH 815/924] ASoC: soc-component: move snd_soc_lookup_component() to soc-component.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct snd_soc_component will be capsuled soon, its member will not be able to access from non soc-component.c. Move snd_soc_lookup_component() to soc-component.c It renames function name snd_soc_lookup_component_nolocked() -> snd_soc_component_lookup_nolock() snd_soc_lookup_component() -> snd_soc_component_lookup() snd_soc_lookup_component_by_name() -> snd_soc_component_lookup_by_name() Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/878q41yokz.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 8 ++++++ include/sound/soc.h | 5 ---- sound/soc/soc-component.c | 48 +++++++++++++++++++++++++++++++++++ sound/soc/soc-core.c | 48 ----------------------------------- 4 files changed, 56 insertions(+), 53 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 0f955f5cbebc..c287ec6f458e 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -289,6 +289,11 @@ void snd_soc_component_set_aux(struct snd_soc_component *component, int snd_soc_component_init(struct snd_soc_component *component); int snd_soc_component_is_dummy(struct snd_soc_component *component); +struct snd_soc_component *snd_soc_component_lookup_nolock(struct device *dev, + const char *driver_name); +struct snd_soc_component *snd_soc_component_lookup(struct device *dev, const char *driver_name); +struct snd_soc_component *snd_soc_component_lookup_by_name(const char *component_name); + /* component IO */ unsigned int snd_soc_component_read(struct snd_soc_component *component, unsigned int reg); @@ -441,5 +446,8 @@ void snd_soc_pcm_component_delay(struct snd_pcm_substream *substream, /* REMOVE ME */ #define snd_soc_component_cache_sync snd_soc_component_regmap_cache_sync +#define snd_soc_lookup_component_nolocked snd_soc_component_lookup_nolock +#define snd_soc_lookup_component snd_soc_component_lookup +#define snd_soc_lookup_component_by_name snd_soc_component_lookup_by_name #endif /* __SOC_COMPONENT_H */ diff --git a/include/sound/soc.h b/include/sound/soc.h index 89612ac0c1d9..bc22720b1559 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -457,11 +457,6 @@ int devm_snd_soc_register_component(struct device *dev, #define snd_soc_unregister_component(dev) snd_soc_unregister_component_by_driver(dev, NULL) void snd_soc_unregister_component_by_driver(struct device *dev, const struct snd_soc_component_driver *component_driver); -struct snd_soc_component *snd_soc_lookup_component_nolocked(struct device *dev, - const char *driver_name); -struct snd_soc_component *snd_soc_lookup_component(struct device *dev, - const char *driver_name); -struct snd_soc_component *snd_soc_lookup_component_by_name(const char *component_name); int soc_new_pcm(struct snd_soc_pcm_runtime *rtd); #ifdef CONFIG_SND_SOC_COMPRESS diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 86ede607ba31..4c2d2dc62f86 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -1505,3 +1505,51 @@ int snd_soc_component_probe(struct snd_soc_component *component, struct snd_soc_ return ret; } + +struct snd_soc_component +*snd_soc_component_lookup_nolock(struct device *dev, const char *driver_name) +{ + struct snd_soc_component *component; + + for_each_component(component) { + if (dev != component->dev) + continue; + + if (!driver_name) + return component; + + if (!component->driver->name) + continue; + + if (component->driver->name == driver_name) + return component; + + if (strcmp(component->driver->name, driver_name) == 0) + return component; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_component_lookup_nolock); + +struct snd_soc_component *snd_soc_component_lookup(struct device *dev, + const char *driver_name) +{ + guard(mutex)(&client_mutex); + + return snd_soc_component_lookup_nolock(dev, driver_name); +} +EXPORT_SYMBOL_GPL(snd_soc_component_lookup); + +struct snd_soc_component *snd_soc_component_lookup_by_name(const char *component_name) +{ + struct snd_soc_component *component; + + guard(mutex)(&client_mutex); + for_each_component(component) + if (strstr(component->name, component_name)) + return component; + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_component_lookup_by_name); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 0e935de91d0a..2ead744a5e07 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -261,54 +261,6 @@ struct snd_soc_component *snd_soc_rtdcom_lookup(struct snd_soc_pcm_runtime *rtd, } EXPORT_SYMBOL_GPL(snd_soc_rtdcom_lookup); -struct snd_soc_component -*snd_soc_lookup_component_nolocked(struct device *dev, const char *driver_name) -{ - struct snd_soc_component *component; - - for_each_component(component) { - if (dev != component->dev) - continue; - - if (!driver_name) - return component; - - if (!component->driver->name) - continue; - - if (component->driver->name == driver_name) - return component; - - if (strcmp(component->driver->name, driver_name) == 0) - return component; - } - - return NULL; -} -EXPORT_SYMBOL_GPL(snd_soc_lookup_component_nolocked); - -struct snd_soc_component *snd_soc_lookup_component(struct device *dev, - const char *driver_name) -{ - guard(mutex)(&client_mutex); - - return snd_soc_lookup_component_nolocked(dev, driver_name); -} -EXPORT_SYMBOL_GPL(snd_soc_lookup_component); - -struct snd_soc_component *snd_soc_lookup_component_by_name(const char *component_name) -{ - struct snd_soc_component *component; - - guard(mutex)(&client_mutex); - for_each_component(component) - if (strstr(component->name, component_name)) - return component; - - return NULL; -} -EXPORT_SYMBOL_GPL(snd_soc_lookup_component_by_name); - struct snd_soc_pcm_runtime *snd_soc_get_pcm_runtime(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) From 19224d29ffb4e341400cbfcafe07b19a2af97b72 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Oct 2026 02:59:28 +0000 Subject: [PATCH 816/924] ASoC: soc-component: rename snd_soc_component_async_complete() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_soc_component_async_complete() is regmap related function. Let's add _regmap_ on function name. It renames function name - snd_soc_component_async_complete() + snd_soc_component_regmap_async_complete() Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Å ipraga Link: https://patch.msgid.link/877bjlyokw.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 3 ++- sound/soc/soc-component.c | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index c287ec6f458e..0839f137a77e 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -305,7 +305,6 @@ int snd_soc_component_update_bits(struct snd_soc_component *component, int snd_soc_component_update_bits_async(struct snd_soc_component *component, unsigned int reg, unsigned int mask, unsigned int val); -void snd_soc_component_async_complete(struct snd_soc_component *component); int snd_soc_component_test_bits(struct snd_soc_component *component, unsigned int reg, unsigned int mask, unsigned int value); @@ -336,6 +335,7 @@ int snd_soc_component_set_bias_level(struct snd_soc_component *component, int snd_soc_component_regmap_val_bytes(struct snd_soc_component *component); int snd_soc_component_regmap_cache_sync(struct snd_soc_component *component); +void snd_soc_component_regmap_async_complete(struct snd_soc_component *component); #ifdef CONFIG_REGMAP void snd_soc_component_init_regmap(struct snd_soc_component *component, struct regmap *regmap); @@ -446,6 +446,7 @@ void snd_soc_pcm_component_delay(struct snd_pcm_substream *substream, /* REMOVE ME */ #define snd_soc_component_cache_sync snd_soc_component_regmap_cache_sync +#define snd_soc_component_async_complete snd_soc_component_regmap_async_complete #define snd_soc_lookup_component_nolocked snd_soc_component_lookup_nolock #define snd_soc_lookup_component snd_soc_component_lookup #define snd_soc_lookup_component_by_name snd_soc_component_lookup_by_name diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 4c2d2dc62f86..07d709385582 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -418,6 +418,20 @@ int snd_soc_component_regmap_cache_sync(struct snd_soc_component *component) } EXPORT_SYMBOL_GPL(snd_soc_component_regmap_cache_sync); +/** + * snd_soc_component_regmap_async_complete() - Ensure asynchronous I/O has completed + * @component: Component for which to wait + * + * This function blocks until all asynchronous I/O which has previously been + * scheduled using snd_soc_component_update_bits_async() has completed. + */ +void snd_soc_component_regmap_async_complete(struct snd_soc_component *component) +{ + if (component->regmap) + regmap_async_complete(component->regmap); +} +EXPORT_SYMBOL_GPL(snd_soc_component_regmap_async_complete); + #ifdef CONFIG_REGMAP /** @@ -922,20 +936,6 @@ int snd_soc_component_write_field(struct snd_soc_component *component, } EXPORT_SYMBOL_GPL(snd_soc_component_write_field); -/** - * snd_soc_component_async_complete() - Ensure asynchronous I/O has completed - * @component: Component for which to wait - * - * This function blocks until all asynchronous I/O which has previously been - * scheduled using snd_soc_component_update_bits_async() has completed. - */ -void snd_soc_component_async_complete(struct snd_soc_component *component) -{ - if (component->regmap) - regmap_async_complete(component->regmap); -} -EXPORT_SYMBOL_GPL(snd_soc_component_async_complete); - /** * snd_soc_component_test_bits - Test register for change * @component: component From 8e5a2eabb0d7cbd6db0b94f7f30e1ca2c0a8994f Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:36 +0700 Subject: [PATCH 817/924] ASoC: mediatek: mt8195: Fix tuner clock error handling Check and propagate errors from mt8195_afe_enable_clk(). If enabling the tuner clock fails, disable the previously enabled APLL clock. Fixes: ff5a90173d98 ("ASoC: mediatek: mt8195: enable apll tuner") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-afe-clk.c | 24 +++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-clk.c b/sound/soc/mediatek/mt8195/mt8195-afe-clk.c index 618d8400913a..ec1ea5b988ff 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-clk.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-clk.c @@ -241,21 +241,35 @@ static int mt8195_afe_enable_tuner_clk(struct mtk_base_afe *afe, unsigned int id) { struct mt8195_afe_private *afe_priv = afe->platform_priv; + int ret; switch (id) { case MT8195_AUD_PLL1: - mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL]); - mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL1_TUNER]); + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL]); + if (ret) + return ret; + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL1_TUNER]); + if (ret) { + mt8195_afe_disable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL]); + return ret; + } break; case MT8195_AUD_PLL2: - mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL2]); - mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL2_TUNER]); + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL2]); + if (ret) + return ret; + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL2_TUNER]); + if (ret) { + mt8195_afe_disable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL2]); + return ret; + } break; default: + ret = 0; break; } - return 0; + return ret; } static int mt8195_afe_disable_tuner_clk(struct mtk_base_afe *afe, From ad1617e536092e53c65687e744b1820602e61fd8 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:37 +0700 Subject: [PATCH 818/924] ASoC: mediatek: mt8195: Use dev_err_probe() in mt8195_afe_init_clock() Use dev_err_probe() when obtaining clocks to avoid redundant error messages, particularly for probe deferral. Reviewed-by: Ajay Kumar Nandam Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-afe-clk.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-clk.c b/sound/soc/mediatek/mt8195/mt8195-afe-clk.c index ec1ea5b988ff..12d8159338be 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-clk.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-clk.c @@ -397,12 +397,9 @@ int mt8195_afe_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT8195_CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_dbg(afe->dev, "%s(), devm_clk_get %s fail, ret %ld\n", - __func__, aud_clks[i], - PTR_ERR(afe_priv->clk[i])); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } /* initial tuner */ From c23390ecf5c068976f25d9c8bfa8e298fa12ea3d Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:38 +0700 Subject: [PATCH 819/924] ASoC: mediatek: mt8195: Fix register access clock error handling Check the return value of mt8195_afe_enable_clk() when enabling the register access clocks. If enabling a clock fails, disable the clocks that were already enabled and propagate the error to the caller Fixes: 6746cc858259 ("ASoC: mediatek: mt8195: add platform driver") Reviewed-by: Ajay Kumar Nandam Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-afe-clk.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-clk.c b/sound/soc/mediatek/mt8195/mt8195-afe-clk.c index 12d8159338be..34c828fdea29 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-clk.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-clk.c @@ -599,7 +599,7 @@ static int mt8195_afe_disable_top_cg(struct mtk_base_afe *afe, unsigned int cg_t int mt8195_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) { struct mt8195_afe_private *afe_priv = afe->platform_priv; - int i; + int i, ret; static const unsigned int clk_array[] = { MT8195_CLK_SCP_ADSP_AUDIODSP, /* bus clock for infra */ MT8195_CLK_TOP_AUDIO_H_SEL, /* clock for ADSP bus */ @@ -611,10 +611,19 @@ int mt8195_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) MT8195_CLK_AUD_A1SYS, /* AFE HW clock */ }; - for (i = 0; i < ARRAY_SIZE(clk_array); i++) - mt8195_afe_enable_clk(afe, afe_priv->clk[clk_array[i]]); + for (i = 0; i < ARRAY_SIZE(clk_array); i++) { + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[clk_array[i]]); + if (ret) + goto err_disable_clk; + } return 0; + +err_disable_clk: + while (--i >= 0) + mt8195_afe_disable_clk(afe, afe_priv->clk[clk_array[i]]); + + return ret; } int mt8195_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) From f18c0a8e528eb8844ee362324220ab8df3664afe Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:39 +0700 Subject: [PATCH 820/924] ASoC: mediatek: mt8195: Fix timing system clock error handling Check the return value of mt8195_afe_enable_clk() when enabling the timing system clocks. If enabling a clock fails, disable the clocks that were already enabled and propagate the error to the caller. In mt8195_afe_enable_main_clock(), return the error instead of continuing with AFE clock enable. Fixes: 6746cc858259 ("ASoC: mediatek: mt8195: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-afe-clk.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-clk.c b/sound/soc/mediatek/mt8195/mt8195-afe-clk.c index 34c828fdea29..03d2c4fd1f85 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-clk.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-clk.c @@ -662,7 +662,7 @@ static int mt8195_afe_disable_afe_on(struct mtk_base_afe *afe) static int mt8195_afe_enable_timing_sys(struct mtk_base_afe *afe) { struct mt8195_afe_private *afe_priv = afe->platform_priv; - int i; + int i, ret; static const unsigned int clk_array[] = { MT8195_CLK_AUD_A1SYS, MT8195_CLK_AUD_A2SYS, @@ -673,13 +673,22 @@ static int mt8195_afe_enable_timing_sys(struct mtk_base_afe *afe) MT8195_TOP_CG_26M_TIMING, }; - for (i = 0; i < ARRAY_SIZE(clk_array); i++) - mt8195_afe_enable_clk(afe, afe_priv->clk[clk_array[i]]); + for (i = 0; i < ARRAY_SIZE(clk_array); i++) { + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[clk_array[i]]); + if (ret) + goto err_disable_clk; + } for (i = 0; i < ARRAY_SIZE(cg_array); i++) mt8195_afe_enable_top_cg(afe, cg_array[i]); return 0; + +err_disable_clk: + while (--i >= 0) + mt8195_afe_disable_clk(afe, afe_priv->clk[clk_array[i]]); + + return ret; } static int mt8195_afe_disable_timing_sys(struct mtk_base_afe *afe) From 9f1b48c658ac38be81c04fbc29ed1522f89fb0c9 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:40 +0700 Subject: [PATCH 821/924] ASoC: mediatek: mt8195: Fix main clock error handling Check the return values of clock enable functions and propagate errors to the caller. Disable previously enabled clocks on failure to match the main clock disable sequence. Fixes: 6746cc858259 ("ASoC: mediatek: mt8195: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-afe-clk.c | 23 +++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-clk.c b/sound/soc/mediatek/mt8195/mt8195-afe-clk.c index 03d2c4fd1f85..44cddc2b1564 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-clk.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-clk.c @@ -716,14 +716,31 @@ static int mt8195_afe_disable_timing_sys(struct mtk_base_afe *afe) int mt8195_afe_enable_main_clock(struct mtk_base_afe *afe) { - mt8195_afe_enable_timing_sys(afe); + int ret; + + ret = mt8195_afe_enable_timing_sys(afe); + if (ret) + return ret; mt8195_afe_enable_afe_on(afe); - mt8195_afe_enable_apll_tuner(afe, MT8195_AUD_PLL1); - mt8195_afe_enable_apll_tuner(afe, MT8195_AUD_PLL2); + ret = mt8195_afe_enable_apll_tuner(afe, MT8195_AUD_PLL1); + if (ret) + goto err_disable_afe_on; + + ret = mt8195_afe_enable_apll_tuner(afe, MT8195_AUD_PLL2); + if (ret) + goto err_disable_apll1_tuner; return 0; + +err_disable_apll1_tuner: + mt8195_afe_disable_apll_tuner(afe, MT8195_AUD_PLL1); +err_disable_afe_on: + mt8195_afe_disable_afe_on(afe); + mt8195_afe_disable_timing_sys(afe); + + return ret; } int mt8195_afe_disable_main_clock(struct mtk_base_afe *afe) From c0ec135cf3fd7c7c7cdefd234111e5d4f069a7e6 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:41 +0700 Subject: [PATCH 822/924] ASoC: mediatek: mt8195: Fix paired memif clock error handling Check and propagate errors when preparing and enabling the paired DL8/DL10 memif clocks. If enabling the second clock fails, undo the clock that was already prepared or enabled to keep the clock state balanced. Fixes: 6746cc858259 ("ASoC: mediatek: mt8195: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-afe-pcm.c | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c index 52c3381e6766..1d3e01e2c3e7 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c @@ -286,15 +286,23 @@ mt8195_afe_paired_memif_clk_prepare(struct snd_pcm_substream *substream, struct mt8195_afe_private *afe_priv = afe->platform_priv; int id = snd_soc_rtd_to_cpu(rtd, 0)->id; int clk_id; + int ret; if (id != MT8195_AFE_MEMIF_DL8 && id != MT8195_AFE_MEMIF_DL10) return 0; if (enable) { clk_id = MT8195_CLK_AUD_MEMIF_DL10; - mt8195_afe_prepare_clk(afe, afe_priv->clk[clk_id]); + ret = mt8195_afe_prepare_clk(afe, afe_priv->clk[clk_id]); + if (ret) + return ret; clk_id = MT8195_CLK_AUD_MEMIF_DL8; - mt8195_afe_prepare_clk(afe, afe_priv->clk[clk_id]); + ret = mt8195_afe_prepare_clk(afe, afe_priv->clk[clk_id]); + if (ret) { + clk_id = MT8195_CLK_AUD_MEMIF_DL10; + mt8195_afe_unprepare_clk(afe, afe_priv->clk[clk_id]); + return ret; + } } else { clk_id = MT8195_CLK_AUD_MEMIF_DL8; mt8195_afe_unprepare_clk(afe, afe_priv->clk[clk_id]); @@ -315,6 +323,7 @@ mt8195_afe_paired_memif_clk_enable(struct snd_pcm_substream *substream, struct mt8195_afe_private *afe_priv = afe->platform_priv; int id = snd_soc_rtd_to_cpu(rtd, 0)->id; int clk_id; + int ret; if (id != MT8195_AFE_MEMIF_DL8 && id != MT8195_AFE_MEMIF_DL10) return 0; @@ -322,11 +331,18 @@ mt8195_afe_paired_memif_clk_enable(struct snd_pcm_substream *substream, if (enable) { /* DL8_DL10_MEM */ clk_id = MT8195_CLK_AUD_MEMIF_DL10; - mt8195_afe_enable_clk_atomic(afe, afe_priv->clk[clk_id]); + ret = mt8195_afe_enable_clk_atomic(afe, afe_priv->clk[clk_id]); + if (ret) + return ret; udelay(1); /* DL8_DL10_AGENT */ clk_id = MT8195_CLK_AUD_MEMIF_DL8; - mt8195_afe_enable_clk_atomic(afe, afe_priv->clk[clk_id]); + ret = mt8195_afe_enable_clk_atomic(afe, afe_priv->clk[clk_id]); + if (ret) { + clk_id = MT8195_CLK_AUD_MEMIF_DL10; + mt8195_afe_disable_clk_atomic(afe, afe_priv->clk[clk_id]); + return ret; + } } else { /* DL8_DL10_AGENT */ clk_id = MT8195_CLK_AUD_MEMIF_DL8; From b1db7408c8e0761d17b49b74f8f2d60c0c1929e8 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:42 +0700 Subject: [PATCH 823/924] ASoC: mediatek: mt8195: Fix FE startup error handling Check and propagate errors from paired memif clock preparation and PCM hardware constraints during FE startup. Unprepare the paired memif clocks and shut down the FE when a later startup step fails to avoid leaving resources enabled on error. Fixes: 6746cc858259 ("ASoC: mediatek: mt8195: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-afe-pcm.c | 44 ++++++++++++++-------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c index 1d3e01e2c3e7..02736949ae6a 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c @@ -364,24 +364,38 @@ static int mt8195_afe_fe_startup(struct snd_pcm_substream *substream, int id = snd_soc_rtd_to_cpu(rtd, 0)->id; int ret = 0; - mt8195_afe_paired_memif_clk_prepare(substream, dai, 1); + ret = mt8195_afe_paired_memif_clk_prepare(substream, dai, 1); + if (ret) + return ret; ret = mtk_afe_fe_startup(substream, dai); - - snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_BUFFER_BYTES, - MT8195_MEMIF_BUFFER_BYTES_ALIGN); - - if (id != MT8195_AFE_MEMIF_DL7) - goto out; - - ret = snd_pcm_hw_constraint_minmax(runtime, - SNDRV_PCM_HW_PARAM_PERIOD_SIZE, - 1, - MT8195_MEMIF_DL7_MAX_PERIOD_SIZE); if (ret < 0) - dev_dbg(afe->dev, "hw_constraint_minmax failed\n"); -out: + goto err_clk_unprepare; + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + MT8195_MEMIF_BUFFER_BYTES_ALIGN); + if (ret) + goto err_fe_shutdow; + + if (id == MT8195_AFE_MEMIF_DL7) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + 1, + MT8195_MEMIF_DL7_MAX_PERIOD_SIZE); + if (ret < 0) { + dev_dbg(afe->dev, "hw_constraint_minmax failed\n"); + goto err_fe_shutdow; + } + } + + return 0; + +err_fe_shutdow: + mtk_afe_fe_shutdown(substream, dai); +err_clk_unprepare: + mt8195_afe_paired_memif_clk_prepare(substream, dai, 0); + return ret; } From a2ae972c75a18ecbdcbf331ddaab21e1a08333fb Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:43 +0700 Subject: [PATCH 824/924] ASoC: mediatek: mt8195: Check paired MEMIF clock enable in FE trigger Check the return value of mt8195_afe_paired_memif_clk_enable() on START/RESUME. On failure, stop the MEMIF and disable the channel merge before returning the error. Also skip enabling the paired clock if mtk_afe_fe_trigger() fails. Fixes: 6746cc858259 ("ASoC: mediatek: mt8195: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-9-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-afe-pcm.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c index 02736949ae6a..4216c47a56c9 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c @@ -466,7 +466,14 @@ static int mt8195_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: - mt8195_afe_paired_memif_clk_enable(substream, dai, 1); + if (ret) + break; + + ret = mt8195_afe_paired_memif_clk_enable(substream, dai, 1); + if (ret) { + mtk_afe_fe_trigger(substream, SNDRV_PCM_TRIGGER_STOP, dai); + mt8195_afe_enable_cm(afe, cm, false); + } break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: From 933fbfb6648850e003a1fc47b7bcc08fe3687961 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:44 +0700 Subject: [PATCH 825/924] ASoC: mediatek: mt8195: Fix runtime resume error handling Check and propagate errors when enabling the register access and main clocks during runtime resume. If regcache_sync() or main clock enable fails, restore regmap cache-only mode and disable the register access clocks before returning the error. Fixes: 6746cc858259 ("ASoC: mediatek: mt8195: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-10-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-afe-pcm.c | 23 +++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c index 4216c47a56c9..19047887ee21 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c @@ -2965,18 +2965,31 @@ static int mt8195_afe_runtime_resume(struct device *dev) { struct mtk_base_afe *afe = dev_get_drvdata(dev); struct mt8195_afe_private *afe_priv = afe->platform_priv; + int ret; - mt8195_afe_enable_reg_rw_clk(afe); + ret = mt8195_afe_enable_reg_rw_clk(afe); + if (ret) + return ret; if (!afe->regmap || afe_priv->pm_runtime_bypass_reg_ctl) - goto skip_regmap; + return 0; regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) + goto err_restore_cache; + + ret = mt8195_afe_enable_main_clock(afe); + if (ret) + goto err_restore_cache; - mt8195_afe_enable_main_clock(afe); -skip_regmap: return 0; + +err_restore_cache: + regcache_cache_only(afe->regmap, true); + mt8195_afe_disable_reg_rw_clk(afe); + + return ret; } static int init_memif_priv_data(struct mtk_base_afe *afe) From 9f9ba582ac82b7b1584308a1978b3e2e890080fc Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:45 +0700 Subject: [PATCH 826/924] ASoC: mediatek: mt8195: Remove redundant error message The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_warn() and dev_err_probe() call is redundant and can be removed. Reviewed-by: Ajay Kumar Nandam Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-11-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-afe-pcm.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c index 19047887ee21..75d4d0578bcc 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c @@ -3090,7 +3090,7 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt8195_afe_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "init clock error\n"); + return ret; /* reset controller to reset audio regs before regmap cache */ rstc = devm_reset_control_get_exclusive(dev, "audiosys"); @@ -3137,7 +3137,7 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt8195_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for asys-isr\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); @@ -3195,10 +3195,9 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) /* register component */ ret = devm_snd_soc_register_component(dev, &mtk_afe_pcm_platform, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_warn(dev, "err_platform\n"); + if (ret) goto err_pm_put; - } + ret = regmap_multi_reg_write(afe->regmap, mt8195_afe_reg_defaults, ARRAY_SIZE(mt8195_afe_reg_defaults)); From 9d5dedb285fb61a40e04f337d9e06fc59c065857 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:46 +0700 Subject: [PATCH 827/924] ASoC: mediatek: mt8195: Propagate IRQ lookup errors Propagate the error returned by platform_get_irq() instead of replacing it with -ENXIO. This preserves errors such as -EPROBE_DEFER for proper handling by the caller. Fixes: 6746cc858259 ("ASoC: mediatek: mt8195: add platform driver") Reviewed-by: Ajay Kumar Nandam Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-12-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-afe-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c index 75d4d0578bcc..13e5bc02fac5 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c @@ -3132,7 +3132,7 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); if (irq_id < 0) - return -ENXIO; + return irq_id; ret = devm_request_irq(dev, irq_id, mt8195_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); From 91dce9c7eec25e95b5ca32e17e60e2c27333d411 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:47 +0700 Subject: [PATCH 828/924] ASoC: mediatek: mt8195: Fix ETDM MCLK error handling Propagate errors from mt8195_afe_enable_clk() when enabling the ETDM MCLK instead of always returning success. This allows callers to properly handle failures when enabling the MCLK. Fixes: 1de9a54acafb ("ASoC: mediatek: mt8195: support etdm in platform driver") Reviewed-by: Ajay Kumar Nandam Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-13-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-dai-etdm.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c b/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c index 1a20adb2cbf5..fba51c552eec 100644 --- a/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c +++ b/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c @@ -1549,9 +1549,7 @@ static int mtk_dai_etdm_enable_mclk(struct mtk_base_afe *afe, int dai_id) if (clkdiv_id < 0) return -EINVAL; - mt8195_afe_enable_clk(afe, afe_priv->clk[clkdiv_id]); - - return 0; + return mt8195_afe_enable_clk(afe, afe_priv->clk[clkdiv_id]); } static int mtk_dai_etdm_disable_mclk(struct mtk_base_afe *afe, int dai_id) From 8e67c67f3fd2505b8b7a9dd38d8dabe47273490d Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:48 +0700 Subject: [PATCH 829/924] ASoC: mediatek: mt8195: Fix ETDM startup error handling Check and propagate errors when enabling the ETDM MCLK and clock gates during startup. If enabling a clock fails, disable the clocks that were already enabled to keep the clock state balanced. Fixes: 1de9a54acafb ("ASoC: mediatek: mt8195: support etdm in platform driver") Reviewed-by: Ajay Kumar Nandam Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-14-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-dai-etdm.c | 50 ++++++++++++++++----- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c b/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c index fba51c552eec..1270fcac6b3f 100644 --- a/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c +++ b/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c @@ -1575,36 +1575,66 @@ static int mtk_dai_etdm_startup(struct snd_pcm_substream *substream, int cg_id; int mst_dai_id; int slv_dai_id; - int i; + int i, ret; if (is_cowork_mode(dai)) { mst_dai_id = get_etdm_cowork_master_id(dai); if (!mt8195_afe_etdm_is_valid(mst_dai_id)) return -EINVAL; - mtk_dai_etdm_enable_mclk(afe, mst_dai_id); + ret = mtk_dai_etdm_enable_mclk(afe, mst_dai_id); + if (ret) + return ret; cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(mst_dai_id); - if (cg_id >= 0) - mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]); + if (cg_id >= 0) { + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]); + if (ret) + goto err_disable_mclk; + } mst_etdm_data = afe_priv->dai_priv[mst_dai_id]; for (i = 0; i < mst_etdm_data->cowork_slv_count; i++) { slv_dai_id = mst_etdm_data->cowork_slv_id[i]; cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(slv_dai_id); - if (cg_id >= 0) - mt8195_afe_enable_clk(afe, - afe_priv->clk[cg_id]); + if (cg_id >= 0) { + ret = mt8195_afe_enable_clk(afe, + afe_priv->clk[cg_id]); + if (ret) + goto err_disable_slv_clk; + } } } else { - mtk_dai_etdm_enable_mclk(afe, dai->id); + ret = mtk_dai_etdm_enable_mclk(afe, dai->id); + if (ret) + return ret; cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(dai->id); - if (cg_id >= 0) - mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]); + if (cg_id >= 0) { + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]); + if (ret) + goto err_disable_mclk; + } } return 0; + +err_disable_slv_clk: + while (--i >= 0) { + slv_dai_id = mst_etdm_data->cowork_slv_id[i]; + cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(slv_dai_id); + if (cg_id >= 0) + mt8195_afe_disable_clk(afe, afe_priv->clk[cg_id]); + } + + cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(mst_dai_id); + if (cg_id >= 0) + mt8195_afe_disable_clk(afe, afe_priv->clk[cg_id]); + +err_disable_mclk: + mtk_dai_etdm_disable_mclk(afe, is_cowork_mode(dai) ? mst_dai_id : dai->id); + + return ret; } static void mtk_dai_etdm_shutdown(struct snd_pcm_substream *substream, From 1d6f1785d90334b66809a57ebfc40a5b1cfa80ec Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:49 +0700 Subject: [PATCH 830/924] ASoC: mediatek: mt8195: Fix HDMI TX startup error handling Check and propagate errors when enabling the clock gate and MCLK during HDMI TX/DP TX startup. If MCLK enabling fails, disable the clock gate that was already enabled before returning the error. Fixes: 1de9a54acafb ("ASoC: mediatek: mt8195: support etdm in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-15-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-dai-etdm.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c b/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c index 1270fcac6b3f..11317cdd14ab 100644 --- a/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c +++ b/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c @@ -2321,13 +2321,25 @@ static int mtk_dai_hdmitx_dptx_startup(struct snd_pcm_substream *substream, struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); struct mt8195_afe_private *afe_priv = afe->platform_priv; int cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(dai->id); + int ret; - if (cg_id >= 0) - mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]); + if (cg_id >= 0) { + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]); + if (ret) + return ret; + } - mtk_dai_etdm_enable_mclk(afe, dai->id); + ret = mtk_dai_etdm_enable_mclk(afe, dai->id); + if (ret) + goto err_disable_cg_clk; return 0; + +err_disable_cg_clk: + if (cg_id >= 0) + mt8195_afe_disable_clk(afe, afe_priv->clk[cg_id]); + + return ret; } static void mtk_dai_hdmitx_dptx_shutdown(struct snd_pcm_substream *substream, From ca3ac9a7eec1884bfff2d2dd92caf30f86a9eea7 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:50 +0700 Subject: [PATCH 831/924] ASoC: mediatek: mt8195: Fix ETDM probe error handling Use pm_runtime_resume_and_get() to properly handle runtime PM errors during ETDM probe. Propagate errors from MCLK configuration and enabling instead of ignoring them. Fixes: 1de9a54acafb ("ASoC: mediatek: mt8195: support etdm in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-16-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-dai-etdm.c | 27 ++++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c b/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c index 11317cdd14ab..8fa830a57502 100644 --- a/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c +++ b/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c @@ -2503,6 +2503,7 @@ static int mtk_dai_etdm_probe(struct snd_soc_dai *dai) struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); struct mt8195_afe_private *afe_priv = afe->platform_priv; struct mtk_dai_etdm_priv *etdm_data; + int ret; dev_dbg(dai->dev, "%s id %d\n", __func__, dai->id); @@ -2510,15 +2511,23 @@ static int mtk_dai_etdm_probe(struct snd_soc_dai *dai) return -EINVAL; etdm_data = afe_priv->dai_priv[dai->id]; - if (etdm_data->mclk_freq) { - dev_dbg(afe->dev, "MCLK always on, rate %d\n", - etdm_data->mclk_freq); - pm_runtime_get_sync(afe->dev); - mtk_dai_etdm_mclk_configure(afe, dai->id); - mtk_dai_etdm_enable_mclk(afe, dai->id); - pm_runtime_put_sync(afe->dev); - } - return 0; + if (!etdm_data->mclk_freq) + return 0; + + dev_dbg(afe->dev, "MCLK always on, rate %d\n", + etdm_data->mclk_freq); + ret = pm_runtime_resume_and_get(afe->dev); + if (ret) + return ret; + ret = mtk_dai_etdm_mclk_configure(afe, dai->id); + if (ret) + goto err_put_pm; + ret = mtk_dai_etdm_enable_mclk(afe, dai->id); + +err_put_pm: + pm_runtime_put_sync(afe->dev); + + return ret; } static const struct snd_soc_dai_ops mtk_dai_hdmitx_dptx_ops = { From a4276a80c62e773829925b24c5269b47ca54b131 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 14 Oct 2026 18:27:51 +0700 Subject: [PATCH 832/924] ASoC: mediatek: mt8195: Fix clock parent error handling mtk_audio_hires_event() ignores the return value of mt8195_afe_set_clk_parent(), so a failure to set the clock parent goes unnoticed. Return the error so that the DAPM core reports it. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261014112752.683032-17-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8195/mt8195-dai-adda.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/mediatek/mt8195/mt8195-dai-adda.c b/sound/soc/mediatek/mt8195/mt8195-dai-adda.c index 94abde15ea09..8a58a142064d 100644 --- a/sound/soc/mediatek/mt8195/mt8195-dai-adda.c +++ b/sound/soc/mediatek/mt8195/mt8195-dai-adda.c @@ -262,9 +262,8 @@ static int mtk_audio_hires_event(struct snd_soc_dapm_widget *w, default: return 0; } - mt8195_afe_set_clk_parent(afe, clk, clk_parent); - return 0; + return mt8195_afe_set_clk_parent(afe, clk, clk_parent); } static struct mtk_dai_adda_priv *get_adda_priv_by_name(struct mtk_base_afe *afe, From a46d9d8ed0e85e348075ab84bddd2388c28c5057 Mon Sep 17 00:00:00 2001 From: Jurica Vukadin Date: Thu, 15 Oct 2026 22:18:20 +0200 Subject: [PATCH 833/924] ALSA: usb-audio: Add mic volume quirk for Logitech Brio 100 Like several other Logitech webcams, Brio 100 reports a 6144 step volume resolution: usb 5-3: new high-speed USB device number 4 using xhci_hcd usb 5-3: New USB device found, idVendor=046d, idProduct=094c, bcdDevice=99.14 usb 5-3: New USB device strings: Mfr=0, Product=2, SerialNumber=1 usb 5-3: Product: Brio 100 usb 5-3: SerialNumber: 2349LZ51Y668 usb 5-3: Warning! Unlikely big volume step count (=6144), linear volume or wrong cval->res? usb 5-3: [5] FU [Mic Capture Volume] ch = 1, val = 1536/7680/1 Signed-off-by: Jurica Vukadin Link: https://patch.msgid.link/20261015201820.10278-1-jura@vukad.in Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 8393a0af7b40..6db571de25fa 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2526,6 +2526,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x046d, 0x08ca, /* Logitech Quickcam Fusion */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x094c, /* Logitech Brio 100 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), DEVICE_FLG(0x046d, 0x0991, /* Logitech QuickCam Pro */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_IGNORE_CTL_ERROR | QUIRK_FLAG_MIC_RES_384), From e05347e54ae3dd0f44cf332d7cb4e29e027e170c Mon Sep 17 00:00:00 2001 From: Imtiaz Chowdhury Date: Thu, 15 Oct 2026 19:22:10 -0400 Subject: [PATCH 834/924] ALSA: hda/realtek: Add HP ENVY Laptop 13-ba1xxx quirk On the HP ENVY Laptop 13-ba1xxx, the F5 and F8 key's LED were not working at all. Add a quirk for subsystem ID 0x8820 so it uses the existing ALC245_FIXUP_HP_X360_MUTE_LEDS fixup, as done for the 13-ba0xxx. Tested on HP ENVY Laptop 13-ba1063cl: F5 and F8 LED lights work as intended. Checked the audio systems both with headphones and no headphones, and there were no issues. Signed-off-by: Imtiaz Chowdhury Link: https://patch.msgid.link/20261015232210.35946-1-imtiazc56@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 76a7ed218012..799a5a7a8ed0 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7696,6 +7696,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8812, "HP Spectre x360 15-eb1xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1), SND_PCI_QUIRK(0x103c, 0x881d, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x881e, "HP Laptop 15s-du3xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8820, "HP ENVY Laptop 13-ba1xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8846, "HP EliteBook 850 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8847, "HP EliteBook x360 830 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x884b, "HP EliteBook 840 Aero G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), From 1f21537b11863e74d752cab3edbb78cc6efe92fc Mon Sep 17 00:00:00 2001 From: Joonho Lee Date: Fri, 16 Oct 2026 08:48:09 +0900 Subject: [PATCH 835/924] ALSA: pcm: allow capture reads while draining Capture drain can leave buffered samples in DRAINING, but the transfer state check rejects reads in that state. Allow capture reads to consume the remaining frames while preserving the playback state check. When drain wakes a blocked capture reader, return the actual available frame count from wait_for_avail(). A zero count would make the transfer loop discard a nonempty tail. Finish capture EOF with snd_pcm_drain_done() so the state transition applies to that substream and leaves a linked playback stream draining its queued data. Allow capture hwsync to use the cached final pointer in DRAINING. Capture has already stopped, so DELAY and SYNC_PTR(HWSYNC) can report its remaining frames without another driver pointer callback. Playback retains its existing pointer update. Synthetic atomic and nonatomic tests reproduce dropped tail frames, linked playback stopping at capture EOF, and rejected position queries. With this change, a blocked read gets all 64 tail frames, playback stays DRAINING at capture EOF, and the queries succeed with no extra callback. Assisted-by: LLM Signed-off-by: Joonho Lee Link: https://patch.msgid.link/v5-core-capture-drain-1.e9b40d3bc08c4162@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_lib.c | 16 ++++++++++------ sound/core/pcm_native.c | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 4d665b4148d7..e8f6a9da4a4c 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -2009,8 +2009,8 @@ static int wait_for_avail(struct snd_pcm_substream *substream, case SNDRV_PCM_STATE_DRAINING: if (is_playback) err = -EPIPE; - else - avail = 0; /* indicate draining */ + else + avail = snd_pcm_avail(substream); goto _endloop; case SNDRV_PCM_STATE_OPEN: case SNDRV_PCM_STATE_SETUP: @@ -2208,13 +2208,17 @@ static int pcm_sanity_check(struct snd_pcm_substream *substream) return 0; } -static int pcm_accessible_state(struct snd_pcm_runtime *runtime) +static int pcm_accessible_state(struct snd_pcm_substream *substream) { + struct snd_pcm_runtime *runtime = substream->runtime; + switch (runtime->state) { case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_PAUSED: return 0; + case SNDRV_PCM_STATE_DRAINING: + return substream->stream == SNDRV_PCM_STREAM_CAPTURE ? 0 : -EBADFD; case SNDRV_PCM_STATE_XRUN: return -EPIPE; case SNDRV_PCM_STATE_SUSPENDED: @@ -2320,7 +2324,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, nonblock = !!(substream->f_flags & O_NONBLOCK); snd_pcm_stream_lock_irq(substream); - err = pcm_accessible_state(runtime); + err = pcm_accessible_state(substream); if (err < 0) goto _end_unlock; @@ -2348,7 +2352,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, if (!avail) { if (!is_playback && runtime->state == SNDRV_PCM_STATE_DRAINING) { - snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + snd_pcm_drain_done(substream); goto _end_unlock; } if (nonblock) { @@ -2388,7 +2392,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, atomic_dec(&runtime->buffer_accessing); if (err < 0) goto _end_unlock; - err = pcm_accessible_state(runtime); + err = pcm_accessible_state(substream); if (err < 0) goto _end_unlock; appl_ptr += frames; diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index f2e378696c7e..914283200fef 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -3030,7 +3030,7 @@ static int do_pcm_hwsync(struct snd_pcm_substream *substream) switch (substream->runtime->state) { case SNDRV_PCM_STATE_DRAINING: if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) - return -EBADFD; + return 0; fallthrough; case SNDRV_PCM_STATE_RUNNING: return snd_pcm_update_hw_ptr(substream); From d9f52113f263498f000cecce5c4a82b7d2abf9d2 Mon Sep 17 00:00:00 2001 From: Joonho Lee Date: Fri, 16 Oct 2026 08:48:17 +0900 Subject: [PATCH 836/924] ALSA: pcm: refresh linked pointers before draining Capture hardware can produce samples after the last PCM pointer update. Drain must refresh those pointers before stopping capture, or its final availability check can miss the remaining frames. With linked hardware, the first STOP may freeze other members, so refresh all running members before entering the drain action. Factor the traversal into snd_pcm_update_linked_hw_ptr(). Hold snd_pcm_link_rwsem for reading to keep membership stable during that traversal, and take each member's stream lock for its ordinary snd_pcm_update_hw_ptr() call. Include running playback streams and invoke the helper after pause resume so paused capture also receives its final update. Keep pause and drain-init outside the helper's rwsem scope. Updating a pointer inside the group drain action can enter the XRUN stop path and recursively acquire the group lock. The helper runs before that action, so an update error follows the existing group XRUN path and is returned to the caller before drain-init. Suggested-by: Takashi Iwai Link: https://lore.kernel.org/all/87jynkcn60.wl-tiwai@suse.de/ Assisted-by: LLM Signed-off-by: Joonho Lee Link: https://patch.msgid.link/v5-core-capture-drain-2.e9b40d3bc08c4162@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 914283200fef..35152fae74ca 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2102,6 +2102,25 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream, (snd_pcm_state_t)f_flags); } +static int snd_pcm_update_linked_hw_ptr(struct snd_pcm_substream *substream) +{ + struct snd_pcm_substream *s; + + /* Keep group membership stable while locking each stream separately. */ + guard(rwsem_read)(&snd_pcm_link_rwsem); + snd_pcm_group_for_each_entry(s, substream) { + int err; + + guard(pcm_stream_lock_irq)(s); + if (!snd_pcm_running(s)) + continue; + err = snd_pcm_update_hw_ptr(s); + if (err < 0) + return err; + } + return 0; +} + /* * drain ioctl */ @@ -2211,7 +2230,13 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, /* resume pause */ if (runtime->state == SNDRV_PCM_STATE_PAUSED) snd_pcm_pause(substream, false); + snd_pcm_stream_unlock_irq(substream); + result = snd_pcm_update_linked_hw_ptr(substream); + if (result < 0) + return result; + + snd_pcm_stream_lock_irq(substream); /* pre-start/stop - all running streams are changed to DRAINING state */ result = snd_pcm_action(&snd_pcm_action_drain_init, substream, ACTION_ARG_IGNORE); From db2db2dec96e24d067cc5692ac5eec35830ee8d9 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Fri, 16 Oct 2026 10:02:29 +0800 Subject: [PATCH 837/924] ALSA: vmaster: Protect follower lookup and setup A control selected by snd_ctl_add_followers() must remain alive until _snd_ctl_add_follower() has copied it and installed its wrapper. The lookup returns a raw pointer without retaining the control, and neither the short-lived list lookup lock nor the fast lookup protects later use. If a kernel client adds followers to a live virtual master while another client removes or replaces a published follower, the following order is possible, with the card and master kept alive: Follower setup Control removal snd_ctl_add_followers() snd_ctl_find_id_mixer() return follower snd_ctl_remove() down_write(controls_rwsem) __snd_ctl_remove() unlink follower snd_ctl_free_one(follower) up_write(controls_rwsem) _snd_ctl_add_follower() read follower->count Removal completes after lookup returns and before setup reads the control. Since setup holds neither controls_rwsem nor a reference, the count read and subsequent control copies can access freed memory. Hold controls_rwsem for writing across the lookup and follower setup. This prevents removal or replacement from freeing the selected control until wrapper installation is complete. Write ownership also excludes control callbacks while setup changes the callback and private fields. KASAN report as below: BUG: KASAN: slab-use-after-free in _snd_ctl_add_follower+0x452/0x520 Read of size 4 at addr ffff888107957d50 by task loadmodule/491 CPU: 3 UID: 0 PID: 491 Comm: loadmodule Tainted: G O 7.2.0-rc6-pmb-bt-functional-v1+ #1 PREEMPT(lazy) Tainted: [O]=OOT_MODULE Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 ? _snd_ctl_add_follower+0x452/0x520 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x20d/0x410 ? _snd_ctl_add_follower+0x452/0x520 kasan_report+0xe0/0x110 ? _snd_ctl_add_follower+0x452/0x520 _snd_ctl_add_follower+0x452/0x520 snd_ctl_add_followers+0x153/0x2b0 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_snd_ctl_add_followers+0x10/0x10 ? __might_fault+0x135/0x190 ? srso_alias_return_thunk+0x5/0xfbef5 ? __might_fault+0x14e/0x190 ? __pfx_fixture_write+0x10/0x10 [pmbd_vmaster_fixture] fixture_write+0x174/0x1f0 [pmbd_vmaster_fixture] ? __pfx_fixture_write+0x10/0x10 [pmbd_vmaster_fixture] ? __pfx___might_resched+0x10/0x10 proc_reg_write+0x1fb/0x2d0 vfs_write+0x25a/0x1010 ? __pfx_proc_reg_write+0x10/0x10 ? kmem_cache_free+0x242/0x580 ? __pfx_vfs_write+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? putname+0x93/0xe0 ? srso_alias_return_thunk+0x5/0xfbef5 ? do_sys_openat2+0x12a/0x1b0 ? __pfx_do_sys_openat2+0x10/0x10 ksys_write+0x111/0x200 ? __pfx_ksys_write+0x10/0x10 ? irqentry_exit+0xbe/0x820 ? srso_alias_return_thunk+0x5/0xfbef5 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f0b835a9687 Code: 48 89 fa 4c 89 df e8 58 b3 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff RSP: 002b:00007ffc82ea0360 EFLAGS: 00000202 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 00007f0b83517780 RCX: 00007f0b835a9687 RDX: 0000000000000005 RSI: 0000564f8a21d074 RDI: 0000000000000003 RBP: 0000000000000003 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000000 R13: 00007ffc82ea04f0 R14: 00007f0b83755000 R15: 0000564f8a21ed78 Allocated by task 485: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_noprof+0x2d7/0x770 snd_ctl_new+0x51/0x190 snd_ctl_new1+0x13c/0x650 0xffffffffc04011b5 do_one_initcall+0xf3/0x4e0 do_init_module+0x281/0x8e0 load_module+0x658c/0x7cc0 init_module_from_file+0x15b/0x180 idempotent_init_module+0x22d/0x770 __x64_sys_finit_module+0xd2/0x160 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 492: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x307/0x580 __snd_ctl_remove+0x2ac/0x3d0 snd_ctl_remove+0x46/0x70 fixture_write+0x1ae/0x1f0 [pmbd_vmaster_fixture] proc_reg_write+0x1fb/0x2d0 vfs_write+0x25a/0x1010 ksys_write+0x111/0x200 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff888107957d00 which belongs to the cache kmalloc-192 of size 192 The buggy address is located 80 bytes inside of freed 192-byte region [ffff888107957d00, ffff888107957dc0) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x107957 flags: 0x200000000000000(node=0|zone=2) page_type: f5(slab) raw: 0200000000000000 ffff8881000423c0 dead000000000100 dead000000000122 raw: 0000000000000000 0000000000100010 00000000f5000000 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff888107957c00: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ffff888107957c80: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc >ffff888107957d00: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ^ ffff888107957d80: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ffff888107957e00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ================================================================== Fixes: ae07eb9bf23e ("ALSA: vmaster: Add snd_ctl_add_followers() helper") Assisted-by: LLM Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-6c0b7e841529ddd0b21a-v2-b53dbcd593a7a4bd9582@gmail.com Signed-off-by: Takashi Iwai --- sound/core/vmaster.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c index 3d57182031d9..f43144ba2797 100644 --- a/sound/core/vmaster.c +++ b/sound/core/vmaster.c @@ -287,6 +287,8 @@ EXPORT_SYMBOL(_snd_ctl_add_follower); * * Adds the multiple follower kcontrols with the given names. * Returns 0 for success or a negative error code. + * + * Note that this function takes card->controls_rwsem lock internally. */ int snd_ctl_add_followers(struct snd_card *card, struct snd_kcontrol *master, const char * const *list) @@ -294,6 +296,7 @@ int snd_ctl_add_followers(struct snd_card *card, struct snd_kcontrol *master, struct snd_kcontrol *follower; int err; + guard(rwsem_write)(&card->controls_rwsem); for (; *list; list++) { follower = snd_ctl_find_id_mixer(card, *list); if (follower) { From 5618f6837ef3314e2c581d4c05ec13b7ed975cb4 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Fri, 16 Oct 2026 10:17:58 +0800 Subject: [PATCH 838/924] ALSA: seq: oss: Ignore sync echoes without a write queue The sync-echo callback needs a write queue before it can wake up a synchronizing writer. However, snd_seq_oss_open() publishes the session's port with snd_seq_oss_event_input() as its callback before allocating and assigning dp->writeq. A kernel caller using snd_seq_kernel_client_dispatch() with the OSS client ID can deliver an ECHO containing SEQ_SYNCTIMER to this port before the write queue is assigned. The helper stamps the supplied client ID into the event, so the callback's source-client check accepts it. Regular userspace events carry their own client ID and are rejected. The failing ordering is: OSS open Kernel direct dispatch -------- ---------------------- Zero-allocate dp Set dp->cseq Publish callback port Dispatch ECHO as the OSS client Look up and pin that port Enter snd_seq_oss_event_input() Pass NULL to writeq_wakeup() Allocate and assign dp->writeq Dispatch reaches the callback after port publication and before the queue assignment. OSS register_mutex serializes open and release, but direct dispatch does not take it. The port use reference keeps dp alive through the callback; it does not wait for constructor initialization. The wakeup helper therefore attempts to acquire sync_lock through a NULL queue pointer, causing a fault. Sample dp->writeq once in the SEQ_SYNCTIMER branch and ignore the echo when the queue is absent, matching the adjacent read-queue check. Pass the checked pointer to the wakeup helper so a NULL sample cannot reach the lock access. The existing port reference protects the queue's lifetime through this call, since port deletion drains callbacks before free_devinfo() frees the queue. KASAN report as below: Oops: general protection fault, probably for non-canonical address 0xdffffc000000000d: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000068-0x000000000000006f] CPU: 2 UID: 0 PID: 69 Comm: kworker/2:1 Tainted: G W 7.2.0-rc6-pmb-bt-functional-v1+ #1 PREEMPT(lazy) Tainted: [W]=WARN Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: events pmbd_dispatch_early_sync_echo RIP: 0010:do_raw_spin_lock+0x71/0x270 Code: d0 60 81 49 8d 14 04 c7 02 f1 f1 f1 f1 c7 42 04 04 f3 f3 f3 65 48 8b 15 05 df 88 09 48 89 54 24 60 31 d2 48 89 fa 48 c1 ea 03 <0f> b6 14 02 48 89 f8 83 e0 07 83 c0 03 38 d0 7c 08 84 d2 0f 85 b9 RSP: 0018:ffff88810450f878 EFLAGS: 00010013 RAX: dffffc0000000000 RBX: 0000000000000068 RCX: 0000000000000000 RDX: 000000000000000d RSI: 0000000000000000 RDI: 000000000000006c RBP: 0000000000000293 R08: 0000000000000001 R09: 0000000000000000 R10: ffffffff8940fe57 R11: 0000000000000000 R12: 1ffff110208a1f10 R13: 0000000000000068 R14: 0000000000000001 R15: ffff88810450fbee FS: 0000000000000000(0000) GS:ffff8881fd885000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00000000006d2890 CR3: 0000000106cbf002 CR4: 0000000000770ef0 PKRU: 55555554 Call Trace: ? pmbd_probe_hit_cookie+0xee/0x1c0 ? __pfx_do_raw_spin_lock+0x10/0x10 ? lock_acquire+0x2b8/0x2f0 ? __pfx_pmbd_probe_hit_cookie+0x10/0x10 _raw_spin_lock_irqsave+0x44/0x60 ? snd_seq_oss_writeq_wakeup+0x30/0xf0 snd_seq_oss_writeq_wakeup+0x30/0xf0 snd_seq_oss_event_input+0x1e4/0x2a0 ? do_raw_read_unlock+0x49/0xe0 ? __pfx_snd_seq_oss_event_input+0x10/0x10 __snd_seq_deliver_single_event+0x105/0x1a0 ? snd_seq_port_use_ptr+0x111/0x170 snd_seq_deliver_single_event+0x2fc/0x770 ? __pfx_snd_seq_deliver_single_event+0x10/0x10 ? __pfx_pmbd_hold_maybe+0x10/0x10 snd_seq_deliver_event+0x45a/0x760 ? __pfx_do_raw_spin_lock+0x10/0x10 ? lock_acquire+0x2b8/0x2f0 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_snd_seq_deliver_event+0x10/0x10 ? _raw_spin_unlock_irqrestore+0x44/0x60 ? srso_alias_return_thunk+0x5/0xfbef5 ? trace_hardirqs_on+0x18/0x160 ? srso_alias_return_thunk+0x5/0xfbef5 ? _raw_spin_unlock_irqrestore+0x44/0x60 ? srso_alias_return_thunk+0x5/0xfbef5 snd_seq_kernel_client_dispatch+0x19c/0x1e0 pmbd_dispatch_early_sync_echo+0x121/0x1c0 ? __pfx_pmbd_dispatch_early_sync_echo+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? finish_task_switch.isra.0+0x1dc/0x980 process_one_work+0x908/0x19c0 ? __pfx_process_one_work+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 worker_thread+0x65c/0xe40 ? srso_alias_return_thunk+0x5/0xfbef5 ? __kthread_parkme+0x175/0x220 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_worker_thread+0x10/0x10 kthread+0x34f/0x460 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x659/0x940 ? __pfx_ret_from_fork+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __switch_to+0x74f/0xf80 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Modules linked in: ---[ end trace 0000000000000000 ]--- RIP: 0010:do_raw_spin_lock+0x71/0x270 Code: d0 60 81 49 8d 14 04 c7 02 f1 f1 f1 f1 c7 42 04 04 f3 f3 f3 65 48 8b 15 05 df 88 09 48 89 54 24 60 31 d2 48 89 fa 48 c1 ea 03 <0f> b6 14 02 48 89 f8 83 e0 07 83 c0 03 38 d0 7c 08 84 d2 0f 85 b9 RSP: 0018:ffff88810450f878 EFLAGS: 00010013 RAX: dffffc0000000000 RBX: 0000000000000068 RCX: 0000000000000000 RDX: 000000000000000d RSI: 0000000000000000 RDI: 000000000000006c RBP: 0000000000000293 R08: 0000000000000001 R09: 0000000000000000 R10: ffffffff8940fe57 R11: 0000000000000000 R12: 1ffff110208a1f10 R13: 0000000000000068 R14: 0000000000000001 R15: ffff88810450fbee FS: 0000000000000000(0000) GS:ffff8881fd885000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00000000006d2890 CR3: 0000000106cbf002 CR4: 0000000000770ef0 PKRU: 55555554 Kernel panic - not syncing: Fatal exception Kernel Offset: disabled Rebooting in 1 seconds.. Assisted-by: LLM Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-3634251be738461947da-v2-64371e8c8d6cbf6956a3@gmail.com Signed-off-by: Takashi Iwai --- sound/core/seq/oss/seq_oss_event.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/core/seq/oss/seq_oss_event.c b/sound/core/seq/oss/seq_oss_event.c index 122735862044..3c2cfaea94d1 100644 --- a/sound/core/seq/oss/seq_oss_event.c +++ b/sound/core/seq/oss/seq_oss_event.c @@ -433,8 +433,12 @@ snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data rec = (union evrec*)&ev->data; if (rec->s.code == SEQ_SYNCTIMER) { + struct seq_oss_writeq *writeq = READ_ONCE(dp->writeq); + + if (!writeq) + return 0; /* sync echo back */ - snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time); + snd_seq_oss_writeq_wakeup(writeq, rec->t.time); } else { /* echo back event */ From 38cb2ef26b6411277826da54c6bcf94523541058 Mon Sep 17 00:00:00 2001 From: Art O Cathain Date: Fri, 16 Oct 2026 11:29:30 +0200 Subject: [PATCH 839/924] ASoC: amd: acp-config: force SoundWire probe on Dell 14S DS14265 The BIOS on the Dell 14S DS14265 (ACP PCI revision 0x72) reports acp-audio-config-flag = FLAG_AMD_LEGACY_ONLY_DMIC. As a result snd_pci_ps returns early and the SoundWire links are never scanned, while the legacy snd_acp_pci driver probes the device, finds no matching machine driver ("No matching ASoC machine driver found") and no internal sound card is created. The CS42L45 headset codec on link 0 and the two CS35L63 speaker amplifiers on link 1 never enumerate. Add a DMI entry for the model to the ACP70 ACPI flag override table so the firmware-provided flag is overridden and snd_pci_ps probes instead. Match on the product name rather than the board name, as the same symptom has been reported on several boards of this model (09PVND, 0M0J65, 0C7F12). Tested on a DS14265 (board 09PVND, BIOS 1.5.3) with Debian's 7.2.6 kernel, with and without this entry. With it, snd_pci_ps binds, both SoundWire links come up, the CS42L45 and both CS35L63 enumerate, the existing CS42L45 + 2x CS35L63 machine entry matches, the amplifiers load their firmware and calibration, and the amd-soundwire card plays through both the internal speakers and the headphone jack. Also build-tested on asoc-next with W=1. Developed with AI assistance. Claude read the ACP driver source, identified the BIOS flag as the cause and the override table as the fix, wrote the DMI entry and the out-of-tree test module, and drafted this changelog. All hardware testing was done by the submitter, who has reviewed the change, understands it and takes responsibility for it. Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Art O Cathain Reviewed-by: Vijendar Mukunda Link: https://patch.msgid.link/20261016-dell-14s-acp-v1-1-89b40d95720c@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index 30e01bc47ee0..2fb9789cac0a 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -98,6 +98,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { DMI_MATCH(DMI_BOARD_NAME, "8EA2"), }, }, + { + /* Dell 14S DS14265 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell 14S DS14265"), + }, + }, {} }; From fcea06d4598ed7072dc201bd659a2f253a514fb6 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Fri, 16 Oct 2026 20:40:30 +0800 Subject: [PATCH 840/924] ASoC: amd: yc: Add DMI quirk for MSI Alpha 17 C7VF The DMIC does not work on the MSI Alpha 17 C7VF because the ACP6x machine driver lacks a matching DMI quirk. Add the DMI entry so the driver binds correctly and the DMIC is functional. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222112 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20261016124030.664895-1-zhangheng@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 64a50fd5ab9c..41f8c0885865 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -535,6 +535,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Bravo 15 C7VF"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_PRODUCT_NAME, "Alpha 17 C7VF"), + } + }, { .driver_data = &acp6x_card, .matches = { From ba982c33278c1fc739c8c220f410d6539de9d9b9 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Fri, 16 Oct 2026 16:22:43 +0800 Subject: [PATCH 841/924] ASoC: rt721-sdca: Fix inconsistent indenting in jack detect handler Smatch warns about inconsistent indenting in rt721_sdca_jack_detect_handler(): the rt_sdca_index_read() call that samples the JD pin state, and the following mask of real_JD, are indented one level deeper than the surrounding statements, so they look like part of the preceding block although they are not. Re-indent them to match the rest of the block. No functional change. Fixes: e2eb4873b901 ("ASoC: rt721-sdca: Adjust blindwrite flow to fix type detection issue") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202610080041.JAcitVQd-lkp@intel.com/ Signed-off-by: Jack Yu Link: https://patch.msgid.link/20261016082243.1352940-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index 5fc0aa991914..9f567caecb70 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -121,9 +121,9 @@ static void rt721_sdca_jack_detect_handler(struct work_struct *work) return; } - rt_sdca_index_read(rt721->mbq_regmap, RT721_JD_CTRL, - RT721_JD_1PIN_GAT_STA1, &real_JD); - real_JD &= 0x1; + rt_sdca_index_read(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_STA1, &real_JD); + real_JD &= 0x1; if (real_JD == 0x0) rt721_sdca_jack_reset_state(rt721); From d602fe6d26c923b1e6193f5a96bfd7b8fbce5027 Mon Sep 17 00:00:00 2001 From: Nicolas Chauvet Date: Fri, 16 Oct 2026 11:44:04 +0200 Subject: [PATCH 842/924] ALSA: usb-audio: Add quirk for Logitech HD Pro Webcam c920 Quirky device sample: usb 5-2: new high-speed USB device number 2 using xhci_hcd usb 5-2: New USB device found, idVendor=046d, idProduct=08e5, bcdDevice= 0.0c usb 5-2: New USB device strings: Mfr=0, Product=2, SerialNumber=0 usb 5-2: Product: HD Pro Webcam C920 ... uvcvideo 5-2:1.0: Found UVC 1.00 device HD Pro Webcam C920 (046d:08e5) uvcvideo 5-2:1.1: Failed to set UVC probe control : -32 (exp. 26). usb 5-2: current rate 16000 is different from the runtime rate 32000 usb 5-2: reset high-speed USB device number 2 using xhci_hcd usb 5-2: current rate 16000 is different from the runtime rate 32000 usb 5-2: current rate 16000 is different from the runtime rate 32000 usb 5-2: current rate 16000 is different from the runtime rate 32000 Signed-off-by: Nicolas Chauvet Link: https://patch.msgid.link/20261016094404.13802-1-kwizart@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 6db571de25fa..4fadb1fdcdea 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2526,6 +2526,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x046d, 0x08ca, /* Logitech Quickcam Fusion */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x08e5, /* Logitech HD Pro Webcam c920 */ + QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x046d, 0x094c, /* Logitech Brio 100 */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), DEVICE_FLG(0x046d, 0x0991, /* Logitech QuickCam Pro */ From 2fc28b1186d6fce8b046e6118072cf3ca32f2960 Mon Sep 17 00:00:00 2001 From: Viktor Menshin Date: Thu, 15 Oct 2026 22:21:57 +0900 Subject: [PATCH 843/924] ALSA: hda/realtek: Fix hiss/crackle on Lenovo Xiaoxin 14 GT (17aa:3912) Commit 3a3e810e9108 ("ALSA: hda/realtek: Add quirk for Lenovo Xiaoxin 14 GT") added a quirk mapping SSID 17aa:3912 to ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, chosen by analogy with neighboring SSIDs (0x390d, 0x3911, 0x391a) without being verified on hardware, as noted in that commit's own message. I have since tested it on the actual hardware (Lenovo Xiaoxin Pro 14 ASP10, same 17aa:3912 SSID) and confirmed it does not fix the reported issue. ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN only repoints pin 0x17 and the preferred DAC pairs for the bass speaker; it does not touch the codec's vendor processing coefficients. The actual symptom - constant electrical hiss/crackle on both the headphone jack and the internal speakers, independent of volume - is unrelated to pin/DAC routing and is also present on stock Windows before the Realtek driver is installed, which points at those coefficients being left at their power-on default. To find the correct values, I passed the physical HD Audio controller through to a Windows 10 VM (VFIO, with QEMU patched to log CORB verb traffic) and captured the coefficient writes the vendor Realtek driver performs at init. Replaying a single one of them on Linux via hda-verb reproducibly eliminates the headphone jack noise: hda-verb /dev/snd/hwC#D0 0x20 SET_COEF_INDEX 0x36 hda-verb /dev/snd/hwC#D0 0x20 SET_PROC_COEF 0x77d7 This has held up across reboots and kernel updates for several weeks (applied via a small systemd oneshot unit while testing). Add a dedicated fixup that performs this coefficient write and chains it with the existing ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN (which may still be relevant for the bass speaker pin wiring on this board), and point the 17aa:3912 quirk entry at it instead. Honesty note: this fixes the headphone jack. The internal speakers still have noise under certain conditions (notably worse on AC power) that I have not been able to isolate via coefficient-table replay; that part remains open. Sending what is verified to work rather than holding this back. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221203 Fixes: 3a3e810e9108 ("ALSA: hda/realtek: Add quirk for Lenovo Xiaoxin 14 GT") Signed-off-by: Viktor Menshin Link: https://patch.msgid.link/20261015132157.57198-1-ripeeerr@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 799a5a7a8ed0..36d3bc23a2df 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -3759,6 +3759,28 @@ static void alc287_fixup_yoga9_14iap7_bass_spk_pin(struct hda_codec *codec, } } +/* + * The Lenovo Xiaoxin 14 GT (SSID 17aa:3912) uses the same bass-speaker + * pin wiring as its 0x390d/0x3911/0x391a siblings, but on this board the + * codec's vendor processing coefficients (node 0x20) are left at their + * power-on default instead of being programmed the way the vendor + * (Windows) driver programs them. That leaves audible hiss/crackle on + * both the headphone jack and the internal speakers, independent of + * volume, regardless of which pin/DAC routing is used. + * + * This single coefficient write was captured from the vendor driver's + * own init sequence (via a VFIO pass-through of the physical codec to a + * Windows VM) and confirmed to eliminate the headphone jack noise. + */ +static void alc287_fixup_lenovo_xiaoxin_14gt_hp_noise(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_INIT) + return; + + alc_write_coef_idx(codec, 0x36, 0x77d7); +} + static void alc_fixup_yoga_pro7_audio(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -4424,6 +4446,7 @@ enum { ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK, ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, ALC287_FIXUP_YOGA9_14IMH9_BASS_SPK_PIN, + ALC287_FIXUP_LENOVO_XIAOXIN_14GT_HP_NOISE, ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS, ALC236_FIXUP_DELL_DUAL_CODECS, ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI, @@ -6945,6 +6968,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_CS35L41_I2C_2, }, + [ALC287_FIXUP_LENOVO_XIAOXIN_14GT_HP_NOISE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_lenovo_xiaoxin_14gt_hp_noise, + .chained = true, + .chain_id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, + }, [ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS] = { .type = HDA_FIXUP_FUNC, .v.func = alc295_fixup_dell_inspiron_top_speakers, @@ -8530,7 +8559,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x3907, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET), SND_PCI_QUIRK(0x17aa, 0x3911, "Lenovo Yoga Pro 7 14IAH10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), - SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_LENOVO_XIAOXIN_14GT_HP_NOISE), SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), SND_PCI_QUIRK(0x17aa, 0x391f, "Yoga S990-16 pro Quad YC Quad", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), From e72cc359a603d57ff3dd257404c551cb473f968e Mon Sep 17 00:00:00 2001 From: Sven Jans Date: Mon, 24 Aug 2026 16:22:07 +0200 Subject: [PATCH 844/924] ALSA: hda: cs35l41: Support HP OmniBook 7 Laptop 16-bh0xxx The laptop has a malformed _DSD which causes the cirrus,dev-index property to fail to parse, so it needs to be supported using the configuration table. Signed-off-by: Sven Jans Link: https://patch.msgid.link/20260824142209.4945-1-skbotnl@disroot.org Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/cs35l41_hda_property.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index 272421d1bc8b..5c390ffbe74c 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -85,6 +85,7 @@ static const struct cs35l41_config cs35l41_config_table[] = { { "103C8C51", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8CDD", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8CDE", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 3900, 24 }, + { "103C8E60", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, { "104312AF", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 1, 2, 0, 1000, 4500, 24 }, { "10431433", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, { "10431463", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, @@ -541,6 +542,7 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "103C8C6A", hp_i2c_int_2amp_dual_spkid }, { "CSC3551", "103C8CDD", generic_dsd_config }, { "CSC3551", "103C8CDE", generic_dsd_config }, + { "CSC3551", "103C8E60", generic_dsd_config }, { "CSC3551", "104312AF", generic_dsd_config }, { "CSC3551", "10431433", generic_dsd_config }, { "CSC3551", "10431463", generic_dsd_config }, From 85ef6509bfc1eb41743ea333bf6854b2eed2f9be Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Fri, 16 Oct 2026 21:02:19 +0800 Subject: [PATCH 845/924] ALSA: hda: cs35l41: Add support for HP OmniBook 7 14-hg0xxx The HP OmniBook 7 14-hg0xxx (SSID 103C8F07) uses two CS35L41 amplifiers connected via I2C. The left speaker works, but the right speaker is silent because the cs35l41-hda driver fails to probe with -EINVAL: cs35l41-hda i2c-CSC3551:00-cs35l41-hda.0: Failed property cirrus,dev-index: -22 cs35l41-hda i2c-CSC3551:00-cs35l41-hda.0: error -EINVAL: Platform not supported cs35l41-hda i2c-CSC3551:00-cs35l41-hda.0: probe with driver cs35l41-hda failed with error -22 Add the missing SSID entry to the CS35L41 configuration and property tables, using the generic DSD configuration and the same parameters as other HP laptops (e.g. 103C8CDE). This allows both amplifiers to probe and restores the right speaker. Tested on HP OmniBook 7 14-hg0xxx (SSID 103C8F07). Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222124 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20261016130219.690074-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/cs35l41_hda_property.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index 5c390ffbe74c..c50c7e689533 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -86,6 +86,7 @@ static const struct cs35l41_config cs35l41_config_table[] = { { "103C8CDD", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8CDE", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 3900, 24 }, { "103C8E60", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, + { "103C8F07", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 3900, 24 }, { "104312AF", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 1, 2, 0, 1000, 4500, 24 }, { "10431433", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, { "10431463", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, @@ -543,6 +544,7 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "103C8CDD", generic_dsd_config }, { "CSC3551", "103C8CDE", generic_dsd_config }, { "CSC3551", "103C8E60", generic_dsd_config }, + { "CSC3551", "103C8F07", generic_dsd_config }, { "CSC3551", "104312AF", generic_dsd_config }, { "CSC3551", "10431433", generic_dsd_config }, { "CSC3551", "10431463", generic_dsd_config }, From a19bf815f40afddac5719a1edb3c46710a52819b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 16 Oct 2026 16:06:09 +0200 Subject: [PATCH 846/924] ALSA: jack: Expose injection debugfs at device register phase Currently we add debugfs interfaces for jack injection (with CONFIG_SND_JACK_INJECTION_DEBUG) at creating jack kctls in snd_jack_kctl_add(). This means that those debugfs interfaces are already exposed to user-space at the early probe phase -- that is, it allows user-space already fiddling with it, while other components are still being initialized and hidden until snd_card_register(). Depending on the driver implementation, such behavior can be an unpleasant surprise. For aligning the device exposure behavior, move the debugfs registration at the device register phase, too. snd_jack_kctl_add() may still add the corresponding debugfs interfaces (for the dynamically added jacks, if any), but it's done only conditionally after the card has been registered. Reviewed-by: Cen Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261016140653.4406-1-tiwai@suse.de --- include/sound/jack.h | 2 +- sound/core/jack.c | 56 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/include/sound/jack.h b/include/sound/jack.h index 715b95983188..cf19c333e2eb 100644 --- a/include/sound/jack.h +++ b/include/sound/jack.h @@ -62,10 +62,10 @@ struct snd_jack { struct list_head kctl_list; struct snd_card *card; const char *id; + bool registered; #ifdef CONFIG_SND_JACK_INPUT_DEV struct input_dev *input_dev; struct mutex input_dev_lock; - int registered; int type; char name[100]; unsigned int key[6]; /* Keep in sync with definitions above */ diff --git a/sound/core/jack.c b/sound/core/jack.c index d9132b885ea3..a81a64b3bfe7 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -38,6 +38,7 @@ static const int jack_switch_types[SND_JACK_SWITCH_TYPES] = { }; #endif /* CONFIG_SND_JACK_INPUT_DEV */ +static void snd_jack_add_debugfs(struct snd_jack *jack); static void snd_jack_remove_debugfs(struct snd_jack *jack); static int snd_jack_dev_disconnect(struct snd_device *device) @@ -88,11 +89,10 @@ static int snd_jack_dev_free(struct snd_device *device) } #ifdef CONFIG_SND_JACK_INPUT_DEV -static int snd_jack_dev_register(struct snd_device *device) +static int snd_jack_input_dev_register(struct snd_card *card, + struct snd_jack *jack) { - struct snd_jack *jack = device->device_data; - struct snd_card *card = device->card; - int err, i; + int i; snprintf(jack->name, sizeof(jack->name), "%s %s", card->shortname, jack->id); @@ -120,14 +120,32 @@ static int snd_jack_dev_register(struct snd_device *device) input_set_capability(jack->input_dev, EV_KEY, jack->key[i]); } - err = input_register_device(jack->input_dev); - if (err == 0) - jack->registered = 1; - - return err; + return input_register_device(jack->input_dev); +} +#else +static inline int snd_jack_input_dev_register(struct snd_card *card, + struct snd_jack *jack) +{ + return 0; } #endif /* CONFIG_SND_JACK_INPUT_DEV */ +static int snd_jack_dev_register(struct snd_device *device) +{ + struct snd_card *card = device->card; + struct snd_jack *jack = device->device_data; + int err; + + err = snd_jack_input_dev_register(card, jack); + if (err < 0) + return err; + + snd_jack_add_debugfs(jack); + + jack->registered = true; + return 0; +} + #ifdef CONFIG_SND_JACK_INJECTION_DEBUG static void snd_jack_inject_report(struct snd_jack_kctl *jack_kctl, int status) { @@ -349,6 +367,9 @@ static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, char *tname; int i; + if (jack_kctl->jack_debugfs_root) + return 0; + /* Don't create injection interface for Phantom jacks */ if (strstr(jack_kctl->kctl->id.name, "Phantom")) return 0; @@ -387,6 +408,14 @@ static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, return 0; } +static void snd_jack_add_debugfs(struct snd_jack *jack) +{ + struct snd_jack_kctl *jack_kctl; + + list_for_each_entry(jack_kctl, &jack->kctl_list, list) + snd_jack_debugfs_add_inject_node(jack, jack_kctl); +} + static void snd_jack_remove_debugfs(struct snd_jack *jack) { struct snd_jack_kctl *jack_kctl; @@ -403,6 +432,10 @@ static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, return 0; } +static void snd_jack_add_debugfs(struct snd_jack *jack) +{ +} + static void snd_jack_remove_debugfs(struct snd_jack *jack) { } @@ -423,7 +456,8 @@ static void snd_jack_kctl_add(struct snd_jack *jack, struct snd_jack_kctl *jack_ { jack_kctl->jack = jack; list_add_tail(&jack_kctl->list, &jack->kctl_list); - snd_jack_debugfs_add_inject_node(jack, jack_kctl); + if (jack->registered) + snd_jack_debugfs_add_inject_node(jack, jack_kctl); } static struct snd_jack_kctl * snd_jack_kctl_new(struct snd_card *card, const char *name, unsigned int mask) @@ -504,9 +538,7 @@ int snd_jack_new(struct snd_card *card, const char *id, int type, int err; static const struct snd_device_ops ops = { .dev_free = snd_jack_dev_free, -#ifdef CONFIG_SND_JACK_INPUT_DEV .dev_register = snd_jack_dev_register, -#endif /* CONFIG_SND_JACK_INPUT_DEV */ .dev_disconnect = snd_jack_dev_disconnect, }; From d030561d53cb272bb55ba2dd11aaa609e8240b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Gli=C5=BAniewicz?= Date: Fri, 16 Oct 2026 09:31:30 +0200 Subject: [PATCH 847/924] ALSA: 6fire: take stream_mutex in the Digital Thru control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit usb6fire_control_digital_thru_put() calls usb6fire_control_streaming_update(), which on an idle card with Digital Thru on selects altsetting 2 and writes the clock register for 96 kHz through usb6fire_control_set_rate(). usb6fire_pcm_prepare() calls both functions through usb6fire_pcm_set_rate() under rt->stream_mutex. The put does not take stream_mutex, so the two can run at the same time. 500 runs of a 0.2 s aplay at 48 kHz while a second loop toggled "Digital Thru Playback Route" with amixer (12693 on/off cycles, kernel 7.2.9): in 216 cases the two usb_set_interface() calls ran at the same time and the kernel logged "sysfs: cannot create duplicate filename '.../3-2:1.1/ep_82'" with a stack dump. 27 of the aplay runs failed, and for each of them the kernel logged "could not start pcm stream". Take stream_mutex in the put. The pcm runtime is created before the controls and freed with the card, so chip->pcm is valid whenever the put runs. The put waits for a running prepare, which makes synchronous USB transfers under stream_mutex; on a card that responds late the wait can reach about 12 s, or 24 s with Digital Thru on, and during the wait the other controls of the card can be neither read nor written, because snd_ctl_elem_write() holds controls_rwsem around the put. Before this patch the put already made transfers of the same kind under controls_rwsem. No code that holds stream_mutex takes controls_rwsem. The same test on kernel 7.2.10 with this patch (12863 cycles) gave no failed aplay run and no "duplicate filename" message. Fixes: 2475b0d40761 ("ALSA: 6fire - Add support of digital-thru mixer") Assisted-by: LLM Signed-off-by: Piotr Gliźniewicz Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261016073132.109288-2-p.glizniewicz@gmail.com --- sound/usb/6fire/control.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/usb/6fire/control.c b/sound/usb/6fire/control.c index 10104cb42165..020e9676d106 100644 --- a/sound/usb/6fire/control.c +++ b/sound/usb/6fire/control.c @@ -20,6 +20,7 @@ #include "control.h" #include "comm.h" #include "chip.h" +#include "pcm.h" static const char * const opt_coax_texts[2] = { "Optical", "Coax" }; static const char * const line_phono_texts[2] = { "Line", "Phono" }; @@ -383,6 +384,12 @@ static int usb6fire_control_digital_thru_put(struct snd_kcontrol *kcontrol, struct control_runtime *rt = snd_kcontrol_chip(kcontrol); int changed = 0; + /* + * with Digital Thru on, streaming_update() selects the altsetting and + * writes the clock register of an idle card; prepare does the same + * under stream_mutex + */ + guard(mutex)(&rt->chip->pcm->stream_mutex); if (rt->digital_thru_switch != ucontrol->value.integer.value[0]) { rt->digital_thru_switch = ucontrol->value.integer.value[0]; usb6fire_control_streaming_update(rt); From a050fabb16a5529a83e9d461b99056ae48761f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Gli=C5=BAniewicz?= Date: Fri, 16 Oct 2026 09:31:31 +0200 Subject: [PATCH 848/924] ALSA: 6fire: add a playback channel map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver registers no "Playback Channel Map" control, so a program that needs the speaker position of each playback channel has to assume an order; speaker-test, for example, assumes FL FR RL RR FC LFE for 6 channels. Channel n of the PCM goes to Line Out n, and the manual of the card assigns Line Out 1 to 6 to front left and right, centre and subwoofer, and rear left and right. Add a playback channel map in the order of the manual, FL FR FC LFE RL RR. A stream with 2 to 5 channels gets the first positions of the list. A 1-channel stream gets MONO, as in every 1-channel entry of a channel map in sound/; the card plays it on Line Out 1 only. The map has an entry for every channel count from 1 to 6, because reading the control fails with -EINVAL for a count without an entry. Tested on a DMX 6Fire USB with kernel 7.2.10: during playback with 1, 3, 5 and 6 channels the control reads MONO, FL FR FC, FL FR FC LFE RL and FL FR FC LFE RL RR, and the "Front Center" test of speaker-test -c 6 played on Line Out 3. Assisted-by: LLM Signed-off-by: Piotr Gliźniewicz Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261016073132.109288-3-p.glizniewicz@gmail.com --- sound/usb/6fire/pcm.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c index 0285d79ace0f..d99395229eb2 100644 --- a/sound/usb/6fire/pcm.c +++ b/sound/usb/6fire/pcm.c @@ -612,6 +612,28 @@ static void usb6fire_pcm_free(struct pcm_runtime *rt) kfree(rt); } +/* + * playback channel map: channel n goes to analog output n, which the + * manual of the card assigns to FL FR FC LFE RL RR for n = 1..6; a + * 1-channel stream is MONO, as in snd_pcm_std_chmaps, and plays on + * analog output 1 only + */ +static const struct snd_pcm_chmap_elem usb6fire_pcm_chmaps[] = { + { .channels = 1, .map = { SNDRV_CHMAP_MONO } }, + { .channels = 2, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 3, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC } }, + { .channels = 4, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } }, + { .channels = 5, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL } }, + { .channels = 6, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { } +}; + int usb6fire_pcm_init(struct sfire_chip *chip) { int i; @@ -665,6 +687,14 @@ int usb6fire_pcm_init(struct sfire_chip *chip) rt->instance = pcm; chip->pcm = rt; + + /* on error, rt is freed with the card */ + ret = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + usb6fire_pcm_chmaps, OUT_N_CHANNELS, 0, NULL); + if (ret < 0) { + dev_err(&chip->dev->dev, "cannot add channel map.\n"); + return ret; + } return 0; error: From 25ea03135739225481d82c9d18a5090f3891ba45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Gli=C5=BAniewicz?= Date: Fri, 16 Oct 2026 09:31:32 +0200 Subject: [PATCH 849/924] ALSA: 6fire: add S/PDIF output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DMX 6Fire USB has a coaxial S/PDIF output, which the driver keeps disabled. Enable it as playback channels 7 and 8 of the existing PCM device, the layout chosen in the RFC thread linked below. The protocol was taken from a usbmon capture of the vendor's Windows driver and checked against a disassembly of that driver. The packets of altsetting 3 (176.4 and 192 kHz) have room for the six analog channels only, so the output is available up to 96 kHz. hw_params rules allow more than 6 channels only up to 96 kHz, and prepare rejects more than 6 channels while the other stream runs at 176.4 or 192 kHz. A client that takes channels_max therefore gets 8 channels at up to 96 kHz instead of 6 channels at up to 192 kHz (the Pro Audio profile of PipeWire 1.6.9 opened the device with 8 channels); a client that asks for 6 channels still gets 192 kHz. A UCM profile can describe the channels and offer a 6-channel configuration with 192 kHz. The channel map marks channels 7 and 8 UNKNOWN; a program that ignores the map and assumes the 7.1 order sends the side pair to the S/PDIF output. The card has one rate and one frame layout for both directions, set by the first prepare and kept until the last close, and the register bit that enables the transmitter is the bit that widens the playback frame from 6 to 8 slots. The driver therefore sets the bit at every rate up to 96 kHz, whichever direction starts first, so that a playback opened later can use 8 channels; the transmitter sends what is in slots 6 and 7, zeros while no playback runs. With "IEC958 Playback Switch" off (it is on by default), the driver sends zeros in the pair and omits the block start flag, and the receiver used for testing loses lock. Two versions in which the switch also cleared the bit were tried with PipeWire and a UCM profile and dropped. With the bit tied to the switch, the channel count depended on the switch and a change had to be refused while a stream ran, which broke profile changes. With the bit set by the switch or by an 8-channel playback, the frame width depended on which stream started first, and an 8-channel playback was refused while a capture ran with the switch off. With the bit tied to the rate, as in the vendor driver, the coaxial output carries a signal whenever the card streams at up to 96 kHz; a receiver that selects its input when it detects a signal was not tested. "Digital Thru Playback Route" has priority: with it on, the output carries the coaxial input. The channel status is consumer, 24 bit, with the sample rate and the channel numbers. "IEC958 Playback Default" sets the non-audio, copyright and emphasis bits, the category code and the generation bit; "IEC958 Playback Con Mask" lists these bits. With the non-audio bit set (AC3, DTS), the validity bit of every subframe is set as well, as the vendor driver does. A change of "IEC958 Playback Default" or of the switch takes effect at the next 192-frame block. Tested on a DMX 6Fire USB at 44.1, 48 and 96 kHz with a second interface locked to the output: a tagged ramp on channels 7/8 arrived without a missing, repeated or changed sample for 4.7 s at each rate, and at 48 kHz a -20 dBFS tone arrived at -20.0 dBFS while a tone on channel 3 played on Line Out 3. With the switch off from the start, the second interface did not lock during an 8-channel, a 6-channel or a capture-only stream. Link: https://lore.kernel.org/linux-sound/30dcedc7-3b56-4396-af3d-6c2701ef1cb7@perex.cz/ Assisted-by: LLM Signed-off-by: Piotr Gliźniewicz Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261016073132.109288-4-p.glizniewicz@gmail.com --- sound/usb/6fire/control.c | 25 +++- sound/usb/6fire/pcm.c | 308 +++++++++++++++++++++++++++++++++++--- sound/usb/6fire/pcm.h | 7 +- 3 files changed, 316 insertions(+), 24 deletions(-) diff --git a/sound/usb/6fire/control.c b/sound/usb/6fire/control.c index 020e9676d106..da5a2d7ac107 100644 --- a/sound/usb/6fire/control.c +++ b/sound/usb/6fire/control.c @@ -149,9 +149,14 @@ static int usb6fire_control_set_channels( if (ret < 0) return ret; - /* disable digital inputs and outputs */ - /* TODO: use spdif_x to enable/disable digital channels */ - ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00); + /* + * digital (S/PDIF) input and output: bit 0 adds four slots (4-7) to + * the capture frame, which carry the subframes of the receiver; up to + * two receiver frames arrive per card frame; bit 1 enables the + * transmitter, which encodes the playback pair in slots 6/7 (pcm.c) + */ + ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, + (spdif_in ? 0x01 : 0x00) | (spdif_out ? 0x02 : 0x00)); if (ret < 0) return ret; @@ -161,14 +166,26 @@ static int usb6fire_control_set_channels( static int usb6fire_control_streaming_update(struct control_runtime *rt) { struct comm_runtime *comm_rt = rt->chip->comm; + int err = 0; + int ret; if (comm_rt) { + /* + * the digital pairs are enabled only while a stream runs; the + * streaming register is written even if this write fails. + * usb6fire_control_digital_thru_put() holds stream_mutex, so + * this write cannot run after set_channels() and before + * streaming starts in usb6fire_pcm_set_rate() + */ + if (!rt->usb_streaming) + err = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00); if (!rt->usb_streaming && rt->digital_thru_switch) usb6fire_control_set_rate(rt, DIGITAL_THRU_ONLY_SAMPLERATE); - return comm_rt->write16(comm_rt, 0x02, 0x00, 0x00, + ret = comm_rt->write16(comm_rt, 0x02, 0x00, 0x00, (rt->usb_streaming ? 0x01 : 0x00) | (rt->digital_thru_switch ? 0x08 : 0x00)); + return err < 0 ? err : ret; } return -EINVAL; } diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c index d99395229eb2..f5dfb8c3096a 100644 --- a/sound/usb/6fire/pcm.c +++ b/sound/usb/6fire/pcm.c @@ -9,13 +9,34 @@ * Copyright: (C) Torsten Schenk */ +#include +#include +#include + #include "pcm.h" #include "chip.h" #include "comm.h" #include "control.h" +/* + * Every USB frame is a 4 byte header followed by 32-bit slots, one per + * channel: 3 bytes of sample and a marker byte. Analog slots are marked + * 0x40. Up to 96 kHz (altsettings 1 and 2) the playback frame carries two + * more slots, 6/7, which carry the data for the S/PDIF transmitter in + * the FPGA. Their marker byte tells the transmitter how to frame the + * sample: 0x10 marks the left subframe and 0x00 the right one; 0x20 marks + * the start of the channel status block (left subframe, every 192 + * frames); 0x04 is the channel status bit of the subframe; 0x01 is the + * validity bit, set for non-audio data; 0x08 is the parity bit, which + * makes the number of ones in the sample, the validity and channel status + * bits and itself even. + * At altsetting 3 (176.4/192 kHz) the packets hold the analog slots only. + */ enum { - OUT_N_CHANNELS = 6, IN_N_CHANNELS = 4 + OUT_N_ANALOG = 6, N_SPDIF = 2, + OUT_N_CHANNELS = OUT_N_ANALOG + N_SPDIF, IN_N_CHANNELS = 4, + SPDIF_OUT_SLOT = OUT_N_ANALOG, + SPDIF_BLOCK_FRAMES = 192 }; /* keep next two synced with @@ -24,6 +45,15 @@ enum { static const int rates_in_packet_size[] = { 228, 228, 420, 420, 404, 404 }; static const int rates_out_packet_size[] = { 228, 228, 420, 420, 604, 604 }; static const int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000 }; +static const u8 rates_iec958_fs[] = { + IEC958_AES3_CON_FS_44100, IEC958_AES3_CON_FS_48000, + IEC958_AES3_CON_FS_88200, IEC958_AES3_CON_FS_96000, + IEC958_AES3_CON_FS_176400, IEC958_AES3_CON_FS_192000 }; +/* channel status bits that "IEC958 Playback Default" sets, bytes 0 and 1 */ +static const u8 spdif_ctl_mask[] = { + IEC958_AES0_NONAUDIO | IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES0_CON_EMPHASIS, + IEC958_AES1_CON_CATEGORY | IEC958_AES1_CON_ORIGINAL }; static const int rates_alsaid[] = { SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_48000, SNDRV_PCM_RATE_88200, SNDRV_PCM_RATE_96000, @@ -71,6 +101,7 @@ static int usb6fire_pcm_set_rate(struct pcm_runtime *rt) { int ret; struct control_runtime *ctrl_rt = rt->chip->control; + bool spdif; ctrl_rt->usb_streaming = false; ret = ctrl_rt->update_streaming(ctrl_rt); @@ -89,8 +120,9 @@ static int usb6fire_pcm_set_rate(struct pcm_runtime *rt) return ret; } - ret = ctrl_rt->set_channels(ctrl_rt, OUT_N_CHANNELS, IN_N_CHANNELS, - false, false); + spdif = rt->rate <= CONTROL_RATE_96KHZ; + ret = ctrl_rt->set_channels(ctrl_rt, OUT_N_ANALOG, IN_N_CHANNELS, + spdif, false); if (ret < 0) { dev_err(&rt->chip->dev->dev, "error initializing channels while setting samplerate %d.\n", @@ -104,16 +136,108 @@ static int usb6fire_pcm_set_rate(struct pcm_runtime *rt) dev_err(&rt->chip->dev->dev, "error starting streaming while setting samplerate %d.\n", rates[rt->rate]); + /* stream_stop() will not run; clear 0x02/0x03 here */ + ctrl_rt->usb_streaming = false; + ctrl_rt->update_streaming(ctrl_rt); return ret; } rt->in_n_analog = IN_N_CHANNELS; - rt->out_n_analog = OUT_N_CHANNELS; + rt->out_n_slots = spdif ? OUT_N_CHANNELS : OUT_N_ANALOG; rt->in_packet_size = rates_in_packet_size[rt->rate]; rt->out_packet_size = rates_out_packet_size[rt->rate]; + + /* + * consumer channel status, 24 bit samples; bytes 0 and 1 are taken + * from rt->spdif_bits at the start of every block + */ + memset(rt->spdif_status, 0, sizeof(rt->spdif_status)); + rt->spdif_status[3] = rates_iec958_fs[rt->rate]; + rt->spdif_status[4] = IEC958_AES4_CON_MAX_WORDLEN_24 | + IEC958_AES4_CON_WORDLEN_24_20; + rt->spdif_frame = 0; return 0; } +/* + * Return the marker byte of one playback slot whose 3 sample bytes are + * already in place. The marker is 0x40 for an analog slot and the S/PDIF + * framing for a digital slot. With "IEC958 Playback Switch" off, the + * function also clears the sample bytes of a digital slot. After the right + * subframe, the function advances the channel status position. + */ +static u8 usb6fire_pcm_marker(struct pcm_runtime *rt, int slot, + u8 *sample) +{ + bool right = slot == SPDIF_OUT_SLOT + 1; + unsigned int n = rt->spdif_frame; + u8 marker = right ? 0x00 : 0x10; + u8 status; + + if (slot < SPDIF_OUT_SLOT) + return 0x40; + if (!right && n == 0) { + /* a new block takes the bits of "IEC958 Playback Default" */ + u32 bits = READ_ONCE(rt->spdif_bits); + + rt->spdif_status[0] = bits & 0xff; + rt->spdif_status[1] = bits >> 8; + /* + * "IEC958 Playback Switch" off omits the block start flag and + * clears the sample bytes of the digital slots + */ + rt->spdif_on = READ_ONCE(rt->spdif_switch); + if (rt->spdif_on) + marker |= 0x20; + } + if (!rt->spdif_on) + memset(sample, 0, 3); + status = rt->spdif_status[n >> 3]; + /* byte 2 holds the channel number: 1 = A (left), 2 = B (right) */ + if ((n >> 3) == 2) + status |= right ? (2 << 4) : (1 << 4); + if (status & (1 << (n & 7))) + marker |= 0x04; + /* V = 1 for non-audio data (AC3, DTS), as the vendor driver does */ + if (rt->spdif_status[0] & IEC958_AES0_NONAUDIO) + marker |= 0x01; + /* P makes the number of ones in bits 0-27 (sample, V, U, C, P) even */ + if (parity8(sample[0] ^ sample[1] ^ sample[2] ^ (marker & 0x07))) + marker |= 0x08; + if (right) + rt->spdif_frame = (n + 1) % SPDIF_BLOCK_FRAMES; + return marker; +} + +/* more than OUT_N_ANALOG playback channels need a rate up to 96 kHz */ +static int usb6fire_pcm_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { .min = 1, .max = OUT_N_ANALOG, .integer = 1 }; + + if (r->min <= rates[CONTROL_RATE_96KHZ]) + return 0; + return snd_interval_refine(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS), &t); +} + +static int usb6fire_pcm_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = 0, .max = rates[CONTROL_RATE_96KHZ], .integer = 1 + }; + + if (c->min <= OUT_N_ANALOG) + return 0; + return snd_interval_refine(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE), &t); +} + static struct pcm_substream *usb6fire_pcm_get_substream( struct snd_pcm_substream *alsa_sub) { @@ -264,19 +388,21 @@ static void usb6fire_pcm_playback(struct pcm_substream *sub, } for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { - /* at least 4 header bytes for valid packet. - * after that: 32 bits per sample for analog channels */ + /* + * at least 4 header bytes for valid packet. + * after that: 32 bits per slot + */ isoc = &urb->instance->iso_frame_desc[i]; if (isoc->length > 4) frame_count = (isoc->length - 4) - / (rt->out_n_analog << 2); + / (rt->out_n_slots << 2); else frame_count = 0; dest++; /* skip leading 4 bytes of every frame */ for (frame = 0; frame < frame_count; frame++) { memcpy(dest, src, bytes_per_frame); src += alsa_rt->channels; - dest += rt->out_n_analog; + dest += rt->out_n_slots; sub->dma_off++; sub->period_off++; if (src == src_end) { @@ -301,6 +427,7 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) int channel; int i; u8 *dest; + unsigned int spdif_frame; if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING) return; @@ -344,10 +471,10 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) / (rt->in_n_analog << 2); frames = min_t(unsigned int, frames, (rt->out_packet_size - 4) - / (rt->out_n_analog << 2)); + / (rt->out_n_slots << 2)); isoc_out->offset = total_length; - isoc_out->length = frames * (rt->out_n_analog << 2) + 4; + isoc_out->length = frames * (rt->out_n_slots << 2) + 4; isoc_out->status = 0; total_length += isoc_out->length; } @@ -368,28 +495,38 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) if (period_elapsed) snd_pcm_period_elapsed(sub->instance); - /* setup the 4th byte of each sample (0x40 for analog channels) */ + /* + * set the 4th byte of each sample (0x40 for analog channels, + * S/PDIF framing for the digital pair) + */ + spdif_frame = rt->spdif_frame; dest = out_urb->buffer; for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { isoc_out = &out_urb->instance->iso_frame_desc[i]; if (isoc_out->length >= 4) { frame_count = (isoc_out->length - 4) - / (rt->out_n_analog << 2); + / (rt->out_n_slots << 2); *(dest++) = 0xaa; *(dest++) = 0xaa; *(dest++) = frame_count; *(dest++) = 0x00; for (frame = 0; frame < frame_count; frame++) for (channel = 0; - channel < rt->out_n_analog; + channel < rt->out_n_slots; channel++) { - dest += 3; /* skip sample data */ - *(dest++) = 0x40; + dest[3] = usb6fire_pcm_marker(rt, + channel, dest); + dest += 4; } } } - usb_submit_urb(out_urb->instance, GFP_ATOMIC); + /* + * the frames of an out urb that was not submitted never reach the + * transmitter; the next urb continues the block at the same position + */ + if (usb_submit_urb(out_urb->instance, GFP_ATOMIC) < 0) + rt->spdif_frame = spdif_frame; usb_submit_urb(in_urb->instance, GFP_ATOMIC); } @@ -409,6 +546,7 @@ static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub) struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); struct pcm_substream *sub = NULL; struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime; + int ret; if (rt->panic) return -EPIPE; @@ -420,6 +558,17 @@ static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub) if (rt->rate < ARRAY_SIZE(rates)) alsa_rt->hw.rates = rates_alsaid[rt->rate]; alsa_rt->hw.channels_max = OUT_N_CHANNELS; + ret = snd_pcm_hw_rule_add(alsa_rt, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + usb6fire_pcm_rule_channels, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (ret < 0) + return ret; + ret = snd_pcm_hw_rule_add(alsa_rt, 0, SNDRV_PCM_HW_PARAM_RATE, + usb6fire_pcm_rule_rate, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret < 0) + return ret; sub = &rt->playback; } else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE) { if (rt->rate < ARRAY_SIZE(rates)) @@ -489,7 +638,17 @@ static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub) alsa_rt->rate); return -EINVAL; } - + } + /* the digital pair is not available at 176.4 and 192 kHz */ + if (rt->rate > CONTROL_RATE_96KHZ && + alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK && + alsa_rt->channels > OUT_N_ANALOG) { + dev_err(&rt->chip->dev->dev, + "%d channels not available at %d Hz.\n", + alsa_rt->channels, rates[rt->rate]); + return -EINVAL; + } + if (rt->stream_state == STREAM_DISABLED) { ret = usb6fire_pcm_set_rate(rt); if (ret) return ret; @@ -612,11 +771,79 @@ static void usb6fire_pcm_free(struct pcm_runtime *rt) kfree(rt); } +static int usb6fire_pcm_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int usb6fire_pcm_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memset(ucontrol->value.iec958.status, 0, + sizeof(ucontrol->value.iec958.status)); + ucontrol->value.iec958.status[0] = spdif_ctl_mask[0]; + ucontrol->value.iec958.status[1] = spdif_ctl_mask[1]; + return 0; +} + +static int usb6fire_pcm_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol); + u32 bits = READ_ONCE(rt->spdif_bits); + + memset(ucontrol->value.iec958.status, 0, + sizeof(ucontrol->value.iec958.status)); + ucontrol->value.iec958.status[0] = bits & 0xff; + ucontrol->value.iec958.status[1] = bits >> 8; + return 0; +} + +static int usb6fire_pcm_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol); + u32 bits = (ucontrol->value.iec958.status[0] & spdif_ctl_mask[0]) | + (ucontrol->value.iec958.status[1] & spdif_ctl_mask[1]) << 8; + + /* the control core serializes puts; the urb handler uses READ_ONCE */ + if (rt->spdif_bits == bits) + return 0; + WRITE_ONCE(rt->spdif_bits, bits); + return 1; +} + +static int usb6fire_pcm_spdif_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = READ_ONCE(rt->spdif_switch); + return 0; +} + +static int usb6fire_pcm_spdif_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol); + bool on = ucontrol->value.integer.value[0]; + + /* the control core serializes puts; the urb handler uses READ_ONCE */ + if (rt->spdif_switch == on) + return 0; + WRITE_ONCE(rt->spdif_switch, on); + return 1; +} + /* * playback channel map: channel n goes to analog output n, which the * manual of the card assigns to FL FR FC LFE RL RR for n = 1..6; a * 1-channel stream is MONO, as in snd_pcm_std_chmaps, and plays on - * analog output 1 only + * analog output 1 only; channels 7 and 8 are the S/PDIF pair and have + * no position */ static const struct snd_pcm_chmap_elem usb6fire_pcm_chmaps[] = { { .channels = 1, .map = { SNDRV_CHMAP_MONO } }, @@ -631,9 +858,41 @@ static const struct snd_pcm_chmap_elem usb6fire_pcm_chmaps[] = { { .channels = 6, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 7, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_UNKNOWN } }, + { .channels = 8, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_UNKNOWN, SNDRV_CHMAP_UNKNOWN } }, { } }; +static const struct snd_kcontrol_new usb6fire_pcm_spdif_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = usb6fire_pcm_spdif_info, + .get = usb6fire_pcm_spdif_default_get, + .put = usb6fire_pcm_spdif_default_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = usb6fire_pcm_spdif_info, + .get = usb6fire_pcm_spdif_mask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH), + .info = snd_ctl_boolean_mono_info, + .get = usb6fire_pcm_spdif_switch_get, + .put = usb6fire_pcm_spdif_switch_put, + }, +}; + int usb6fire_pcm_init(struct sfire_chip *chip) { int i; @@ -652,6 +911,9 @@ int usb6fire_pcm_init(struct sfire_chip *chip) rt->chip = chip; rt->stream_state = STREAM_DISABLED; rt->rate = ARRAY_SIZE(rates); + rt->spdif_switch = true; + rt->spdif_bits = IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES1_CON_GENERAL << 8; init_waitqueue_head(&rt->stream_wait_queue); mutex_init(&rt->stream_mutex); @@ -688,7 +950,15 @@ int usb6fire_pcm_init(struct sfire_chip *chip) chip->pcm = rt; - /* on error, rt is freed with the card */ + /* on error, rt and the controls added so far are freed with the card */ + for (i = 0; i < ARRAY_SIZE(usb6fire_pcm_spdif_controls); i++) { + ret = snd_ctl_add(chip->card, + snd_ctl_new1(&usb6fire_pcm_spdif_controls[i], rt)); + if (ret < 0) { + dev_err(&chip->dev->dev, "cannot add S/PDIF control.\n"); + return ret; + } + } ret = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, usb6fire_pcm_chmaps, OUT_N_CHANNELS, 0, NULL); if (ret < 0) { diff --git a/sound/usb/6fire/pcm.h b/sound/usb/6fire/pcm.h index b586fe220fd1..f1b867b9c468 100644 --- a/sound/usb/6fire/pcm.h +++ b/sound/usb/6fire/pcm.h @@ -53,7 +53,12 @@ struct pcm_runtime { int in_packet_size; int out_packet_size; int in_n_analog; /* number of analog channels soundcard sends */ - int out_n_analog; /* number of analog channels soundcard receives */ + int out_n_slots; /* 32-bit slots per frame the soundcard receives */ + bool spdif_switch; /* "IEC958 Playback Switch" */ + bool spdif_on; /* copy of spdif_switch, read at the start of each block */ + unsigned int spdif_frame; /* position in the channel status block */ + u8 spdif_status[24]; /* IEC 60958 channel status of the S/PDIF output */ + u32 spdif_bits; /* status bytes 0 and 1, "IEC958 Playback Default" */ struct mutex stream_mutex; u8 stream_state; /* one of STREAM_XXX (pcm.c) */ From 1ba2cb30eeab2b9b9d576f87a80a750f04b4bfa7 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 16 Oct 2026 11:09:36 +0700 Subject: [PATCH 850/924] ASoC: amd: Explain component open return value handling Add comments explaining why the .open() callbacks must return 0 on success to ensure proper rollback handling and prevent future changes from reintroducing the issue. Reviewed-by: Vijendar Mukunda Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261016040946.521195-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-platform.c | 5 +++++ sound/soc/amd/ps/ps-pdm-dma.c | 6 ++++++ sound/soc/amd/ps/ps-sdw-dma.c | 6 ++++++ sound/soc/amd/raven/acp3x-pcm-dma.c | 6 ++++++ sound/soc/amd/renoir/acp3x-pdm-dma.c | 6 ++++++ sound/soc/amd/vangogh/acp5x-pcm-dma.c | 6 ++++++ sound/soc/amd/yc/acp6x-pdm-dma.c | 6 ++++++ 7 files changed, 41 insertions(+) diff --git a/sound/soc/amd/acp/acp-platform.c b/sound/soc/amd/acp/acp-platform.c index ba6dfe8fb6b7..f2c8853775ad 100644 --- a/sound/soc/amd/acp/acp-platform.c +++ b/sound/soc/amd/acp/acp-platform.c @@ -248,6 +248,11 @@ static int acp_dma_open(struct snd_soc_component *component, struct snd_pcm_subs list_add_tail(&stream->list, &chip->stream_list); spin_unlock_irq(&chip->acp_lock); + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ return 0; } diff --git a/sound/soc/amd/ps/ps-pdm-dma.c b/sound/soc/amd/ps/ps-pdm-dma.c index 28e0686e9373..12c341e89bce 100644 --- a/sound/soc/amd/ps/ps-pdm-dma.c +++ b/sound/soc/amd/ps/ps-pdm-dma.c @@ -211,6 +211,12 @@ static int acp63_pdm_dma_open(struct snd_soc_component *component, pdm_data->acp63_base = adata->acp63_base; runtime->private_data = pdm_data; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ return 0; } diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c index 553fe11a2e50..c2640985f54c 100644 --- a/sound/soc/amd/ps/ps-sdw-dma.c +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -337,6 +337,12 @@ static int acp63_sdw_dma_open(struct snd_soc_component *component, stream->stream_id = cpu_dai->id; stream->instance = amd_manager->instance; runtime->private_data = stream; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ return 0; } diff --git a/sound/soc/amd/raven/acp3x-pcm-dma.c b/sound/soc/amd/raven/acp3x-pcm-dma.c index 072f3dc35d40..765057499021 100644 --- a/sound/soc/amd/raven/acp3x-pcm-dma.c +++ b/sound/soc/amd/raven/acp3x-pcm-dma.c @@ -237,6 +237,12 @@ static int acp3x_dma_open(struct snd_soc_component *component, i2s_data->acp3x_base = adata->acp3x_base; runtime->private_data = i2s_data; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ return 0; } diff --git a/sound/soc/amd/renoir/acp3x-pdm-dma.c b/sound/soc/amd/renoir/acp3x-pdm-dma.c index c0481a9ba858..89dc60849c06 100644 --- a/sound/soc/amd/renoir/acp3x-pdm-dma.c +++ b/sound/soc/amd/renoir/acp3x-pdm-dma.c @@ -233,6 +233,12 @@ static int acp_pdm_dma_open(struct snd_soc_component *component, pdm_data->acp_base = adata->acp_base; runtime->private_data = pdm_data; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ return 0; } diff --git a/sound/soc/amd/vangogh/acp5x-pcm-dma.c b/sound/soc/amd/vangogh/acp5x-pcm-dma.c index 33d59d992ee0..72050fa2de80 100644 --- a/sound/soc/amd/vangogh/acp5x-pcm-dma.c +++ b/sound/soc/amd/vangogh/acp5x-pcm-dma.c @@ -231,6 +231,12 @@ static int acp5x_dma_open(struct snd_soc_component *component, } i2s_data->acp5x_base = adata->acp5x_base; runtime->private_data = i2s_data; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ return 0; } diff --git a/sound/soc/amd/yc/acp6x-pdm-dma.c b/sound/soc/amd/yc/acp6x-pdm-dma.c index afa9f66533e3..cba2509faaa1 100644 --- a/sound/soc/amd/yc/acp6x-pdm-dma.c +++ b/sound/soc/amd/yc/acp6x-pdm-dma.c @@ -209,6 +209,12 @@ static int acp6x_pdm_dma_open(struct snd_soc_component *component, pdm_data->acp6x_base = adata->acp6x_base; runtime->private_data = pdm_data; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ return 0; } From ec2e1434c7f78f59ef26d12d3ed6560de9202e6c Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 16 Oct 2026 11:09:37 +0700 Subject: [PATCH 851/924] ASoC: apple: Explain DAI startup return value handling Add a comment explaining why .startup() must return 0 on success to ensure the DAI is correctly marked as started and prevent future changes from reintroducing the issue. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261016040946.521195-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/apple/mca.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index 3585432ad49b..26022a42a95e 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -535,6 +535,11 @@ static int mca_fe_startup(struct snd_pcm_substream *substream, if (ret < 0) return ret; + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ return 0; } From 1554ce7a5d9972780c8ebdddfc78299a107a50d8 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 16 Oct 2026 11:09:38 +0700 Subject: [PATCH 852/924] ASoC: atmel: Explain component open return value handling Add a comment explaining why .open() must return 0 on success to ensure proper rollback handling and prevent future changes from reintroducing the issue. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261016040946.521195-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/atmel/atmel-pcm-pdc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/atmel/atmel-pcm-pdc.c b/sound/soc/atmel/atmel-pcm-pdc.c index 552bfa30d4ba..4cedf2ad934d 100644 --- a/sound/soc/atmel/atmel-pcm-pdc.c +++ b/sound/soc/atmel/atmel-pcm-pdc.c @@ -295,6 +295,11 @@ static int atmel_pcm_open(struct snd_soc_component *component, } runtime->private_data = prtd; + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ return 0; out: From a6b1f3ccca48f9aefcbb6f09487931064a1ebe76 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 16 Oct 2026 11:09:39 +0700 Subject: [PATCH 853/924] ASoC: codecs: Explain DAI startup return value handling Add comments explaining why the .startup() callbacks must return 0 on success to ensure the DAI is correctly marked as started and prevent future changes from reintroducing the issue. Signed-off-by: bui duc phuc Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20261016040946.521195-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l42.c | 5 +++++ sound/soc/codecs/nau8325.c | 5 +++++ sound/soc/codecs/nau8540.c | 5 +++++ sound/soc/codecs/nau8821.c | 5 +++++ sound/soc/codecs/nau8824.c | 5 +++++ sound/soc/codecs/nau8825.c | 5 +++++ sound/soc/codecs/wm8580.c | 5 +++++ sound/soc/codecs/wm8782.c | 5 +++++ 8 files changed, 40 insertions(+) diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index f6e4bf6206b7..18d6cb9bfa2c 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -907,6 +907,11 @@ static int cs42l42_dai_startup(struct snd_pcm_substream *substream, struct snd_s if (ret < 0) return ret; + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ return 0; } diff --git a/sound/soc/codecs/nau8325.c b/sound/soc/codecs/nau8325.c index 0902a7d7730d..5ccf4c110567 100644 --- a/sound/soc/codecs/nau8325.c +++ b/sound/soc/codecs/nau8325.c @@ -501,6 +501,11 @@ static int nau8325_dai_startup(struct snd_pcm_substream *substream, if (ret < 0) return ret; + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ return 0; } diff --git a/sound/soc/codecs/nau8540.c b/sound/soc/codecs/nau8540.c index 9484388ab262..09e7f31a904d 100644 --- a/sound/soc/codecs/nau8540.c +++ b/sound/soc/codecs/nau8540.c @@ -431,6 +431,11 @@ static int nau8540_dai_startup(struct snd_pcm_substream *substream, if (ret < 0) return ret; + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ return 0; } diff --git a/sound/soc/codecs/nau8821.c b/sound/soc/codecs/nau8821.c index 935df063822c..7b88ee3159d1 100644 --- a/sound/soc/codecs/nau8821.c +++ b/sound/soc/codecs/nau8821.c @@ -841,6 +841,11 @@ static int nau8821_dai_startup(struct snd_pcm_substream *substream, if (ret < 0) return ret; + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ return 0; } diff --git a/sound/soc/codecs/nau8824.c b/sound/soc/codecs/nau8824.c index c89889aa420e..046cce9192b7 100644 --- a/sound/soc/codecs/nau8824.c +++ b/sound/soc/codecs/nau8824.c @@ -1073,6 +1073,11 @@ static int nau8824_dai_startup(struct snd_pcm_substream *substream, if (ret < 0) return ret; + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ return 0; } diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index 10bbfbb460b1..af2a49c893d3 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -1273,6 +1273,11 @@ static int nau8825_dai_startup(struct snd_pcm_substream *substream, if (ret < 0) return ret; + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ return 0; } diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 5343d445c322..cee80da8f753 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -860,6 +860,11 @@ static int wm8580_playback_startup(struct snd_pcm_substream *substream, if (ret < 0) return ret; + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ return 0; } diff --git a/sound/soc/codecs/wm8782.c b/sound/soc/codecs/wm8782.c index 767251ac4f89..cbf0c823c022 100644 --- a/sound/soc/codecs/wm8782.c +++ b/sound/soc/codecs/wm8782.c @@ -46,6 +46,11 @@ static int wm8782_dai_startup(struct snd_pcm_substream *sub, struct snd_soc_dai if (ret < 0) return ret; + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ return 0; } From f3e3dbbbbfd6139a05f96c628a694df590b439cd Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 16 Oct 2026 11:09:40 +0700 Subject: [PATCH 854/924] ASoC: imx-pcm-rpmsg: Explain component open return value handling Add a comment explaining why .open() must return 0 on success to ensure proper rollback handling and prevent future changes from reintroducing the issue. Reviewed-by: Chancel Liu Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261016040946.521195-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/imx-pcm-rpmsg.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c index 30e75fee5175..85b7c929a28a 100644 --- a/sound/soc/fsl/imx-pcm-rpmsg.c +++ b/sound/soc/fsl/imx-pcm-rpmsg.c @@ -269,6 +269,12 @@ static int imx_rpmsg_pcm_open(struct snd_soc_component *component, info->stream_timer[substream->stream].substream = substream; timer_setup(&info->stream_timer[substream->stream].timer, imx_rpmsg_timer_callback, 0); + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ return 0; } From 2f8d93394b7fe6af629760aca368bc55af7b2919 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 16 Oct 2026 11:09:41 +0700 Subject: [PATCH 855/924] ASoC: intel: atom: Explain DAI startup return value handling Add a comment explaining why .startup() must return 0 on success to ensure proper rollback handling and prevent future changes from reintroducing the issue. Acked-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261016040946.521195-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/intel/atom/sst-mfld-platform-pcm.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c index db1fb3b26909..58681e007abf 100644 --- a/sound/soc/intel/atom/sst-mfld-platform-pcm.c +++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c @@ -341,6 +341,11 @@ static int sst_media_open(struct snd_pcm_substream *substream, stream = NULL; + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .shutdown() is not skipped on rollback. + */ return 0; } From 394f91bd0f7a3770201a53a104d37ca6a53b4df3 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 16 Oct 2026 11:09:42 +0700 Subject: [PATCH 856/924] ASoC: renesas: msiof: Explain component open return value handling Add a comment explaining why .open() must return 0 on success to ensure the component is correctly marked as opened and prevent future changes from reintroducing the issue. Acked-by: Kuninori Morimoto Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261016040946.521195-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/renesas/fsi.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/renesas/fsi.c b/sound/soc/renesas/fsi.c index 88618e4b052d..74e2ea002fbb 100644 --- a/sound/soc/renesas/fsi.c +++ b/sound/soc/renesas/fsi.c @@ -1820,6 +1820,11 @@ static int fsi_pcm_open(struct snd_soc_component *component, if (ret < 0) return ret; + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure the component is correctly marked as opened. + */ return 0; } From d2f4c848d0103027b16c4ed5171f3669031a69d4 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 16 Oct 2026 11:09:43 +0700 Subject: [PATCH 857/924] ASoC: renesas: msiof: Remove redundant period constraint snd_pcm_hw_constraint_integer() is already called by snd_dmaengine_pcm_open(). Calling it again in msiof_open() is unnecessary, as the constraint is already applied and the second call returns 0. Remove the redundant constraint call. Acked-by: Kuninori Morimoto Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261016040946.521195-9-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/renesas/rcar/msiof.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/sound/soc/renesas/rcar/msiof.c b/sound/soc/renesas/rcar/msiof.c index 9fead9a22cdc..11a6f22ec0ad 100644 --- a/sound/soc/renesas/rcar/msiof.c +++ b/sound/soc/renesas/rcar/msiof.c @@ -419,19 +419,13 @@ static int msiof_open(struct snd_soc_component *component, return PTR_ERR(chan); ret = snd_dmaengine_pcm_open(substream, chan); - if (ret < 0) - goto open_err_dma; - - snd_soc_set_runtime_hwparams(substream, &msiof_pcm_hardware); - - ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); - -open_err_dma: if (ret < 0) { dma_release_channel(chan); return ret; } + snd_soc_set_runtime_hwparams(substream, &msiof_pcm_hardware); + return 0; } From 65850cb2f9ce16a19c739e119a902927f3841c11 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 16 Oct 2026 11:09:44 +0700 Subject: [PATCH 858/924] ASoC: renesas: rz-ssi: Explain component open return value handling Add a comment explaining why .open() must return 0 on success to ensure the component is correctly marked as opened and prevent future changes from reintroducing the issue. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261016040946.521195-10-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/renesas/rz-ssi.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c index 2e297f721bb6..9f36fe2c16d2 100644 --- a/sound/soc/renesas/rz-ssi.c +++ b/sound/soc/renesas/rz-ssi.c @@ -946,6 +946,11 @@ static int rz_ssi_pcm_open_pio(struct snd_soc_component *component, if (ret < 0) return ret; + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure the component is correctly marked as opened. + */ return 0; } From a0b64de6ea7e086f64ce97dec61e6639b334e13f Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 16 Oct 2026 11:09:45 +0700 Subject: [PATCH 859/924] ASoC: renesas: rz-ssi: Remove redundant component open callback In DMA mode, the generic dmaengine PCM component already applies the integer PERIODS constraint in snd_dmaengine_pcm_open(). Since rz_ssi_pcm_open_dma() only applies this constraint, remove the redundant component .open callback. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261016040946.521195-11-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/renesas/rz-ssi.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c index 9f36fe2c16d2..9a0781bb072d 100644 --- a/sound/soc/renesas/rz-ssi.c +++ b/sound/soc/renesas/rz-ssi.c @@ -954,19 +954,6 @@ static int rz_ssi_pcm_open_pio(struct snd_soc_component *component, return 0; } -static int rz_ssi_pcm_open_dma(struct snd_soc_component *component, - struct snd_pcm_substream *substream) -{ - int ret; - - ret = snd_pcm_hw_constraint_integer(substream->runtime, - SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) - return ret; - - return 0; -} - static snd_pcm_uframes_t rz_ssi_pcm_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream) { @@ -1017,7 +1004,6 @@ static const struct snd_soc_component_driver rz_ssi_soc_component_pio = { static const struct snd_soc_component_driver rz_ssi_soc_component_dma = { .name = "rz-ssi", - .open = rz_ssi_pcm_open_dma, .legacy_dai_naming = 1, }; From 48ed45aa30ced064b3425be777028aa3bd0ce423 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 16 Oct 2026 11:09:46 +0700 Subject: [PATCH 860/924] ASoC: rockchip: Explain DAI link startup return value handling Add comments explaining why the .startup() callbacks must return 0 on success to ensure the DAI link is correctly marked as started and prevent future changes from reintroducing the issue. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20261016040946.521195-12-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/rockchip/rk3399_gru_sound.c | 5 +++++ sound/soc/rockchip/rockchip_max98090.c | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/sound/soc/rockchip/rk3399_gru_sound.c b/sound/soc/rockchip/rk3399_gru_sound.c index 0ec80a4b8c59..d4a0daabfb42 100644 --- a/sound/soc/rockchip/rk3399_gru_sound.c +++ b/sound/soc/rockchip/rk3399_gru_sound.c @@ -266,6 +266,11 @@ static int rockchip_sound_startup(struct snd_pcm_substream *substream) if (ret < 0) return ret; + /* + * ASoC only marks the DAI link as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI link is correctly marked as started. + */ return 0; } diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c index d54c36682415..de392b420ebd 100644 --- a/sound/soc/rockchip/rockchip_max98090.c +++ b/sound/soc/rockchip/rockchip_max98090.c @@ -202,6 +202,11 @@ static int rk_aif1_startup(struct snd_pcm_substream *substream) if (ret < 0) return ret; + /* + * ASoC only marks the DAI link as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI link is correctly marked as started. + */ return 0; } From ebef9212cb469706e28002a20f9d48307d1fd867 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Fri, 16 Oct 2026 16:25:16 +0900 Subject: [PATCH 861/924] ASoC: codecs: da732x: Fix definition of ADC2 EQ Overall Volume control The ADC2 EQ overall volume is in DA732X_REG_ADC2_EQ5 (0x9F), not in DA732X_REG_ADC1_EQ5 (0x97), which holds the ADC1 one. Fixes: 48e278746070 ("ASoC: codecs: Add DA732x codec driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/35bae3f84cdc3ceb6ca412153f44dee3647fdbd6.1792135144.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/da732x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/da732x.c b/sound/soc/codecs/da732x.c index c9b6172ac0a1..e417c7ada237 100644 --- a/sound/soc/codecs/da732x.c +++ b/sound/soc/codecs/da732x.c @@ -511,7 +511,7 @@ static const struct snd_kcontrol_new da732x_snd_controls[] = { SOC_SINGLE_TLV("ACD2 EQ Band 5 Volume", DA732X_REG_ADC2_EQ5, DA732X_EQ_BAND5_SHIFT, DA732X_EQ_VOL_VAL_MAX, DA732X_INVERT, eq_band_pga_tlv), - SOC_SINGLE_TLV("ADC2 EQ Overall Volume", DA732X_REG_ADC1_EQ5, + SOC_SINGLE_TLV("ADC2 EQ Overall Volume", DA732X_REG_ADC2_EQ5, DA732X_EQ_OVERALL_SHIFT, DA732X_EQ_OVERALL_VOL_VAL_MAX, DA732X_INVERT, eq_overall_tlv), From 0a9efcb663b5a4d468cbb90de29c6dd3794af124 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Fri, 16 Oct 2026 16:25:17 +0900 Subject: [PATCH 862/924] ASoC: codecs: tlv320adc3xxx: Fix definition of AGC Target Level control The right channel AGC target level is in RIGHT_CHN_AGC_1, as on the left, not in RIGHT_CHN_AGC_2, which holds the right noise threshold and hysteresis. Fixes: e9a3b57efd28 ("ASoC: codec: tlv320adc3xxx: New codec driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/09cad9ec9bb382e44cad079a680bdf0f6163567e.1792135144.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320adc3xxx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/tlv320adc3xxx.c b/sound/soc/codecs/tlv320adc3xxx.c index 528c44446482..79f4abc245dd 100644 --- a/sound/soc/codecs/tlv320adc3xxx.c +++ b/sound/soc/codecs/tlv320adc3xxx.c @@ -691,7 +691,7 @@ static const struct snd_kcontrol_new adc3xxx_snd_controls[] = { SOC_DOUBLE_R("AGC Capture Switch", ADC3XXX_LEFT_CHN_AGC_1, ADC3XXX_RIGHT_CHN_AGC_1, 7, 1, 0), SOC_DOUBLE_R_TLV("AGC Target Level Capture Volume", ADC3XXX_LEFT_CHN_AGC_1, - ADC3XXX_RIGHT_CHN_AGC_2, 4, 0x07, 1, agc_target_tlv), + ADC3XXX_RIGHT_CHN_AGC_1, 4, 0x07, 1, agc_target_tlv), SOC_DOUBLE_R_TLV("AGC Noise Threshold Capture Volume", ADC3XXX_LEFT_CHN_AGC_2, ADC3XXX_RIGHT_CHN_AGC_2, 1, 0x1f, 1, agc_thresh_tlv), SOC_DOUBLE_R_TLV("AGC Hysteresis Capture Volume", ADC3XXX_LEFT_CHN_AGC_2, From ea53ab9003d8655cf7e94428de3006e86fbd06a0 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Fri, 16 Oct 2026 16:25:18 +0900 Subject: [PATCH 863/924] ASoC: codecs: sta32x: Fix definition of Ch2 and Ch3 Volume Bypass controls Each channel's master volume bypass (CxVBP) is in its own configuration register, C2CFG and C3CFG, not in C1CFG, which holds the channel 1 bit. Fixes: c034abf6e503 ("ASoC: add STA32X codec driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/8576f091aa0fe172ce43f28dacd6a1bf638258c6.1792135144.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/sta32x.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c index 2865a07be395..1e4a19769618 100644 --- a/sound/soc/codecs/sta32x.c +++ b/sound/soc/codecs/sta32x.c @@ -491,8 +491,8 @@ SOC_SINGLE("Ch2 Tone Control Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_TCB_SHIF SOC_SINGLE("Ch1 EQ Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_EQBP_SHIFT, 1, 0), SOC_SINGLE("Ch2 EQ Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_EQBP_SHIFT, 1, 0), SOC_SINGLE("Ch1 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), -SOC_SINGLE("Ch2 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), -SOC_SINGLE("Ch3 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), +SOC_SINGLE("Ch2 Master Volume Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), +SOC_SINGLE("Ch3 Master Volume Bypass Switch", STA32X_C3CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), SOC_ENUM("Ch1 Limiter Select", sta32x_limiter_ch1_enum), SOC_ENUM("Ch2 Limiter Select", sta32x_limiter_ch2_enum), SOC_ENUM("Ch3 Limiter Select", sta32x_limiter_ch3_enum), From 7308ce7c8459e994a36a22607e974e3e132a9275 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Fri, 16 Oct 2026 16:25:19 +0900 Subject: [PATCH 864/924] ASoC: codecs: sta350: Fix definition of Ch2 and Ch3 Volume Bypass controls Each channel's master volume bypass (CxVBP) is in its own configuration register, C2CFG and C3CFG, not in C1CFG, which holds the channel 1 bit. Fixes: 2439ea1f0f8f ("ASoC: sta350: Add codec driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/1107a42a563459b26038bdff4d8f6e2423816d0a.1792135144.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/sta350.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/sta350.c b/sound/soc/codecs/sta350.c index d2b6f73d5e10..95aee5aa2bff 100644 --- a/sound/soc/codecs/sta350.c +++ b/sound/soc/codecs/sta350.c @@ -477,9 +477,9 @@ SOC_SINGLE("Ch2 EQ Bypass Switch", SOC_SINGLE("Ch1 Master Volume Bypass Switch", STA350_C1CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), SOC_SINGLE("Ch2 Master Volume Bypass Switch", - STA350_C1CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), + STA350_C2CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), SOC_SINGLE("Ch3 Master Volume Bypass Switch", - STA350_C1CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), + STA350_C3CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), SOC_ENUM("Ch1 Binary Output Select", sta350_binary_output_ch1_enum), SOC_ENUM("Ch2 Binary Output Select", sta350_binary_output_ch2_enum), SOC_ENUM("Ch3 Binary Output Select", sta350_binary_output_ch3_enum), From 2b20c976bb630905993d47699085462a477bc7b6 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Fri, 16 Oct 2026 04:31:49 -0300 Subject: [PATCH 865/924] ASoC: qcom: qdsp6: q6routing: add Senary MI2S ports This commit adds support for senary MI2S mixers. Reviewed-by: Srinivas Kandagatla Signed-off-by: Val Packett Link: https://patch.msgid.link/20261016073157.611956-2-val@packett.cool Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6routing.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c index d076c99f197f..ea511200c8d7 100644 --- a/sound/soc/qcom/qdsp6/q6routing.c +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -68,6 +68,7 @@ { mix_name, "SEC_MI2S_TX", "SEC_MI2S_TX" }, \ { mix_name, "QUAT_MI2S_TX", "QUAT_MI2S_TX" }, \ { mix_name, "QUIN_MI2S_TX", "QUIN_MI2S_TX" }, \ + { mix_name, "SEN_MI2S_TX", "SEN_MI2S_TX" }, \ { mix_name, "TERT_MI2S_TX", "TERT_MI2S_TX" }, \ { mix_name, "SLIMBUS_0_TX", "SLIMBUS_0_TX" }, \ { mix_name, "SLIMBUS_1_TX", "SLIMBUS_1_TX" }, \ @@ -152,6 +153,9 @@ SOC_SINGLE_EXT("QUIN_MI2S_TX", QUINARY_MI2S_TX, \ id, 1, 0, msm_routing_get_audio_mixer, \ msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEN_MI2S_TX", SENARY_MI2S_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ SOC_SINGLE_EXT("SLIMBUS_0_TX", SLIMBUS_0_TX, \ id, 1, 0, msm_routing_get_audio_mixer, \ msm_routing_put_audio_mixer), \ @@ -563,6 +567,9 @@ static const struct snd_kcontrol_new quaternary_mi2s_rx_mixer_controls[] = { static const struct snd_kcontrol_new quinary_mi2s_rx_mixer_controls[] = { Q6ROUTING_RX_MIXERS(QUINARY_MI2S_RX) }; +static const struct snd_kcontrol_new senary_mi2s_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SENARY_MI2S_RX) }; + static const struct snd_kcontrol_new tertiary_mi2s_rx_mixer_controls[] = { Q6ROUTING_RX_MIXERS(TERTIARY_MI2S_RX) }; @@ -825,6 +832,9 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { SND_SOC_DAPM_MIXER("QUIN_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, quinary_mi2s_rx_mixer_controls, ARRAY_SIZE(quinary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SEN_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + senary_mi2s_rx_mixer_controls, + ARRAY_SIZE(senary_mi2s_rx_mixer_controls)), SND_SOC_DAPM_MIXER("TERT_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, tertiary_mi2s_rx_mixer_controls, ARRAY_SIZE(tertiary_mi2s_rx_mixer_controls)), @@ -1038,6 +1048,7 @@ static const struct snd_soc_dapm_route intercon[] = { Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_6_RX Audio Mixer", "SLIMBUS_6_RX"), Q6ROUTING_RX_DAPM_ROUTE("QUAT_MI2S_RX Audio Mixer", "QUAT_MI2S_RX"), Q6ROUTING_RX_DAPM_ROUTE("QUIN_MI2S_RX Audio Mixer", "QUIN_MI2S_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SEN_MI2S_RX Audio Mixer", "SEN_MI2S_RX"), Q6ROUTING_RX_DAPM_ROUTE("TERT_MI2S_RX Audio Mixer", "TERT_MI2S_RX"), Q6ROUTING_RX_DAPM_ROUTE("SEC_MI2S_RX Audio Mixer", "SEC_MI2S_RX"), Q6ROUTING_RX_DAPM_ROUTE("PRI_MI2S_RX Audio Mixer", "PRI_MI2S_RX"), From 41c6eb15291fe201973561c293b44b19b6c2d595 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Fri, 16 Oct 2026 04:31:50 -0300 Subject: [PATCH 866/924] ASoC: qcom: sm8250: add Senary MI2S RX support Add support for the SENARY_MI2S_RX DAI which is used on some devices to send audio data to speaker amplifiers. Reviewed-by: Srinivas Kandagatla Signed-off-by: Val Packett Link: https://patch.msgid.link/20261016073157.611956-3-val@packett.cool Signed-off-by: Mark Brown --- sound/soc/qcom/sm8250.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c index 76dc3a3f4a31..2d49b04cb1f6 100644 --- a/sound/soc/qcom/sm8250.c +++ b/sound/soc/qcom/sm8250.c @@ -112,6 +112,14 @@ static int sm8250_snd_startup(struct snd_pcm_substream *substream) snd_soc_dai_set_fmt(cpu_dai, fmt); snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); break; + case SENARY_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_SEN_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; case LPI_MI2S_RX_0: codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; snd_soc_dai_set_sysclk(cpu_dai, From a0f1887655af52892e4a9447f9434f286a7bce48 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Fri, 16 Oct 2026 04:31:51 -0300 Subject: [PATCH 867/924] ASoC: dt-bindings: qcom,sm8250: Add Motorola Edge 30 sound card Document the bindings for the sound card on motorola-dubai which uses the Elite DSP architecture. Signed-off-by: Val Packett Link: https://patch.msgid.link/20261016073157.611956-4-val@packett.cool Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/qcom,sm8250.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml index fc7bccdace0f..c76c9c670bc1 100644 --- a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml @@ -36,6 +36,7 @@ properties: - ayaneo,pocket-s2-sndcard - fairphone,fp4-sndcard - fairphone,fp5-sndcard + - motorola,dubai-sndcard - qcom,apq8096-sndcard - qcom,glymur-sndcard - qcom,qcm6490-idp-sndcard From 32c9d661dab029348fcd389ca918402d341d2ff2 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Fri, 16 Oct 2026 04:31:52 -0300 Subject: [PATCH 868/924] ASoC: qcom: sm8250: Add motorola-dubai soundcard compatible Add a compatible for the SM7325-based Motorola Edge 30 which can use this machine driver. Signed-off-by: Val Packett Link: https://patch.msgid.link/20261016073157.611956-5-val@packett.cool Signed-off-by: Mark Brown --- sound/soc/qcom/sm8250.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c index 2d49b04cb1f6..5866f1f34b3d 100644 --- a/sound/soc/qcom/sm8250.c +++ b/sound/soc/qcom/sm8250.c @@ -215,6 +215,7 @@ static int sm8250_platform_probe(struct platform_device *pdev) static const struct of_device_id snd_sm8250_dt_match[] = { { .compatible = "fairphone,fp4-sndcard", .data = "sm7225" }, { .compatible = "fairphone,fp5-sndcard", .data = "qcm6490" }, + { .compatible = "motorola,dubai-sndcard", .data = "sm7325" }, { .compatible = "qcom,qrb2210-sndcard", .data = "qcm2290" }, { .compatible = "qcom,qrb4210-rb2-sndcard", .data = "sm4250" }, { .compatible = "qcom,qrb5165-rb5-sndcard", .data = "sm8250" }, From 8855a9319c18d46db02ddbd11960a30436e642f5 Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Fri, 9 Oct 2026 18:42:47 +0530 Subject: [PATCH 869/924] ASoC: dt-bindings: ti,pcm1681: Document the optional SCK clock The PCM1681 can use an external system clock supplied on the SCK input pin. Platforms that provide this clock need a way to describe the clock connection in Device Tree. Document the optional 'clocks' and 'clock-names' properties and define the clock name as "sck" to represent the external system clock source. This allows boards using an externally supplied SCK clock to accurately describe the hardware configuration. Reviewed-by: Shawn Guo Signed-off-by: Mohammad Rafi Shaik Link: https://patch.msgid.link/20261009-nord-asoc-driver-support-v3-v3-1-0c1897f21ccf@oss.qualcomm.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/ti,pcm1681.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml b/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml index 1f0e6787a746..1b38035189a0 100644 --- a/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml +++ b/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml @@ -21,6 +21,13 @@ properties: reg: maxItems: 1 + clocks: + maxItems: 1 + description: System clock (SCK) + + clock-names: + const: sck + "#sound-dai-cells": const: 0 From 8987e9a24f453e5080327ee5c71b4d40858c24eb Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Fri, 9 Oct 2026 18:42:48 +0530 Subject: [PATCH 870/924] ASoC: codec: pcm1681: Add optional SCK clock and runtime PM support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PCM1681 requires its SCK system clock to be running before register access. On platforms where SCK is provided by a gateable clock, register access may fail when the clock is disabled.   Add support for an optional "sck" clock and enable it before accessing the device. After enabling SCK, wait for the required 65536 system clock cycles to allow the device to complete its internal reset sequence.   Use runtime PM to manage SCK instead of keeping it enabled for the lifetime of the device. Disable the clock during runtime suspend and restore it on runtime resume. Use autosuspend to avoid unnecessary clock toggling between closely spaced accesses.   Since the device register state may be lost when SCK is disabled, enable the regmap cache. Switch regmap to cache-only mode and mark the cache dirty on runtime suspend, then synchronize the cached register state after SCK is restored on runtime resume. Mark the zero-detect status register volatile since it is updated by hardware.   Drop idle_bias_on so that the component can reach SND_SOC_BIAS_OFF and runtime suspend can gate SCK. Assisted-by: LLM Signed-off-by: Mohammad Rafi Shaik Reviewed-by: Shawn Guo Link: https://patch.msgid.link/20261009-nord-asoc-driver-support-v3-v3-2-0c1897f21ccf@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/pcm1681.c | 137 +++++++++++++++++++++++++++++++++++-- 1 file changed, 131 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c index 60fdbe5c4e05..a1ccf1edf481 100644 --- a/sound/soc/codecs/pcm1681.c +++ b/sound/soc/codecs/pcm1681.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -36,6 +38,12 @@ #define PCM1681_DEEMPH_CONTROL 0x0a /* De-emphasis control */ #define PCM1681_ZERO_DETECT_STATUS 0x0e /* Zero detect status reg */ +/* + * The DAC requires 65536 system clock cycles after the clock is supplied to + * complete its internal reset sequence before it is ready to be used. + */ +#define PCM1681_SCK_SETTLE_CYCLES 65536 + static const struct reg_default pcm1681_reg_defaults[] = { { 0x01, 0xff }, { 0x02, 0xff }, @@ -67,6 +75,12 @@ static bool pcm1681_writeable_reg(struct device *dev, unsigned int reg) (reg != PCM1681_ZERO_DETECT_STATUS); } +static bool pcm1681_volatile_reg(struct device *dev, unsigned int reg) +{ + /* Status is updated by the hardware and must never be cached */ + return reg == PCM1681_ZERO_DETECT_STATUS; +} + struct pcm1681_private { struct regmap *regmap; unsigned int format; @@ -74,8 +88,24 @@ struct pcm1681_private { unsigned int deemph; /* Current rate for deemphasis control */ unsigned int rate; + struct clk *sck; }; +/* Wait for the DAC to come out of its internal reset after sck is enabled */ +static void pcm1681_sck_settle(struct pcm1681_private *priv) +{ + unsigned long rate = clk_get_rate(priv->sck); + + /* + * sck is optional, so there may be no clock to wait on, and its rate + * is not always discoverable. Only wait when the cycle count can be + * converted into a delay. + */ + if (rate) + fsleep(DIV_ROUND_UP_ULL(PCM1681_SCK_SETTLE_CYCLES * + (u64)USEC_PER_SEC, rate)); +} + static const int pcm1681_deemph[] = { 44100, 48000, 32000 }; static int pcm1681_set_deemph(struct snd_soc_component *component) @@ -282,6 +312,8 @@ static const struct regmap_config pcm1681_regmap = { .num_reg_defaults = ARRAY_SIZE(pcm1681_reg_defaults), .writeable_reg = pcm1681_writeable_reg, .readable_reg = pcm1681_accessible_reg, + .volatile_reg = pcm1681_volatile_reg, + .cache_type = REGCACHE_MAPLE, }; static const struct snd_soc_component_driver soc_component_dev_pcm1681 = { @@ -291,7 +323,6 @@ static const struct snd_soc_component_driver soc_component_dev_pcm1681 = { .num_dapm_widgets = ARRAY_SIZE(pcm1681_dapm_widgets), .dapm_routes = pcm1681_dapm_routes, .num_dapm_routes = ARRAY_SIZE(pcm1681_dapm_routes), - .idle_bias_on = 1, .use_pmdown_time = 1, .endianness = 1, }; @@ -304,34 +335,128 @@ MODULE_DEVICE_TABLE(i2c, pcm1681_i2c_id); static int pcm1681_i2c_probe(struct i2c_client *client) { + struct device *dev = &client->dev; int ret; struct pcm1681_private *priv; - priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; + priv->sck = devm_clk_get_optional(dev, "sck"); + if (IS_ERR(priv->sck)) + return dev_err_probe(dev, PTR_ERR(priv->sck), + "Failed to get sck\n"); + priv->regmap = devm_regmap_init_i2c(client, &pcm1681_regmap); if (IS_ERR(priv->regmap)) { ret = PTR_ERR(priv->regmap); - dev_err(&client->dev, "Failed to create regmap: %d\n", ret); + dev_err(dev, "Failed to create regmap: %d\n", ret); return ret; } + /* Must be set before runtime PM is enabled, the callbacks use it */ i2c_set_clientdata(client, priv); - return devm_snd_soc_register_component(&client->dev, - &soc_component_dev_pcm1681, - &pcm1681_dai, 1); + ret = clk_prepare_enable(priv->sck); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable sck\n"); + + pcm1681_sck_settle(priv); + + /* The clock is on, so hand the now-active device over to runtime PM */ + pm_runtime_set_autosuspend_delay(dev, 100); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = devm_snd_soc_register_component(dev, + &soc_component_dev_pcm1681, + &pcm1681_dai, 1); + if (ret) { + dev_err(dev, "Failed to register component: %d\n", ret); + goto err_pm; + } + + return 0; + +err_pm: + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); + if (!pm_runtime_status_suspended(dev)) + clk_disable_unprepare(priv->sck); + pm_runtime_set_suspended(dev); + + return ret; } +static void pcm1681_i2c_remove(struct i2c_client *client) +{ + struct pcm1681_private *priv = i2c_get_clientdata(client); + struct device *dev = &client->dev; + + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); + /* Runtime PM may already have gated the clock */ + if (!pm_runtime_status_suspended(dev)) + clk_disable_unprepare(priv->sck); + pm_runtime_set_suspended(dev); +} + +static int pcm1681_runtime_suspend(struct device *dev) +{ + struct pcm1681_private *priv = dev_get_drvdata(dev); + + /* + * Registers lose their contents once the clock is gated, so serve + * further access from the cache and replay it on resume. + */ + regcache_cache_only(priv->regmap, true); + regcache_mark_dirty(priv->regmap); + + clk_disable_unprepare(priv->sck); + + return 0; +} + +static int pcm1681_runtime_resume(struct device *dev) +{ + struct pcm1681_private *priv = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(priv->sck); + if (ret) { + dev_err(dev, "Failed to enable sck: %d\n", ret); + return ret; + } + + pcm1681_sck_settle(priv); + + regcache_cache_only(priv->regmap, false); + ret = regcache_sync(priv->regmap); + if (ret) { + dev_err(dev, "Failed to sync regcache: %d\n", ret); + regcache_cache_only(priv->regmap, true); + clk_disable_unprepare(priv->sck); + return ret; + } + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(pcm1681_pm_ops, pcm1681_runtime_suspend, + pcm1681_runtime_resume, NULL); + static struct i2c_driver pcm1681_i2c_driver = { .driver = { .name = "pcm1681", .of_match_table = of_match_ptr(pcm1681_dt_ids), + .pm = pm_ptr(&pcm1681_pm_ops), }, .id_table = pcm1681_i2c_id, .probe = pcm1681_i2c_probe, + .remove = pcm1681_i2c_remove, }; module_i2c_driver(pcm1681_i2c_driver); From a93f77e4187f210772b6024d2d05180b82521a92 Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Fri, 9 Oct 2026 18:42:49 +0530 Subject: [PATCH 871/924] ASoC: codec: pcm1681: Add TDM support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PCM1681 selects the audio interface format using FMT[3:0] in the format register. The driver currently supports only I2S, left-justified, and right-justified formats, preventing a machine driver from selecting TDM mode.   Add support for SND_SOC_DAIFMT_DSP_A and program the PCM1681 I2S TDM format when it is selected. Also advertise DSP_A in the selectable DAI formats.   While adding TDM support, correct the reset default for the format register from 0x06 to 0x05. The current default does not match the hardware reset value. Although this has no effect while regmap caching is disabled, it could cause the TDM format write to be skipped if regmap caching is enabled in the future.   The existing I2S, left-justified, and right-justified formats remain unchanged. Assisted-by: LLM Signed-off-by: Mohammad Rafi Shaik Link: https://patch.msgid.link/20261009-nord-asoc-driver-support-v3-v3-3-0c1897f21ccf@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/pcm1681.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c index a1ccf1edf481..3675b2236d66 100644 --- a/sound/soc/codecs/pcm1681.c +++ b/sound/soc/codecs/pcm1681.c @@ -53,7 +53,7 @@ static const struct reg_default pcm1681_reg_defaults[] = { { 0x06, 0xff }, { 0x07, 0x00 }, { 0x08, 0x00 }, - { 0x09, 0x06 }, + { 0x09, 0x05 }, { 0x0A, 0x00 }, { 0x0B, 0xff }, { 0x0C, 0x0f }, @@ -217,6 +217,9 @@ static int pcm1681_hw_params(struct snd_pcm_substream *substream, case SND_SOC_DAIFMT_LEFT_J: val = 0x05; break; + case SND_SOC_DAIFMT_DSP_A: + val = 0x06; + break; default: dev_err(component->dev, "Invalid DAI format\n"); return -EINVAL; @@ -232,7 +235,8 @@ static int pcm1681_hw_params(struct snd_pcm_substream *substream, static const u64 pcm1681_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_I2S | SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | - SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; static const struct snd_soc_dai_ops pcm1681_dai_ops = { .set_fmt = pcm1681_set_dai_fmt, From f5f5bfcecf65a2a20ed391490b31ac7f6008a5be Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Fri, 9 Oct 2026 18:42:50 +0530 Subject: [PATCH 872/924] ASoC: dt-bindings: ti,pcm1681: Document the PCM1681 supplies The PCM1681 takes a 5V analog supply on VCC1 and VCC2 and a 3.3V digital supply on VDD. On boards where these rails are switchable, the codec has to know about them to be able to power the DAC up and down, which needs a way to describe the connections in Device Tree. Document the VCC1, VCC2 and VDD supply properties. Signed-off-by: Shawn Guo Signed-off-by: Mohammad Rafi Shaik Link: https://patch.msgid.link/20261009-nord-asoc-driver-support-v3-v3-4-0c1897f21ccf@oss.qualcomm.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/ti,pcm1681.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml b/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml index 1b38035189a0..fb09d1c39e3b 100644 --- a/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml +++ b/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml @@ -28,6 +28,15 @@ properties: clock-names: const: sck + VCC1-supply: + description: 5V analog power supply + + VCC2-supply: + description: 5V analog power supply + + VDD-supply: + description: 3.3V digital power supply + "#sound-dai-cells": const: 0 From 3b953d0e72a1dcbbe9c833ef114cbae246d63ed8 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Fri, 9 Oct 2026 18:42:51 +0530 Subject: [PATCH 873/924] ASoC: codec: pcm1681: Enable the PCM1681 supplies The driver assumes that the analog and digital supplies of the DAC are already up by the time it probes. That only holds when the rails are hardwired or left on by the bootloader. Boards which gate them, like the Qualcomm Nord RRD and Ride, have no way to get them enabled, and the first register access then fails. Enable the VCC1, VCC2 and VDD supplies before touching the clock or the regmap, so that the DAC is powered whenever the driver is bound. The supplies are optional as far as the regulator core is concerned, so boards which do not describe them keep working through the dummy regulator. Signed-off-by: Shawn Guo Signed-off-by: Mohammad Rafi Shaik Link: https://patch.msgid.link/20261009-nord-asoc-driver-support-v3-v3-5-0c1897f21ccf@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/pcm1681.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c index 3675b2236d66..6002da3a5666 100644 --- a/sound/soc/codecs/pcm1681.c +++ b/sound/soc/codecs/pcm1681.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -337,6 +338,10 @@ static const struct i2c_device_id pcm1681_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, pcm1681_i2c_id); +static const char * const pcm1681_supply_names[] = { + "VCC1", "VCC2", "VDD", +}; + static int pcm1681_i2c_probe(struct i2c_client *client) { struct device *dev = &client->dev; @@ -347,6 +352,13 @@ static int pcm1681_i2c_probe(struct i2c_client *client) if (!priv) return -ENOMEM; + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(pcm1681_supply_names), + pcm1681_supply_names); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable supplies\n"); + priv->sck = devm_clk_get_optional(dev, "sck"); if (IS_ERR(priv->sck)) return dev_err_probe(dev, PTR_ERR(priv->sck), From 22a72cf1d60c55e5cd3d03fcfdb5a92af0dc590e Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Fri, 9 Oct 2026 18:42:52 +0530 Subject: [PATCH 874/924] ASoC: dt-bindings: qcom,sm8250: Add Nord sound card Document the bindings for the Qualcomm Nord sound card. The bindings are the same as for other newer Qualcomm ADSP sound cards, thus keep them in existing qcom,sm8250.yaml file. Acked-by: Rob Herring (Arm) Signed-off-by: Mohammad Rafi Shaik Link: https://patch.msgid.link/20261009-nord-asoc-driver-support-v3-v3-6-0c1897f21ccf@oss.qualcomm.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/qcom,sm8250.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml index fc7bccdace0f..372919ff9836 100644 --- a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml @@ -38,6 +38,7 @@ properties: - fairphone,fp5-sndcard - qcom,apq8096-sndcard - qcom,glymur-sndcard + - qcom,nord-sndcard - qcom,qcm6490-idp-sndcard - qcom,qcs615-sndcard - qcom,qcs6490-rb3gen2-sndcard From 6e539c02b04bd6700eab5d5115639646118f7fde Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Fri, 9 Oct 2026 18:42:53 +0530 Subject: [PATCH 875/924] ASoC: qcom: sc8280xp: Add Nord sound card support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the Nord Ride and RRD sound cards by introducing the qcom,nord-sndcard compatible and associated platform-specific data.   Nord uses external codecs for its playback and capture paths and requires board-specific DAI and clock configuration. Configure the codec DAI format for DSP_A operation and enable MI2S bit clock handling for the platform.   Add a platform-specific sound card initialization callback to configure the ADAU1979 capture codec to use LRCLK as its system clock source. This configuration is done during DAI link initialization so that the codec clock source and associated rate constraints are established before the capture stream is opened. Signed-off-by: Mohammad Rafi Shaik Link: https://patch.msgid.link/20261009-nord-asoc-driver-support-v3-v3-7-0c1897f21ccf@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/sc8280xp.c | 51 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index b5d367486d56..a759ae9511b6 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -25,6 +25,10 @@ #define I2S_BIT_RATE(rate, channels, format) \ ((rate) * (channels) * (format)) +#define ADAU1977_SYSCLK 0 +#define ADAU1977_SYSCLK_SRC_MCLK 0 +#define ADAU1977_SYSCLK_SRC_LRCLK 1 + static struct snd_soc_dapm_widget sc8280xp_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("Mic Jack", NULL), @@ -92,6 +96,7 @@ struct qcom_snd_soc_common { bool wcd_jack; const struct sc8280xp_dai_data *dai_data; size_t num_dai_data; + int (*snd_init)(struct snd_soc_pcm_runtime *rtd); int (*snd_prepare)(struct snd_pcm_substream *substream); }; @@ -224,9 +229,16 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) struct snd_soc_card *card = rtd->card; struct snd_soc_jack *dp_jack = NULL; int dp_pcm_id = 0; + int ret; dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + if (data->priv->snd_init) { + ret = data->priv->snd_init(rtd); + if (ret) + return ret; + } + switch (cpu_dai->id) { case WSA_CODEC_DMA_RX_0: case WSA_CODEC_DMA_RX_1: @@ -305,6 +317,35 @@ static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, return 0; } +static int nord_snd_init(struct snd_soc_pcm_runtime *rtd) +{ + 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); + int ret; + + switch (cpu_dai->id) { + case AIF_TDM_TX_8: + /* + * adau1979 PLL clocked from LRCLK, no external MCLK. Selecting + * the source also installs the PLL rate constraint, which the + * codec applies from its startup() callback, so it has to be + * in place before the first stream is opened. The frequency is + * unused for an LRCLK-sourced PLL. + */ + ret = snd_soc_component_set_sysclk(codec_dai->component, + ADAU1977_SYSCLK, + ADAU1977_SYSCLK_SRC_LRCLK, + 48000, SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) + return ret; + break; + default: + break; + } + + return 0; +} + static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -552,6 +593,15 @@ static const struct qcom_snd_soc_common kaanapali_priv_data = { .wcd_jack = true, }; +static const struct qcom_snd_soc_common nord_priv_data = { + .driver_name = "nord", + .mi2s_bclk_enable = true, + .codec_dai_fmt = SND_SOC_DAIFMT_CBC_CFC | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_DSP_A, + .snd_init = nord_snd_init, +}; + static const struct qcom_snd_soc_common qcs9100_priv_data = { .driver_name = "sa8775p", .dapm_widgets = sc8280xp_dapm_widgets, @@ -677,6 +727,7 @@ static const struct of_device_id snd_sc8280xp_dt_match[] = { { .compatible = "qcom,hawi-sndcard", .data = &hawi_priv_data }, { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data }, { .compatible = "qcom,maili-sndcard", .data = &hawi_priv_data }, + { .compatible = "qcom,nord-sndcard", .data = &nord_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 }, From 68d4a6ea6cc6091a05836f783bc17d525d266d0b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 16 Oct 2026 18:19:29 +0200 Subject: [PATCH 876/924] ALSA: seq: oss: Skip events until fully initialized When an OSS sequencer device is opened, it creates a sequencer port, then further initializes the internal states like a timer instance, etc. Since the sequencer port is published, an event can be already delivered, and this may hit unexpected event handling, which can eventually cause a NULL dereference (like the timer start/stop event triggered before the timer initialization). For avoiding such a race, introduce a simple guard and skips the event processing until the port is fully initialized. At releasing the device, in turn, clear this guard, so that any input events will be discarded during the teardown. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Sashiko Cc: Link: https://patch.msgid.link/20261016161930.482309-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/oss/seq_oss_device.h | 1 + sound/core/seq/oss/seq_oss_event.c | 3 +++ sound/core/seq/oss/seq_oss_init.c | 2 ++ 3 files changed, 6 insertions(+) diff --git a/sound/core/seq/oss/seq_oss_device.h b/sound/core/seq/oss/seq_oss_device.h index 935cf3df0b30..571186db4fa5 100644 --- a/sound/core/seq/oss/seq_oss_device.h +++ b/sound/core/seq/oss/seq_oss_device.h @@ -72,6 +72,7 @@ struct seq_oss_devinfo { int cseq; /* sequencer client number */ int port; /* sequencer port number */ int queue; /* sequencer queue number */ + bool initialized; struct snd_seq_addr addr; /* address of this device */ diff --git a/sound/core/seq/oss/seq_oss_event.c b/sound/core/seq/oss/seq_oss_event.c index 3c2cfaea94d1..cf493f6c983b 100644 --- a/sound/core/seq/oss/seq_oss_event.c +++ b/sound/core/seq/oss/seq_oss_event.c @@ -425,6 +425,9 @@ snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data; union evrec *rec; + if (!dp || !smp_load_acquire(&dp->initialized)) + return 0; /* ignored */ + if (ev->type != SNDRV_SEQ_EVENT_ECHO) return snd_seq_oss_midi_input(ev, direct, private_data); diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c index 6586e07431c3..6474a76a3cce 100644 --- a/sound/core/seq/oss/seq_oss_init.c +++ b/sound/core/seq/oss/seq_oss_init.c @@ -258,6 +258,7 @@ snd_seq_oss_open(struct file *file, int level) client_table[dp->index] = dp; num_clients++; + smp_store_release(&dp->initialized, true); return 0; @@ -403,6 +404,7 @@ snd_seq_oss_release(struct seq_oss_devinfo *dp) client_table[dp->index] = NULL; num_clients--; + smp_store_release(&dp->initialized, false); snd_seq_oss_reset(dp); From ba8c3d5fc27ac3f86962641dd3b6600906234bab Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 17 Oct 2026 15:00:58 +0800 Subject: [PATCH 877/924] ALSA: jack: Protect the index scan against control removal A control found during a jack-name index scan must remain alive until snd_ctl_find_id() finishes comparing its ID and count. However, get_available_index() calls it without holding card->controls_rwsem. With CONFIG_SND_CTL_FAST_LOOKUP enabled, the lookup loads a raw control pointer from ctl_hash and dereferences it in elem_id_matches() without any lock that excludes removal. This can happen when a jack is created while a different jack owner on the same card removes an existing control with the same name and index. For example, probe and unbind of different HDA codecs can reach these paths on a shared card if their generated jack names match. The codec device locks do not serialize operations on different codecs. The unbind path can free the control during the index scan: Codec A: probe Codec B: unbind -------------- --------------- get_available_index() snd_ctl_find_id() kctl = xa_load(...) hda_codec_driver_remove() snd_hda_codec_cleanup_for_unbind() snd_hda_jack_tbl_clear() snd_device_free() __snd_device_free() snd_jack_dev_free() snd_ctl_remove() snd_ctl_remove_locked() __snd_ctl_remove() snd_ctl_free_one() kfree(kctl) elem_id_matches(kctl, ...) read freed id/count -> UAF Hold controls_rwsem for read across the index scan so removal cannot free a control until the lookup has finished using it. Release the lock when get_available_index() returns, before the caller adds the new control under the write lock. Fixes: 38ea4c3dc306 ("ALSA: control: Optimize locking for look-up") Signed-off-by: Cen Zhang Assisted-by: LLM Link: https://patch.msgid.link/pm-pmb-f08c844bb1163c8d4fa1-v2-2f3c99e40f90134846ee@gmail.com Signed-off-by: Takashi Iwai --- sound/core/ctljack.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/core/ctljack.c b/sound/core/ctljack.c index 6b6ab34fbde8..36700fda79f3 100644 --- a/sound/core/ctljack.c +++ b/sound/core/ctljack.c @@ -38,6 +38,7 @@ static int get_available_index(struct snd_card *card, const char *name) sid.iface = SNDRV_CTL_ELEM_IFACE_CARD; strscpy(sid.name, name, sizeof(sid.name)); + guard(rwsem_read)(&card->controls_rwsem); while (snd_ctl_find_id(card, &sid)) { sid.index++; /* reset numid; otherwise snd_ctl_find_id() hits this again */ From c80286aaa4acd52f9bd0b0d4914d2069fe6ba3a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Castellanos=20Valladolid?= Date: Fri, 16 Oct 2026 23:06:15 -0600 Subject: [PATCH 878/924] ALSA: hda/realtek: Enable mute LEDs on HP OmniBook X Flip 14-fm0xxx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP OmniBook X Flip Laptop 14-fm0xxx (subsystem ID 0x103c8d9f, ALC245 with two CS35L41 amplifiers over I2C) is currently handled by ALC287_FIXUP_CS35L41_I2C_2. That binds the amplifiers but does not set up the mute LEDs, so neither the speaker mute key LED nor the microphone mute key LED lights up under Linux. With that fixup the codec GPIOs stay disabled (IO[0..2] enable=0). Switch the entry to ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS, which chains cs35l41_fixup_i2c_two with ALC245_FIXUP_HP_X360_MUTE_LEDS. This keeps the amplifier setup and adds the speaker mute LED through the ALC245 COEF register and the mic mute LED through codec GPIO2. It is the same fixup already used for the 17 Turbine OmniBook 7 DIS (0x103c8d9c). Tested on the affected laptop with v7.2.11 using the equivalent ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX fixup, which has the identical cs35l41_fixup_i2c_two + ALC245_FIXUP_HP_X360_MUTE_LEDS chain: hda::mute and hda::micmute LED class devices appear and both key LEDs light up when muting. Fixes: 29951021367f ("ALSA: hda/realtek: Add support for various HP Laptops using CS35L41 HDA") Cc: stable@vger.kernel.org Signed-off-by: José Miguel Castellanos Valladolid Link: https://patch.msgid.link/20261017050615.14481-1-josstamy@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 36d3bc23a2df..c33a385982f2 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7957,7 +7957,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8d9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8d9e, "HP 17 Turbine OmniBook X DIS", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8da0, "HP 16 Clipper OmniBook 7(X360)", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8da1, "HP 16 Clipper OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8da7, "HP 14 Enstrom OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), From d933955c456e0573b40eae4f72a3af37ab1c15c3 Mon Sep 17 00:00:00 2001 From: Luis Gerardo Lopez Hernandez Date: Fri, 16 Oct 2026 18:50:39 -0600 Subject: [PATCH 879/924] ALSA: hda/realtek: Fix internal speakers on Acer Aspire AL16-31P The Acer Aspire AL16-31P (Intel Alder Lake-N, Realtek ALC269VC, PCI/codec SSID 2782:1718) has its internal speakers wired to pin 0x1b, but the BIOS marks that pin as not connected (0x411111f0) and only exposes pin 0x14 as the speaker. As a result the internal speakers are silent while headphones, HDMI and Bluetooth work. This is the same layout as the Infinix Y4 Max and MEDION E15433, which share the 0x2782 subsystem vendor. Reuse their fixup, which configures pin 0x1b as an internal speaker and limits the internal mic boost. Tested by booting with snd-hda-intel model=2782:1705: the fixup is picked, pin 0x1b is used as a speaker output, the internal speakers work, and they are muted when headphones are plugged in. Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Luis Gerardo Lopez Hernandez Link: https://patch.msgid.link/20261017005039.9139-1-gamaerry@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index c33a385982f2..af43e7fb99b7 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8671,6 +8671,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x2782, 0x1701, "Infinix Y4 Max", ALC269VC_FIXUP_INFINIX_Y4_MAX), SND_PCI_QUIRK(0x2782, 0x1705, "MEDION E15433", ALC269VC_FIXUP_INFINIX_Y4_MAX), SND_PCI_QUIRK(0x2782, 0x1707, "Vaio VJFE-ADL", ALC298_FIXUP_SPK_VOLUME), + SND_PCI_QUIRK(0x2782, 0x1718, "Acer Aspire AL16-31P", ALC269VC_FIXUP_INFINIX_Y4_MAX), SND_PCI_QUIRK(0x2782, 0x4900, "MEDION E15443", ALC233_FIXUP_MEDION_MTL_SPK), SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), From 2533eecf592c2d7434170bf9669a4270fd594ae2 Mon Sep 17 00:00:00 2001 From: Giovanni Maria Mazzone Date: Fri, 16 Oct 2026 19:46:27 +0200 Subject: [PATCH 880/924] ALSA: hda/realtek: Add quirk for HP 15s-eq3xxx mute LED The HP Laptop 15s-eq3xxx (SSID 103c:89fc) uses the Realtek ALC236 codec. Its speaker mute LED is driven by COEF index 0x07 bit 0, which is exactly what the existing ALC236_FIXUP_HP_MUTE_LED_COEFBIT2 quirk implements. The sibling models 15s-eq1xxx (103c:8706) and 15s-eq2xxx (103c:887a) already carry this quirk, but the eq3xxx SSID was never added, so the LED never follows the mute state. Add the missing SSID to the quirk table. Signed-off-by: Giovanni Maria Mazzone Link: https://patch.msgid.link/20261016174627.362872-1-giovannimariamazzone@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index af43e7fb99b7..ddff42cdcfb2 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7737,6 +7737,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8870, "HP ZBook Fury 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x8873, "HP ZBook Studio 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x887a, "HP Laptop 15s-eq2xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x89fc, "HP Laptop 15s-eq3xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x887c, "HP Laptop 14s-fq1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x888a, "HP ENVY x360 Convertible 15-eu0xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x888d, "HP ZBook Power 15.6 inch G8 Mobile Workstation PC", ALC236_FIXUP_HP_GPIO_LED), From 54f82c0614b10d70e0679e89aff622ed86598edb Mon Sep 17 00:00:00 2001 From: Keane Mbae Date: Sat, 17 Oct 2026 12:51:32 +0300 Subject: [PATCH 881/924] ALSA: hda/realtek: Add mic-mute LED quirk for the HP 245 14 inch G10 The HP 245 14 inch G10 Notebook PC (codec SSID 103c:8ba4, ALC236) is not listed in alc269_fixup_tbl[], so the codec falls back to the vendor-wide HP entry (ALC269_FIXUP_HP_MUTE_LED). That entry only registers a speaker mute LED (hda::mute); no microphone mute LED class device (hda::micmute) is created, so muting the microphone never lights the LED on the F8 key. On this model the microphone mute LED is wired to COEF 0x35 bits 2-3 (the same register as ALC236_FIXUP_HP_MUTE_LED), but with the opposite sense: 1 << 2 lights the LED and 2 << 2 turns it off. Setting micmute_led_polarity = 1 makes alc_update_coef_led() write the level that matches the hardware. The speaker mute LED (F5) keeps being driven through the HP_Mute_LED DMI string, exactly as the current fallback does. Add ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV for 103c:8ba4. Signed-off-by: Keane Mbae Tested-by: Keane Mbae Link: https://patch.msgid.link/20261017095132.20279-1-keanembae@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index ddff42cdcfb2..c2f5bbf56b75 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -1754,6 +1754,25 @@ static void alc295_fixup_hp_pavilion_mute_led_1b(struct hda_codec *codec, spec->mute_led_nid = 0x1b; } +/* + * HP 245 14 inch G10 Notebook PC (SSID 103c:8ba4): the speaker mute LED is + * controlled through the HP_Mute_LED DMI string (NID 0x1a), same as the + * ALC269_FIXUP_HP_MUTE_LED fallback, but the microphone mute LED is wired to + * COEF 0x35 bits 2-3 with an inverted sense. + */ +static void alc236_fixup_hp_mute_led_micmute_coef_inv(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->micmute_led_polarity = 1; + + alc269_fixup_hp_mute_led(codec, fix, action); + alc236_fixup_hp_coef_micmute_led(codec, fix, action); +} + static void alc233_fixup_lenovo_coef_micmute_led(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -4359,6 +4378,7 @@ enum { ALC236_FIXUP_HP_MUTE_LED_COEFBIT2, ALC236_FIXUP_HP_GPIO_LED, ALC236_FIXUP_HP_MUTE_LED, + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV, ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, ALC236_FIXUP_HP_15_FD0XXX, ALC236_FIXUP_LENOVO_INV_DMIC, @@ -6228,6 +6248,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc236_fixup_hp_mute_led_micmute_vref, }, + [ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_mute_led_micmute_coef_inv, + }, [ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO] = { .type = HDA_FIXUP_FUNC, .v.func = alc236_fixup_hp_mute_led_micmute_gpio, @@ -7858,6 +7882,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b92, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8ba4, "HP 245 14 inch G10 Notebook PC", + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV), SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bb1, "HP Victus 15-fa1xxx (MB 8BB1)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), From 11bdeb33df051f4c3a8504d78e113e1127f97b63 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Sat, 17 Oct 2026 17:54:00 +0800 Subject: [PATCH 882/924] ALSA: hda/realtek: Add fixup for Huawei MateBook X Pro 2019 (MACHR-WX9) On the MACHR-WX9 (ALC256, PCI SSID 19e5:3e13) the BIOS pin table disables the main speaker pair (pin 0x14), so only the tweeters on 0x1b play. Reusing the MACH_WX9 (19e5:3204) pin table restores all four speakers. Pin 0x19 (headset mic) has broken presence detect on this board: it reports "plugged" even with the jack empty, so auto-mic routes capture to the empty jack and the internal mic goes silent. Setting NO_PRESENCE on 0x19 (0x04a11140) suppresses the false reading. Chaining to ALC269_FIXUP_HEADSET_MODE then infers headset-mic presence from the headphone jack (pin 0x21), whose detection works correctly. Add ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS with the MACH_WX9 pin table, chained to ALC269_FIXUP_HEADSET_MODE, registered for PCI SSID 19e5:3e13. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222126 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20261017095400.350164-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index c2f5bbf56b75..de251fc46447 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4321,6 +4321,7 @@ enum { ALC255_FIXUP_DUMMY_LINEOUT_VERB, ALC255_FIXUP_DELL_HEADSET_MIC, ALC256_FIXUP_HUAWEI_MACH_WX9_PINS, + ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS, ALC298_FIXUP_HUAWEI_MBX_STEREO, ALC295_FIXUP_HP_X360, ALC295_FIXUP_HP_PAVILION_X360, @@ -5034,6 +5035,24 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC255_FIXUP_MIC_MUTE_LED }, + [ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + {0x12, 0x90a60130}, + {0x13, 0x40000000}, + {0x14, 0x90170110}, + {0x18, 0x411111f0}, + {0x19, 0x04a11140}, /* use as headset mic, without its own jack detect */ + {0x1a, 0x411111f0}, + {0x1b, 0x90170112}, + {0x1d, 0x40759a05}, + {0x1e, 0x411111f0}, + {0x21, 0x04211020}, + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, [ALC298_FIXUP_HUAWEI_MBX_STEREO] = { .type = HDA_FIXUP_FUNC, .v.func = alc298_fixup_huawei_mbx_stereo, @@ -8636,6 +8655,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), SND_PCI_QUIRK(0x19e5, 0x320f, "Huawei WRT-WX9 ", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x19e5, 0x3212, "Huawei KLV-WX9 ", ALC256_FIXUP_ACER_HEADSET_MIC), + SND_PCI_QUIRK(0x19e5, 0x3e13, "Huawei MACHR-WX9", ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS), SND_PCI_QUIRK(0x1a58, 0x2023, "Razer Blade 16 (2025)", ALC298_FIXUP_RAZER_BLADE16_2025_PINS), SND_PCI_QUIRK(0x1a58, 0x300e, "Razer Blade 16 (2025)", From f6e8d5ebde5ceb7d3c0eef6e9e4d82862b503ade Mon Sep 17 00:00:00 2001 From: Vasiliy Kovalev Date: Sat, 17 Oct 2026 13:12:57 +0300 Subject: [PATCH 883/924] ALSA: hda/via: Apply runtime PM workaround for ASUS X55A ASUS X55A requires the same workaround like other machines with VT1802, otherwise it loses the codec power on a few nodes and the sound stays silent. Link: https://bugzilla.altlinux.org/45606 Cc: Tested-by: Nikita Maksimov Signed-off-by: Vasiliy Kovalev Link: https://patch.msgid.link/20261017101257.215166-1-kovalev@altlinux.org Signed-off-by: Takashi Iwai --- sound/hda/codecs/via.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/via.c b/sound/hda/codecs/via.c index 807312cffd35..0641fc14b346 100644 --- a/sound/hda/codecs/via.c +++ b/sound/hda/codecs/via.c @@ -933,6 +933,7 @@ static const struct hda_fixup via_fixups[] = { static const struct hda_quirk vt2002p_fixups[] = { SND_PCI_QUIRK(0x1043, 0x13f7, "Asus B23E", VIA_FIXUP_POWER_SAVE), SND_PCI_QUIRK(0x1043, 0x1487, "Asus G75", VIA_FIXUP_ASUS_G75), + SND_PCI_QUIRK(0x1043, 0x1c33, "Asus X55A", VIA_FIXUP_POWER_SAVE), SND_PCI_QUIRK(0x1043, 0x8532, "Asus X202E", VIA_FIXUP_INTMIC_BOOST), SND_PCI_QUIRK_VENDOR(0x1558, "Clevo", VIA_FIXUP_POWER_SAVE), {} From e06bf60ee5bffa9eeacff0bf8b03d4572b04f67d Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sun, 18 Oct 2026 15:47:44 +0900 Subject: [PATCH 884/924] ASoC: codecs: sta32x: Fix definition of limiter threshold controls The limiter attack and release thresholds are the two 4-bit halves of one register, so their maximum is 15, not 16. With 16 the controls on bits 3:0 also write bit 4, which belongs to the other threshold. Fixes: c034abf6e503 ("ASoC: add STA32X codec driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/179118fc7680b2b872d1f917b7fd21f54a73a6d8.1792135145.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/sta32x.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c index 2865a07be395..48b9f9621ef8 100644 --- a/sound/soc/codecs/sta32x.c +++ b/sound/soc/codecs/sta32x.c @@ -507,21 +507,21 @@ SOC_ENUM("Limiter2 Release Rate (dB/ms)", sta32x_limiter2_release_rate_enum), * two different enum definitions; provide both */ SOC_SINGLE_TLV("Limiter1 Attack Threshold (AC Mode)", STA32X_L1ATRT, STA32X_LxA_SHIFT, - 16, 0, sta32x_limiter_ac_attack_tlv), + 15, 0, sta32x_limiter_ac_attack_tlv), SOC_SINGLE_TLV("Limiter2 Attack Threshold (AC Mode)", STA32X_L2ATRT, STA32X_LxA_SHIFT, - 16, 0, sta32x_limiter_ac_attack_tlv), + 15, 0, sta32x_limiter_ac_attack_tlv), SOC_SINGLE_TLV("Limiter1 Release Threshold (AC Mode)", STA32X_L1ATRT, STA32X_LxR_SHIFT, - 16, 0, sta32x_limiter_ac_release_tlv), + 15, 0, sta32x_limiter_ac_release_tlv), SOC_SINGLE_TLV("Limiter2 Release Threshold (AC Mode)", STA32X_L2ATRT, STA32X_LxR_SHIFT, - 16, 0, sta32x_limiter_ac_release_tlv), + 15, 0, sta32x_limiter_ac_release_tlv), SOC_SINGLE_TLV("Limiter1 Attack Threshold (DRC Mode)", STA32X_L1ATRT, STA32X_LxA_SHIFT, - 16, 0, sta32x_limiter_drc_attack_tlv), + 15, 0, sta32x_limiter_drc_attack_tlv), SOC_SINGLE_TLV("Limiter2 Attack Threshold (DRC Mode)", STA32X_L2ATRT, STA32X_LxA_SHIFT, - 16, 0, sta32x_limiter_drc_attack_tlv), + 15, 0, sta32x_limiter_drc_attack_tlv), SOC_SINGLE_TLV("Limiter1 Release Threshold (DRC Mode)", STA32X_L1ATRT, STA32X_LxR_SHIFT, - 16, 0, sta32x_limiter_drc_release_tlv), + 15, 0, sta32x_limiter_drc_release_tlv), SOC_SINGLE_TLV("Limiter2 Release Threshold (DRC Mode)", STA32X_L2ATRT, STA32X_LxR_SHIFT, - 16, 0, sta32x_limiter_drc_release_tlv), + 15, 0, sta32x_limiter_drc_release_tlv), BIQUAD_COEFS("Ch1 - Biquad 1", 0), BIQUAD_COEFS("Ch1 - Biquad 2", 5), From 77623752c156763d7a80414afe5b769ba4532bd3 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sun, 18 Oct 2026 15:47:45 +0900 Subject: [PATCH 885/924] ASoC: codecs: sta350: Fix definition of limiter threshold controls The limiter attack and release thresholds are the two 4-bit halves of one register, so their maximum is 15, not 16. With 16 the controls on bits 3:0 also write bit 4, which belongs to the other threshold. Fixes: 2439ea1f0f8f ("ASoC: sta350: Add codec driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/d65a21665a1e0d63dec174dd3691bd97764b8ae0.1792135145.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/sta350.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/sta350.c b/sound/soc/codecs/sta350.c index d2b6f73d5e10..a9ed583b0c46 100644 --- a/sound/soc/codecs/sta350.c +++ b/sound/soc/codecs/sta350.c @@ -502,28 +502,28 @@ SOC_ENUM("Limiter2 Release Rate (dB/ms)", sta350_limiter2_release_rate_enum), */ SOC_SINGLE_TLV("Limiter1 Attack Threshold (AC Mode)", STA350_L1ATRT, STA350_LxA_SHIFT, - 16, 0, sta350_limiter_ac_attack_tlv), + 15, 0, sta350_limiter_ac_attack_tlv), SOC_SINGLE_TLV("Limiter2 Attack Threshold (AC Mode)", STA350_L2ATRT, STA350_LxA_SHIFT, - 16, 0, sta350_limiter_ac_attack_tlv), + 15, 0, sta350_limiter_ac_attack_tlv), SOC_SINGLE_TLV("Limiter1 Release Threshold (AC Mode)", STA350_L1ATRT, STA350_LxR_SHIFT, - 16, 0, sta350_limiter_ac_release_tlv), + 15, 0, sta350_limiter_ac_release_tlv), SOC_SINGLE_TLV("Limiter2 Release Threshold (AC Mode)", STA350_L2ATRT, STA350_LxR_SHIFT, - 16, 0, sta350_limiter_ac_release_tlv), + 15, 0, sta350_limiter_ac_release_tlv), SOC_SINGLE_TLV("Limiter1 Attack Threshold (DRC Mode)", STA350_L1ATRT, STA350_LxA_SHIFT, - 16, 0, sta350_limiter_drc_attack_tlv), + 15, 0, sta350_limiter_drc_attack_tlv), SOC_SINGLE_TLV("Limiter2 Attack Threshold (DRC Mode)", STA350_L2ATRT, STA350_LxA_SHIFT, - 16, 0, sta350_limiter_drc_attack_tlv), + 15, 0, sta350_limiter_drc_attack_tlv), SOC_SINGLE_TLV("Limiter1 Release Threshold (DRC Mode)", STA350_L1ATRT, STA350_LxR_SHIFT, - 16, 0, sta350_limiter_drc_release_tlv), + 15, 0, sta350_limiter_drc_release_tlv), SOC_SINGLE_TLV("Limiter2 Release Threshold (DRC Mode)", STA350_L2ATRT, STA350_LxR_SHIFT, - 16, 0, sta350_limiter_drc_release_tlv), + 15, 0, sta350_limiter_drc_release_tlv), BIQUAD_COEFS("Ch1 - Biquad 1", 0), BIQUAD_COEFS("Ch1 - Biquad 2", 5), From f28c09b7e62fb5ead65bdfab93012a86d64b6540 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sun, 18 Oct 2026 15:47:46 +0900 Subject: [PATCH 886/924] ASoC: tlv320aic32x4: Fix definition of TAS2505 HP Driver Gain Volume control The TAS2505 HP volume is bits 6:0 of page 1 register 22, not of register 16, which holds the HP driver mute (bit 6) and gain (bits 5:0). Fixes: 2169d6a0f072 ("ASoC: tlv320aic32x4: Fix TAS2505 volume controls") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/e50b88689e8efd353ee597a6692ab4db30aa63e7.1792135145.git.sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic32x4.c | 2 +- sound/soc/codecs/tlv320aic32x4.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index 7fec94768afb..2ee4a9d2abf2 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -1057,7 +1057,7 @@ static const struct snd_kcontrol_new aic32x4_tas2505_snd_controls[] = { SOC_ENUM("DAC Playback PowerTune Switch", l_ptm_enum), SOC_SINGLE_TLV("HP Driver Gain Volume", - AIC32X4_HPLGAIN, 0, 0x74, 1, tlv_tas_driver_gain), + TAS2505_HPVOL, 0, 0x74, 1, tlv_tas_driver_gain), SOC_SINGLE("HP DAC Playback Switch", AIC32X4_HPLGAIN, 6, 1, 1), SOC_SINGLE_TLV("Speaker Driver Playback Volume", diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h index cfab6f8ce5ac..d0c2e1002e99 100644 --- a/sound/soc/codecs/tlv320aic32x4.h +++ b/sound/soc/codecs/tlv320aic32x4.h @@ -94,6 +94,7 @@ int aic32x4_register_clocks(struct device *dev, const char *mclk_name); #define AIC32X4_LOLGAIN AIC32X4_REG(1, 18) #define AIC32X4_LORGAIN AIC32X4_REG(1, 19) #define AIC32X4_HEADSTART AIC32X4_REG(1, 20) +#define TAS2505_HPVOL AIC32X4_REG(1, 22) #define TAS2505_SPK AIC32X4_REG(1, 45) #define TAS2505_SPKVOL1 AIC32X4_REG(1, 46) #define TAS2505_SPKVOL2 AIC32X4_REG(1, 48) From 680be9f82d4977d3410c513d3bfe912d100f64fc Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sun, 18 Oct 2026 22:07:26 +0800 Subject: [PATCH 887/924] ALSA: pcm: Protect the unregister notifier traversal snd_pcm_free() traverses snd_pcm_notify_list and calls n_unregister without register_mutex, although snd_pcm_notify() takes that mutex to add and remove notifiers. PCM disconnect releases the mutex before the free callback runs, so it does not protect this later traversal. With no open OSS file, normal snd-pcm-oss removal can overlap PCM card teardown, for example when unbinding snd-dummy: PCM card teardown snd-pcm-oss unload ----------------- ------------------ snd_pcm_dev_disconnect() remove PCM under register_mutex unlock register_mutex snd_pcm_free() fetch notifier snd_pcm_notify(..., 1) unlink notifier under mutex free module storage read notifier->n_unregister invoke callback The free path can then read the notifier from released module storage or call its unloaded code. Hold register_mutex across the unregister traversal and its callbacks. Notifier removal must wait until both have finished. This also protects the traversal if a callback releases the last module reference: module exit cannot unlink its notifier until the free path releases the mutex. Drop the mutex before private_free and the remaining stream cleanup. Fixes: c461482c8072 ("[ALSA] Unregister device files at disconnection") Assisted-by: LLM Signed-off-by: Cen Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/pm-pmb-26d66390a25fec9fad6c-v6-5be251a3ac8832955a31-1@gmail.com --- sound/core/pcm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/core/pcm.c b/sound/core/pcm.c index c133f23418fe..35ece386d1a2 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -853,8 +853,10 @@ static int snd_pcm_free(struct snd_pcm *pcm) { if (!pcm) return 0; - if (!pcm->internal) - pcm_call_notify(pcm, n_unregister); + if (!pcm->internal) { + scoped_guard(mutex, ®ister_mutex) + pcm_call_notify(pcm, n_unregister); + } if (pcm->private_free) pcm->private_free(pcm); snd_pcm_lib_preallocate_free_for_all(pcm); From f0780cfa6acf72576464dc3b40431db389f46ee8 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sun, 18 Oct 2026 22:07:27 +0800 Subject: [PATCH 888/924] ALSA: oss: Keep emulation modules loaded until object teardown OSS proc entries and notifier callbacks can remain in use without an open OSS device file. Normal snd-pcm-oss removal can therefore delete an existing PCM's OSS proc entry while late HDA codec binding repeats snd_card_register() on the same live card: OSS PCM module unload Card registration --------------------- ----------------- snd_info_free_entry(entry) proc_remove(entry->p) clear entry->p snd_info_card_register() republish linked entry unlink and free entry The new proc node retains a pointer to freed storage. Mixer OSS removal can likewise release an adapter while a card notification still uses it. Hold a snd-pcm-oss module reference for each PCM registered with its notifier and a snd-mixer-oss reference for each card adapter. Release them at final unregister or FREE, after proc and adapter cleanup. Normal module unload can no longer initiate removal while these objects exist. Track PCM OSS registration with a separate bool registered. Unlike oss.reg, this state also covers unmapped PCMs and remains set across DISCONNECT until final unregister. BUILD-state and creation-error cleanup must not drop a reference when registration never acquired one. The PCM notifier's register_mutex serialization keeps the final put ahead of notifier removal and module teardown until dispatch has finished. For mixer OSS, serialize callback publication, dispatch and withdrawal in the core so module exit waits for callbacks, including one dropping the last reference, to return. Keep card enumeration in the OSS module, with card references released after dispatch drops the mutex. Ignore duplicate REGISTER notifications so a delayed registration cannot replace an adapter retained across DISCONNECT and leak its module pin. Partial HDA codec teardown can still remove an individual PCM while the card remains registered; its object-lifetime handling is separate. Fixes: 2471b6c80a70 ("ALSA: info: Register proc entries recursively, too") Suggested-by: Takashi Iwai Assisted-by: LLM Signed-off-by: Cen Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/pm-pmb-26d66390a25fec9fad6c-v6-5be251a3ac8832955a31-2@gmail.com --- include/sound/core.h | 3 ++- include/sound/pcm_oss.h | 1 + sound/core/init.c | 29 +++++++++++++++++++++-------- sound/core/oss/mixer_oss.c | 36 ++++++++++++++++++++---------------- sound/core/oss/pcm_oss.c | 12 ++++++++++++ 5 files changed, 56 insertions(+), 25 deletions(-) diff --git a/include/sound/core.h b/include/sound/core.h index 657b12e1e95b..d6584967fd49 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -301,7 +301,8 @@ int snd_card_locked(int card); #define SND_MIXER_OSS_NOTIFY_REGISTER 0 #define SND_MIXER_OSS_NOTIFY_DISCONNECT 1 #define SND_MIXER_OSS_NOTIFY_FREE 2 -extern int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int cmd); +void snd_mixer_oss_set_notify(int (*callback)(struct snd_card *card, int cmd)); +void snd_mixer_oss_notify(struct snd_card *card, int cmd); #endif int snd_card_new(struct device *parent, int idx, const char *xid, diff --git a/include/sound/pcm_oss.h b/include/sound/pcm_oss.h index c96f23c7755b..43f765300031 100644 --- a/include/sound/pcm_oss.h +++ b/include/sound/pcm_oss.h @@ -70,6 +70,7 @@ struct snd_pcm_oss_stream { struct snd_pcm_oss { int reg; unsigned int reg_mask; + bool registered; }; #endif /* __SOUND_PCM_OSS_H */ diff --git a/sound/core/init.c b/sound/core/init.c index 9deb5e920d0d..d518cb838777 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -82,8 +82,24 @@ static int module_slot_match(struct module *module, int idx) } #if IS_ENABLED(CONFIG_SND_MIXER_OSS) -int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int free_flag); -EXPORT_SYMBOL(snd_mixer_oss_notify_callback); +static DEFINE_MUTEX(mixer_oss_notify_mutex); +static int (*mixer_oss_notify_callback)(struct snd_card *card, int cmd); + +void snd_mixer_oss_notify(struct snd_card *card, int cmd) +{ + guard(mutex)(&mixer_oss_notify_mutex); + if (mixer_oss_notify_callback) + mixer_oss_notify_callback(card, cmd); +} +EXPORT_SYMBOL(snd_mixer_oss_notify); + +void snd_mixer_oss_set_notify(int (*callback)(struct snd_card *, int)) +{ + /* Wait for callbacks, including a final module_put(), to return. */ + guard(mutex)(&mixer_oss_notify_mutex); + mixer_oss_notify_callback = callback; +} +EXPORT_SYMBOL(snd_mixer_oss_set_notify); #endif static int check_empty_slot(struct module *module, int slot) @@ -508,8 +524,7 @@ void snd_card_disconnect(struct snd_card *card) /* at this point, they cannot respond to any calls except release() */ #if IS_ENABLED(CONFIG_SND_MIXER_OSS) - if (snd_mixer_oss_notify_callback) - snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_DISCONNECT); + snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_DISCONNECT); #endif /* notify all devices that we are disconnected */ @@ -567,8 +582,7 @@ static int snd_card_do_free(struct snd_card *card) card->releasing = true; #if IS_ENABLED(CONFIG_SND_MIXER_OSS) - if (snd_mixer_oss_notify_callback) - snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); + snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_FREE); #endif snd_device_free_all(card); kfree(card->components); @@ -912,8 +926,7 @@ int snd_card_register(struct snd_card *card) return err; #if IS_ENABLED(CONFIG_SND_MIXER_OSS) - if (snd_mixer_oss_notify_callback) - snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER); + snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_REGISTER); #endif return 0; } diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c index f1fa42e634fc..a51bc68473e6 100644 --- a/sound/core/oss/mixer_oss.c +++ b/sound/core/oss/mixer_oss.c @@ -1309,13 +1309,22 @@ static int snd_mixer_oss_free1(void *private) static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd) { struct snd_mixer_oss *mixer; + int err; if (cmd == SND_MIXER_OSS_NOTIFY_REGISTER) { - int idx, err; + int idx; + + /* Module loading and card registration can both notify us. */ + if (card->mixer_oss) + return 0; mixer = kzalloc_objs(*mixer, 2); if (mixer == NULL) return -ENOMEM; + if (!try_module_get(THIS_MODULE)) { + kfree(mixer); + return -ENODEV; + } mutex_init(&mixer->reg_mutex); err = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, card, 0, @@ -1325,6 +1334,7 @@ static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd) "unable to register OSS mixer device %i:%i\n", card->number, 0); kfree(mixer); + module_put(THIS_MODULE); return err; } mixer->oss_dev_alloc = 1; @@ -1358,7 +1368,10 @@ static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd) if (cmd == SND_MIXER_OSS_NOTIFY_DISCONNECT) return 0; snd_mixer_oss_proc_done(mixer); - return snd_mixer_oss_free1(mixer); + err = snd_mixer_oss_free1(mixer); + if (!err) + module_put(THIS_MODULE); + return err; } return 0; } @@ -1367,12 +1380,12 @@ static int __init alsa_mixer_oss_init(void) { struct snd_card *card; int idx; - - snd_mixer_oss_notify_callback = snd_mixer_oss_notify_handler; + + snd_mixer_oss_set_notify(snd_mixer_oss_notify_handler); for (idx = 0; idx < SNDRV_CARDS; idx++) { card = snd_card_ref(idx); if (card) { - snd_mixer_oss_notify_handler(card, SND_MIXER_OSS_NOTIFY_REGISTER); + snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_REGISTER); snd_card_unref(card); } } @@ -1381,17 +1394,8 @@ static int __init alsa_mixer_oss_init(void) static void __exit alsa_mixer_oss_exit(void) { - struct snd_card *card; - int idx; - - snd_mixer_oss_notify_callback = NULL; - for (idx = 0; idx < SNDRV_CARDS; idx++) { - card = snd_card_ref(idx); - if (card) { - snd_mixer_oss_notify_handler(card, SND_MIXER_OSS_NOTIFY_FREE); - snd_card_unref(card); - } - } + /* Live adapters hold module references and prevent normal unload. */ + snd_mixer_oss_set_notify(NULL); } module_init(alsa_mixer_oss_init) diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index bd19328dca9e..e8df27d09b97 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -3151,6 +3151,12 @@ static void register_oss_dsp(struct snd_pcm *pcm, int index) static int snd_pcm_oss_register_minor(struct snd_pcm *pcm) { + if (pcm->oss.registered) + return 0; + if (!try_module_get(THIS_MODULE)) + return -ENODEV; + pcm->oss.registered = true; + pcm->oss.reg = 0; if (dsp_map[pcm->card->number] == (int)pcm->device) { char name[128]; @@ -3205,8 +3211,14 @@ static int snd_pcm_oss_disconnect_minor(struct snd_pcm *pcm) static int snd_pcm_oss_unregister_minor(struct snd_pcm *pcm) { + /* BUILD-state and creation-error cleanup need not have registered us. */ + if (!pcm->oss.registered) + return 0; + snd_pcm_oss_disconnect_minor(pcm); snd_pcm_oss_proc_done(pcm); + pcm->oss.registered = false; + module_put(THIS_MODULE); return 0; } From 40409fa23d32c8ed51e832031daa6decd9d6fd11 Mon Sep 17 00:00:00 2001 From: George Christidis Date: Sun, 18 Oct 2026 18:56:31 +0200 Subject: [PATCH 889/924] ALSA: hda/realtek: Fix headset mic on ASUS Zenbook 14 UX3402VA The UX3402VA (1043:16a3) BIOS leaves pin 0x19 unconfigured, so the headset mic does not work. Use ALC294_FIXUP_ASUS_SPI_HEADSET_MIC like the UX3405MA, it sets up the mic and keeps the CS35L41 speaker fixup. Tested on a UX3402VA: headset mic, internal mic and speakers work. Fixes: 07058dceb038 ("ALSA: hda/realtek: Splitting the UX3402 into two separate models") Cc: stable@vger.kernel.org Signed-off-by: George Christidis Link: https://patch.msgid.link/20261018-for-linus-v1-1-4d8e9ff27d89@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index de251fc46447..e3b3e88dbf8a 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8148,7 +8148,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1663, "ASUS GU603ZI/ZJ/ZQ/ZU/ZV", ALC285_FIXUP_ASUS_HEADSET_MIC), SND_PCI_QUIRK(0x1043, 0x1664, "ASUS ExpertBook PM3406CHA", ALC256_FIXUP_ASUS_PM3406CHA), SND_PCI_QUIRK(0x1043, 0x1683, "ASUS UM3402YAR", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC294_FIXUP_ASUS_SPI_HEADSET_MIC), SND_PCI_QUIRK(0x1043, 0x16b2, "ASUS GU603", ALC289_FIXUP_ASUS_GA401), SND_PCI_QUIRK(0x1043, 0x16d3, "ASUS UX5304VA", ALC245_FIXUP_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC), From 1869c47e5d59c6940823a58d58b647645cb69017 Mon Sep 17 00:00:00 2001 From: Thomas Nemer Date: Sun, 18 Oct 2026 13:15:19 +0200 Subject: [PATCH 890/924] ALSA: usb-audio: Fix missing sample-rate control for PreSonus AudioBox USB The fixed audio formats for the PreSonus AudioBox USB declare .attributes = 0, dropping UAC_EP_CS_ATTR_SAMPLE_RATE that the device advertises in its class-specific endpoint descriptors (EP_GENERAL bmAttributes 0x01 on both EP 0x01 and EP 0x82). As a result set_sample_rate_v1() bails out and the kernel never sends the UAC1 SET_CUR SAMPLING_FREQ request, leaving the device on its power-on default clock. Playback and capture then run distorted whenever the stream rate differs from that default. The device keeps the last rate set by a host as long as it stays powered, which made the regression look intermittent (e.g. it disappears after the device was initialized by a host running a kernel without this quirk entry). Restore the sample-rate attribute on both streams. Fixes: 34fe4a9df247 ("ALSA: usb-audio: Add quirk for PreSonus AudioBox USB") Cc: stable@vger.kernel.org Signed-off-by: Thomas Nemer Link: https://patch.msgid.link/20261018111519.191844-1-thomas.nemer@fortytwo.fr Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 2da8cbe3e6de..6343a50fb273 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2911,7 +2911,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .iface = 2, .altsetting = 1, .altset_idx = 1, - .attributes = 0, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, .endpoint = 0x01, .ep_attr = USB_ENDPOINT_XFER_ISOC, .rates = SNDRV_PCM_RATE_44100 | @@ -2932,7 +2932,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .iface = 3, .altsetting = 1, .altset_idx = 1, - .attributes = 0, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, .endpoint = 0x82, .ep_attr = USB_ENDPOINT_XFER_ISOC, .rates = SNDRV_PCM_RATE_44100 | From 4c0d77c469398ba4a7e690b463818b66c430af59 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 18 Oct 2026 14:41:54 +0200 Subject: [PATCH 891/924] ALSA: usb-audio: Simplify usb_audio_make_shortname() Copy the preset and quirk product names directly and remove the local char s pointer. Return after trimming the device product name and use the USB device ID as a fallback. No functional change. Signed-off-by: Thorsten Blum Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261018124155.2081-3-blum@kernel.org --- sound/usb/card.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/sound/usb/card.c b/sound/usb/card.c index 9307da95efbe..8a62e8e1c6fc 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -618,30 +618,24 @@ static void usb_audio_make_shortname(struct usb_device *dev, { struct snd_card *card = chip->card; const struct usb_audio_device_name *preset; - const char *s = NULL; + *card->shortname = 0; preset = lookup_device_name(chip->usb_id); if (preset && preset->product_name) - s = preset->product_name; + strscpy(card->shortname, preset->product_name); else if (quirk && quirk->product_name) - s = quirk->product_name; - if (s && *s) { - strscpy(card->shortname, s, sizeof(card->shortname)); + strscpy(card->shortname, quirk->product_name); + if (*card->shortname) + return; + + if (dev->product && *dev->product) { + strscpy(card->shortname, dev->product); + strim(card->shortname); return; } - /* retrieve the device string as shortname */ - if (dev->product && *dev->product) { - strscpy(card->shortname, dev->product); - } else { - /* no name available from anywhere, so use ID */ - scnprintf(card->shortname, sizeof(card->shortname), - "USB Device %#04x:%#04x", - USB_ID_VENDOR(chip->usb_id), - USB_ID_PRODUCT(chip->usb_id)); - } - - strim(card->shortname); + scnprintf(card->shortname, sizeof(card->shortname), "USB Device %#04x:%#04x", + USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id)); } static void usb_audio_make_longname(struct usb_device *dev, From e45d0fad00bacca06a55dee30197367916a85b41 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 18 Oct 2026 14:41:55 +0200 Subject: [PATCH 892/924] ALSA: usb-audio: Simplify usb_audio_make_longname() To prepare for removing strlcat() from the kernel [1], track the output length and replace strlcat() with scnprintf() and strscpy(). Replace the speed switch statement with a lookup table. No functional change. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Thorsten Blum Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261018124155.2081-4-blum@kernel.org --- sound/usb/card.c | 71 ++++++++++++++++++------------------------------ 1 file changed, 27 insertions(+), 44 deletions(-) diff --git a/sound/usb/card.c b/sound/usb/card.c index 8a62e8e1c6fc..59979b793fe9 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -642,63 +642,46 @@ static void usb_audio_make_longname(struct usb_device *dev, struct snd_usb_audio *chip, const struct snd_usb_audio_quirk *quirk) { + static const char * const speed_suffixes[] = { + [USB_SPEED_LOW] = ", low speed", + [USB_SPEED_FULL] = ", full speed", + [USB_SPEED_HIGH] = ", high speed", + [USB_SPEED_SUPER] = ", super speed", + [USB_SPEED_SUPER_PLUS] = ", super speed plus", + }; struct snd_card *card = chip->card; const struct usb_audio_device_name *preset; - const char *s = NULL; - int len; + enum usb_device_speed speed = snd_usb_get_speed(dev); + int len, ret; preset = lookup_device_name(chip->usb_id); /* shortcut - if any pre-defined string is given, use it */ - if (preset && preset->profile_name) - s = preset->profile_name; - if (s && *s) { - strscpy(card->longname, s, sizeof(card->longname)); + if (preset && preset->profile_name && *preset->profile_name) { + strscpy(card->longname, preset->profile_name); return; } - if (preset && preset->vendor_name) - s = preset->vendor_name; - else if (quirk && quirk->vendor_name) - s = quirk->vendor_name; *card->longname = 0; - if (s && *s) - strscpy(card->longname, s); - else if (dev->manufacturer && *dev->manufacturer) - strscpy(card->longname, dev->manufacturer); + if (preset && preset->vendor_name) + strscpy(card->longname, preset->vendor_name); + else if (quirk && quirk->vendor_name) + strscpy(card->longname, quirk->vendor_name); + if (!*card->longname) + strscpy(card->longname, dev->manufacturer ?: ""); - if (*card->longname) { - strim(card->longname); - if (*card->longname) - strlcat(card->longname, " ", sizeof(card->longname)); - } + strim(card->longname); + len = strlen(card->longname); + len += scnprintf(card->longname + len, sizeof(card->longname) - len, + "%s%s at ", len ? " " : "", card->shortname); - strlcat(card->longname, card->shortname, sizeof(card->longname)); + ret = usb_make_path(dev, card->longname + len, sizeof(card->longname) - len); + if (ret < 0) + return; + len += ret; - len = strlcat(card->longname, " at ", sizeof(card->longname)); - - if (len < sizeof(card->longname)) - usb_make_path(dev, card->longname + len, sizeof(card->longname) - len); - - switch (snd_usb_get_speed(dev)) { - case USB_SPEED_LOW: - strlcat(card->longname, ", low speed", sizeof(card->longname)); - break; - case USB_SPEED_FULL: - strlcat(card->longname, ", full speed", sizeof(card->longname)); - break; - case USB_SPEED_HIGH: - strlcat(card->longname, ", high speed", sizeof(card->longname)); - break; - case USB_SPEED_SUPER: - strlcat(card->longname, ", super speed", sizeof(card->longname)); - break; - case USB_SPEED_SUPER_PLUS: - strlcat(card->longname, ", super speed plus", sizeof(card->longname)); - break; - default: - break; - } + if (speed < ARRAY_SIZE(speed_suffixes) && speed_suffixes[speed]) + strscpy(card->longname + len, speed_suffixes[speed], sizeof(card->longname) - len); } static void snd_usb_init_quirk_flags(int idx, struct snd_usb_audio *chip) From 069003afab42b5a9394faa6985a3d76bd4700e72 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 19 Oct 2026 17:04:42 +0800 Subject: [PATCH 893/924] ALSA: pcmtest: Pin the module while pattern files are open The fill_pattern_fops table must remain alive until the final release of each pattern debugfs file. It has no .owner, so the fops_get() call in full_proxy_open_regular() does not take a PCMTest module reference. With snd-pcmtest loaded as a module, a user with permission to open a fill_patternN file and unload the module can retain the descriptor across normal unload, provided no PCM file or other module reference prevents it. The following ordering is possible: Pattern file user Module unload ----------------- ------------- open(fill_patternN) full_proxy_open_regular() fops_get(fill_pattern_fops) owner == NULL: no module pin keep the descriptor open mod_exit() clear_debug_files() debugfs_remove_recursive() drain active operations return from mod_exit() free_module() free the module image close(fd) full_proxy_release() read real_fops->release -> UAF fops_put(real_fops) read real_fops->owner access freed module memory Even though the table has no release callback, reading those fields is a use-after-free and can fault. The retained dentry keeps the proxy's bookkeeping alive, but does not retain the module storage. Set .owner to THIS_MODULE so the existing fops_get()/fops_put() pair pins PCMTest until final release has finished accessing the table. Normal module unload is then blocked while any pattern file is open. Fixes: 315a3d57c64c ("ALSA: Implement the new Virtual PCM Test Driver") Signed-off-by: Cen Zhang Assisted-by: LLM Link: https://patch.msgid.link/pm-pmb-1ce354e709dbab28e563-v2-6b2ea0aa16ec39442f6d@gmail.com Signed-off-by: Takashi Iwai --- sound/drivers/pcmtest.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c index 393f132a438b..d1b0dff56cd9 100644 --- a/sound/drivers/pcmtest.c +++ b/sound/drivers/pcmtest.c @@ -633,6 +633,7 @@ static ssize_t pattern_read(struct file *file, char __user *u_buff, size_t len, } static const struct file_operations fill_pattern_fops = { + .owner = THIS_MODULE, .read = pattern_read, .write = pattern_write, }; From d5dd05c1f72a95d8ab06828d7272a185ad2b9185 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 19 Oct 2026 18:01:46 +0800 Subject: [PATCH 894/924] ALSA: pcm: Remove channel-map controls before freeing substreams A late HDA codec bind on an already registered card builds new PCMs before registering them. add_std_chmaps() publishes generic channel-map controls during this BUILD stage. If an earlier chmap succeeds and a later allocation fails, probe cleanup frees the BUILD PCM while the card's control device remains accessible. These generic controls are added directly by snd_pcm_add_chmap_ctls(); snd_hda_ctls_clear() only removes codec-tracked mixer controls. BUILD devices also skip dev_disconnect. snd_pcm_free_stream() then frees the substreams before free_chmap() withdraws their generic control: Late HDA codec bind / rollback Control GET on the live card ------------------------------ ---------------------------- hda_codec_driver_probe() snd_hda_codec_build_pcms() PCM is BUILD snd_hda_codec_build_controls() add_std_chmaps() publish an earlier chmap later allocation: -ENOMEM snd_hda_codec_cleanup_for_unbind() snd_hda_ctls_clear() generic chmap remains codec_release_pcms() snd_device_free() BUILD: skip dev_disconnect snd_pcm_free_stream() kfree(substream) snd_ctl_elem_read() down_read(controls_rwsem) pcm_chmap_ctl_get() walk freed substream free_chmap() snd_ctl_remove() wait for GET up_read(controls_rwsem) The control read lock cannot protect a substream already freed before control removal starts. The failure above occurs before snd_card_register(), so the PCM has not transitioned out of BUILD. Move free_chmap() before the substream free loop. Its snd_ctl_remove() withdraws the control and drains readers under controls_rwsem before any substream is released. Normal registered-device disconnect already removes the control, so the later call remains harmless on that path. Fixes: 2d3391ec0ecc ("ALSA: PCM: channel mapping API implementation") Assisted-by: LLM Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-4a207d9ef3a3597a74a4-v4-80b0113669b1a62fbbe9@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 35ece386d1a2..1e4e0ecb3dbd 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -819,6 +819,9 @@ static void snd_pcm_free_stream(struct snd_pcm_str * pstr) /* free all proc files under the stream */ snd_pcm_stream_proc_done(pstr); + /* Drain channel-map callbacks before freeing the substreams. */ + free_chmap(pstr); + substream = pstr->substream; while (substream) { substream_next = substream->next; @@ -833,7 +836,6 @@ static void snd_pcm_free_stream(struct snd_pcm_str * pstr) kfree(setup); } #endif - free_chmap(pstr); if (pstr->substream_count) put_device(pstr->dev); } From e88f4749c02fba11c62e880c29d35d254b7e69d0 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 19 Oct 2026 12:23:38 +0200 Subject: [PATCH 895/924] ALSA: usb-audio: Avoid applying boot quirk for Behringer UMC204HD There have been reports about a regression with the recent boot quirk for Behringer devices; it broke UMC204HD models(1397:0508 and 1397:0509). The quirk was to just run usb_get_descriptor(), so I expected it rather harmful, but in this case, it doesn't look so, resulting in an error like 'failed to read device descriptor: -110' that leads to a fatal probe error. As a quick workaround, skip the quirk for those UMC204HD models. Fixes: b5c4823cf3e4 ("ALSA: usb-audio: Apply boot quirk for Behringer models generically") Reported-by: Will Bush Closes: https://lore.kernel.org/45162613-2802-401c-84c9-742f9c8c68e6@app.fastmail.com Closes: https://bugzilla.suse.com/show_bug.cgi?id=1285896 Link: https://patch.msgid.link/20261019102347.2425406-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 4fadb1fdcdea..4ac4aef47dd7 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -1790,8 +1790,15 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, } /* Behringer devices may need explicit device descriptor read at boot */ - if (USB_ID_VENDOR(id) == 0x1397) + if (USB_ID_VENDOR(id) == 0x1397) { + /* the quirk seems breaking on some Behringer devices */ + switch (id) { + case USB_ID(0x1397, 0x0508): /* UMC204HD */ + case USB_ID(0x1397, 0x0509): /* UMC204HD */ + return 0; + } return snd_usb_cm1a_boot_quirk(dev); + } return 0; } From 8b479b265301e97bfbec0ef25de1c1f7eb4d2f74 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 19 Oct 2026 18:30:54 +0800 Subject: [PATCH 896/924] ALSA: jack: Pin the module while debugfs files are open With CONFIG_SND=m and CONFIG_SND_JACK_INJECTION_DEBUG enabled, an in-tree HDA jack creates these debugfs files through snd_jack_new() and snd_jack_add_debugfs(). Their file_operations live in snd, but none sets .owner. full_proxy_open_regular() therefore takes no snd module reference when it calls fops_get(real_fops). A debugfs descriptor can outlive removal of its HDA card. Once the card and dependent modules, including the OSS emulation modules, have been removed, snd can be unloaded while that descriptor is still open: Descriptor holder Card/module teardown ----------------- -------------------- open jack debugfs file full_proxy_open_regular() fops_get(real_fops) owner == NULL: no snd pin keep descriptor open remove HDA card snd_card_disconnect() snd_jack_dev_disconnect() remove debugfs nodes snd_jack_dev_free() unload dependent modules rmmod snd alsa_sound_exit() free_module(): unmap snd close descriptor full_proxy_release() read real_fops->release fops_put(real_fops) read real_fops->owner debugfs_remove() drains active operations, but does not wait for idle open descriptors. Release deliberately bypasses removal protection and reads the real table even when no release callback is installed. The last close can therefore access unloaded snd storage. Set .owner to THIS_MODULE in all six tables. The existing proxy fops_get()/fops_put() pair then keeps snd loaded through the final table access on close. Fixes: 2d670ea2bd53 ("ALSA: jack: implement software jack injection via debugfs") Assisted-by: LLM Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-431ddb947e1a13b95d3a-v3-02d37dd7b4eb4774e100@gmail.com Signed-off-by: Takashi Iwai --- sound/core/jack.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/core/jack.c b/sound/core/jack.c index a81a64b3bfe7..af2dc2cfbe19 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -324,6 +324,7 @@ static ssize_t jack_type_read(struct file *file, } static const struct file_operations jack_type_fops = { + .owner = THIS_MODULE, .open = simple_open, .read = jack_type_read, .llseek = default_llseek, @@ -331,6 +332,7 @@ static const struct file_operations jack_type_fops = { #endif static const struct file_operations sw_inject_enable_fops = { + .owner = THIS_MODULE, .open = simple_open, .read = sw_inject_enable_read, .write = sw_inject_enable_write, @@ -338,24 +340,28 @@ static const struct file_operations sw_inject_enable_fops = { }; static const struct file_operations jackin_inject_fops = { + .owner = THIS_MODULE, .open = simple_open, .write = jackin_inject_write, .llseek = default_llseek, }; static const struct file_operations jack_kctl_id_fops = { + .owner = THIS_MODULE, .open = simple_open, .read = jack_kctl_id_read, .llseek = default_llseek, }; static const struct file_operations jack_kctl_mask_bits_fops = { + .owner = THIS_MODULE, .open = simple_open, .read = jack_kctl_mask_bits_read, .llseek = default_llseek, }; static const struct file_operations jack_kctl_status_fops = { + .owner = THIS_MODULE, .open = simple_open, .read = jack_kctl_status_read, .llseek = default_llseek, From bb4834d5242c4829c0d8467ee68eb25a9bafc38d Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 19 Oct 2026 18:29:33 +0800 Subject: [PATCH 897/924] ALSA: pcm: Protect channel-map reads against runtime detach Channel-map controls remain accessible between PCM opens. In-tree HDA code publishes the generic controls through add_std_chmaps(), so a SNDRV_CTL_IOCTL_ELEM_READ can overlap the final close of the same PCM substream. pcm_chmap_ctl_get() checks substream->runtime and later reads its channel count. Final close clears and frees that runtime under pcm->open_mutex, while the control callback holds only controls_rwsem: Control GET Final PCM close ----------- --------------- snd_ctl_elem_read() down_read(controls_rwsem) pcm_chmap_ctl_get() check substream->runtime lock(pcm->open_mutex) snd_pcm_release_substream() snd_pcm_detach_substream() substream->runtime = NULL kfree(runtime) unlock(pcm->open_mutex) substream->runtime->channels NULL or freed-pointer access up_read(controls_rwsem) Reloading the cleared field can fault through NULL, while retaining the old pointer can access freed storage. controls_rwsem keeps the control alive, but does not serialize the matching PCM close. Copy the channel count under open_mutex and use that scalar for the map lookup. Use trylock with -EBUSY because snd_pcm_dev_disconnect() takes open_mutex before removing controls under controls_rwsem; blocking in the reverse order from GET could deadlock. Fixes: 2d3391ec0ecc ("ALSA: PCM: channel mapping API implementation") Assisted-by: LLM Signed-off-by: Cen Zhang Link: https://patch.msgid.link/pm-pmb-0b5d6bc7124d0b921f2c-v4-b9738bdeab8823999181@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_lib.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index e8f6a9da4a4c..bb16738bb591 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -2500,6 +2500,7 @@ static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol, unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); struct snd_pcm_substream *substream; const struct snd_pcm_chmap_elem *map; + unsigned int channels; if (!info->chmap) return -EINVAL; @@ -2508,11 +2509,20 @@ static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol, return -ENODEV; memset(ucontrol->value.integer.value, 0, sizeof(long) * info->max_channels); - if (!substream->runtime) - return 0; /* no channels set */ + /* + * PCM disconnect takes open_mutex before removing controls. + * Use trylock to avoid a deadlock with controls_rwsem. + */ + scoped_cond_guard(mutex_try, return -EBUSY, + &info->pcm->open_mutex) { + if (!substream->runtime) + return 0; /* no channels set */ + channels = substream->runtime->channels; + } for (map = info->chmap; map->channels; map++) { int i; - if (map->channels == substream->runtime->channels && + + if (map->channels == channels && valid_chmap_channels(info, map->channels)) { for (i = 0; i < map->channels; i++) ucontrol->value.integer.value[i] = map->map[i]; From fc49516fbffd0cbbf2afded6743f81342f2a0cfd Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Sun, 18 Oct 2026 19:43:56 +0200 Subject: [PATCH 898/924] ASoC: aw88399: pass firmware name as argument Pass the ACF firmware filename as an argument to aw88399_request_firmware_file() instead of hardcoding AW88399_ACF_FILE. Both existing callers (ASoC and HDA) pass AW88399_ACF_FILE to preserve current behavior. Also add missing newlines to the log messages of the modified lines. This allows the HDA side codec driver to request per-model firmware files in the next patch. Tested-by: Xia Yun'an Signed-off-by: Marco Giunta Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/SN6PR19MB2303DB0BF41797A38EB04CBFFC992@SN6PR19MB2303.namprd19.prod.outlook.com --- include/sound/aw88399.h | 2 +- sound/hda/codecs/side-codecs/aw88399_hda.c | 2 +- sound/soc/codecs/aw88399-lib.c | 10 +++++----- sound/soc/codecs/aw88399.c | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h index 38d9bd6abd8b..d72c491ee866 100644 --- a/include/sound/aw88399.h +++ b/include/sound/aw88399.h @@ -617,7 +617,7 @@ void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel); void aw88399_hw_reset(struct aw88399 *aw88399); int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap); extern const struct regmap_config aw88399_remap_config; -int aw88399_request_firmware_file(struct aw88399 *aw88399); +int aw88399_request_firmware_file(struct aw88399 *aw88399, const char *fw_name); void aw88399_start(struct aw88399 *aw88399, bool sync_start); void aw88399_startup_work(struct work_struct *work); int aw88399_stop(struct aw_device *aw_dev); diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c index 69d113af8ae3..720f407bb90b 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda.c @@ -129,7 +129,7 @@ static int aw88399_hda_init(struct aw88399_hda *aw88399) if (core->aw_pa) aw88399_dev_set_channel(core, aw88399->channel); - ret = aw88399_request_firmware_file(core); + ret = aw88399_request_firmware_file(core, AW88399_ACF_FILE); if (ret) return ret; diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index 75a533c9b752..b4e8aae56805 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -1284,21 +1284,21 @@ static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg return 0; } -int aw88399_request_firmware_file(struct aw88399 *aw88399) +int aw88399_request_firmware_file(struct aw88399 *aw88399, const char *fw_name) { const struct firmware *cont __free(firmware) = NULL; int ret; aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; - ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); + ret = request_firmware(&cont, fw_name, aw88399->aw_pa->dev); if (ret) { - dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); + dev_err(aw88399->aw_pa->dev, "request [%s] failed!\n", fw_name); return ret; } dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", - AW88399_ACF_FILE, cont ? cont->size : 0); + fw_name, cont ? cont->size : 0); aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); @@ -1310,7 +1310,7 @@ int aw88399_request_firmware_file(struct aw88399 *aw88399) ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); if (ret) { - dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); + dev_err(aw88399->aw_pa->dev, "load [%s] failed!\n", fw_name); return ret; } diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c index e9dab5e7bae3..320dc1b87841 100644 --- a/sound/soc/codecs/aw88399.c +++ b/sound/soc/codecs/aw88399.c @@ -727,7 +727,7 @@ static int aw88399_codec_probe(struct snd_soc_component *component) INIT_DELAYED_WORK(&aw88399->start_work, aw88399_startup_work); - ret = aw88399_request_firmware_file(aw88399); + ret = aw88399_request_firmware_file(aw88399, AW88399_ACF_FILE); if (ret) dev_err(aw88399->aw_pa->dev, "%s failed\n", __func__); From 3e3a0bdf6a645cd6e5461d22ab6607303975606a Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Sun, 18 Oct 2026 19:43:57 +0200 Subject: [PATCH 899/924] ALSA: hda: aw88399: use SSID-specific firmware name Use the ACPI subsystem ID to construct per-model firmware filenames (e.g. "awinic/aw88399_acf_17aa3938.bin") when loading firmware on HDA systems. This makes the driver compatible with the linux-firmware repository's per-device naming convention. Also, since the ACPI SSID is now required for firmware loading, change the error handling for a missing SSID from silently succeeding to an early -ENODEV return in aw88399_hda_acpi_probe(). Tested-by: Xia Yun'an Signed-off-by: Marco Giunta Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/SN6PR19MB23030BA9E3636DBBBB8C6408FC992@SN6PR19MB2303.namprd19.prod.outlook.com --- sound/hda/codecs/side-codecs/aw88399_hda.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c index 720f407bb90b..93fd09558ac9 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "../generic.h" #include "hda_component.h" @@ -129,10 +130,17 @@ static int aw88399_hda_init(struct aw88399_hda *aw88399) if (core->aw_pa) aw88399_dev_set_channel(core, aw88399->channel); - ret = aw88399_request_firmware_file(core, AW88399_ACF_FILE); + char *fw_name __free(kfree) = + kasprintf(GFP_KERNEL, "awinic/aw88399_acf_%s.bin", aw88399->acpi_subsystem_id); + if (!fw_name) + return -ENOMEM; + string_lower(fw_name, fw_name); + ret = aw88399_request_firmware_file(core, fw_name); if (ret) return ret; + dev_info(dev, "Loaded firmware %s\n", fw_name); + aw88399->core = core; aw88399->aw_dev = core->aw_pa; @@ -194,8 +202,10 @@ static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) return -ENODEV; sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); - if (IS_ERR_OR_NULL(sub)) - return 0; + if (IS_ERR_OR_NULL(sub)) { + dev_err(aw88399->dev, "ACPI subsystem ID not available\n"); + return -ENODEV; + } aw88399->acpi_subsystem_id = devm_kstrdup(aw88399->dev, sub, GFP_KERNEL); kfree(sub); @@ -348,4 +358,4 @@ MODULE_DESCRIPTION("AW88399 HDA driver"); MODULE_AUTHOR("Yakov Till "); MODULE_AUTHOR("Marco Giunta "); MODULE_LICENSE("GPL"); -MODULE_FIRMWARE("aw88399_acf.bin"); +MODULE_FIRMWARE("awinic/aw88399_acf_*.bin"); From a7d4d780f34b9e43eefab6a7e6c9f4101f2c144e Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 19 Oct 2026 17:05:21 +0800 Subject: [PATCH 900/924] ALSA: aloop: Keep shared timer callbacks alive through close The shared sound-timer callback must finish using the selected PCM runtimes before a close can detach them. loopback_snd_timer_period_elapsed() saves the active substreams under cable->lock, then drops the lock before calling snd_pcm_period_elapsed() or snd_pcm_stop_xrun(). The sound-timer backend has no per-stream close drain, and free_cable() keeps the timer open when the paired endpoint remains attached. With both directions running on a shared sound timer, playback can be dropped and closed while a notification is pending. Capture keeps the timer alive, allowing this order: Timer callback Playback owner -------------- -------------- select playback under cable->lock unlock; delay notification DROP; HW_FREE -> OPEN close: snd_pcm_drop(); unlock free_cable(); peer stays alive snd_pcm_period_elapsed() lock stream; cache runtime snd_pcm_detach_substream() free runtime and its fasync read runtime->fasync The stream lock taken by snd_pcm_drop() cannot protect a notification that starts after it returns, and detach does not take it. The callback can therefore read runtime->fasync after the runtime has been freed, and an escaped notification can also reach a replacement runtime if the same substream slot is reopened before the notification resumes. Take the existing cable->stop_count reference under cable->lock when selecting the targets and release it after the PCM callbacks finish, including the XRUN path. free_cable() already removes the closing endpoint under that lock and waits for stop_count before returning, so runtime detach and slot reuse cannot overtake the notification. Fixes: 26c53379f98d ("ALSA: aloop: Support selection of snd_timer instead of jiffies") Signed-off-by: Cen Zhang Assisted-by: LLM Link: https://patch.msgid.link/pm-pmb-275c4399f517e09d7188-v2-2f64c1328529ef7a8fec@gmail.com Signed-off-by: Takashi Iwai --- sound/drivers/aloop.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index 0aaa64b189da..39710389f624 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -100,7 +100,7 @@ struct loopback_ops { struct loopback_cable { spinlock_t lock; struct loopback_pcm *streams[2]; - /* in-flight peer stops running outside cable->lock */ + /* in-flight PCM callbacks running outside cable->lock */ struct snd_refcount stop_count; struct snd_pcm_hardware hw; /* flags */ @@ -873,6 +873,8 @@ static void loopback_snd_timer_period_elapsed(struct loopback_cable *cable, valid_runtime = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ? dpcm_play->substream->runtime : dpcm_capt->substream->runtime; + /* Keep runtimes alive through the PCM callbacks. */ + snd_refcount_get(&cable->stop_count); /* resolution is only valid for SNDRV_TIMER_EVENT_TICK events */ if (event == SNDRV_TIMER_EVENT_TICK) { @@ -908,13 +910,16 @@ static void loopback_snd_timer_period_elapsed(struct loopback_cable *cable, snd_pcm_stop_xrun(substream_play); if (substream_capt) snd_pcm_stop_xrun(substream_capt); - return; + goto exit; } if (substream_play) snd_pcm_period_elapsed(substream_play); if (substream_capt) snd_pcm_period_elapsed(substream_capt); + +exit: + snd_refcount_put(&cable->stop_count); } static void loopback_snd_timer_function(struct snd_timer_instance *timeri, @@ -1174,7 +1179,7 @@ static void free_cable(struct snd_pcm_substream *substream) other_alive = cable->streams[!substream->stream]; } - /* Pair with the stop_count increment in loopback_check_format(). */ + /* Wait for PCM callbacks running outside cable->lock. */ snd_refcount_sync(&cable->stop_count); if (other_alive) return; From 762ef425cf0ac9c4b9e9e61e0da0950e16e6fbea Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 19 Oct 2026 17:06:13 +0800 Subject: [PATCH 901/924] ALSA: aloop: Keep the timer source alive until card release The timer_source string must remain valid while PCM opens can select and parse it. loopback_set_timer_source() allocates the string with parent-device devres, which can release it while the card is still registered. A successful nonempty proc replacement after snd_card_register() adds the string allocation after trigger_card_free. Unbinding the platform device while snd_aloop remains loaded releases devres in reverse order. An open for an unused cable can run after the string is freed and before the card-free action disconnects the card: Device unbind PCM open devres_release_all() free replacement string snd_pcm_open() snd_card_file_add() loopback_open() loopback_snd_timer_open() loopback_parse_timer_id() trigger_card_free() snd_card_free() snd_card_disconnect() The parser can then read freed memory. KASAN has reported a one-byte use-after-free read in strchr() from loopback_snd_timer_open(). The cable_lock mutex serializes explicit replacements with opens, but implicit devres release does not take it. The card and module references held by PCM open do not retain this separate devres allocation. Use kstrdup() and release the current string from card->private_free. PCM admission already retains the card until each file is removed, and card disconnect drains proc callbacks before final card release. This keeps the string alive across admitted PCM opens and preserves the cable_lock serialization of replacements. Install the callback before the initial allocation so probe failures also release the string. Fixes: b072e65aadd6 ("ALSA: aloop: Allocate resources with device-managed APIs") Signed-off-by: Cen Zhang Assisted-by: LLM Link: https://patch.msgid.link/pm-pmb-71a33a26d68356e3ade7-v1-108d86919c5c92fe1ea8@gmail.com Signed-off-by: Takashi Iwai --- sound/drivers/aloop.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index 39710389f624..d6a059ab3747 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -1902,13 +1902,10 @@ static int loopback_cable_proc_new(struct loopback *loopback, int cidx) static void loopback_set_timer_source(struct loopback *loopback, const char *value) { - if (loopback->timer_source) { - devm_kfree(loopback->card->dev, loopback->timer_source); - loopback->timer_source = NULL; - } + kfree(loopback->timer_source); + loopback->timer_source = NULL; if (value && *value) - loopback->timer_source = devm_kstrdup(loopback->card->dev, - value, GFP_KERNEL); + loopback->timer_source = kstrdup(value, GFP_KERNEL); } static void print_timer_source_info(struct snd_info_entry *entry, @@ -1939,6 +1936,13 @@ static int loopback_timer_source_proc_new(struct loopback *loopback) change_timer_source_info); } +static void loopback_private_free(struct snd_card *card) +{ + struct loopback *loopback = card->private_data; + + kfree(loopback->timer_source); +} + static int loopback_probe(struct platform_device *devptr) { struct snd_card *card; @@ -1964,6 +1968,7 @@ static int loopback_probe(struct platform_device *devptr) pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; loopback->card = card; + card->private_free = loopback_private_free; loopback_set_timer_source(loopback, timer_source[dev]); mutex_init(&loopback->cable_lock); From a4ef328ce3b3ab1bf63c6350e890f13b9b25e3f1 Mon Sep 17 00:00:00 2001 From: Moroz Tymofii Date: Mon, 19 Oct 2026 19:47:25 +0300 Subject: [PATCH 902/924] ALSA: hda/realtek: Enable mute LED on HP Pavilion 15-eg2xxx The HP Pavilion 15-eg2xxx (103c:89f6) uses GPIO bit 4 for its speaker mute LED. Enable the existing ALC287 GPIO LED fixup for this system. Tested on Linux 7.3-rc7 without an hda_model override. The F5 LED follows speaker mute/unmute correctly, and audio playback works normally. Signed-off-by: Moroz Tymofii Link: https://patch.msgid.link/20261019164725.10666-1-moroztymofii@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index e3b3e88dbf8a..2e99088eb7a8 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7829,6 +7829,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x89d3, "HP EliteBook 645 G9 (MB 89D2)", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x89da, "HP Spectre x360 14t-ea100", ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX), SND_PCI_QUIRK(0x103c, 0x89e7, "HP Elite x2 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x89f6, "HP Pavilion 15-eg2xxx", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8a05, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8a06, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8a0e, "HP Pavilion Laptop 15-eh2xxx", ALC287_FIXUP_HP_GPIO_LED), From 91efff289f4108d7b8d1252371353f10c15c6b13 Mon Sep 17 00:00:00 2001 From: Aaron Kling Date: Tue, 20 Oct 2026 03:18:14 -0500 Subject: [PATCH 903/924] ALSA: hda/hdmi: Enable HBR capability Tegra HDA does not set the HBR capability bit, however every SoC since Tegra114 does support HBR. Override the hbr setup function for these SoCs to unconditionally enable the feature. Since Tegra30 does not support HBR, this override will not be applied. Signed-off-by: Aaron Kling Link: https://patch.msgid.link/20261020-tegra-hda-hbr-v2-1-6968017f585b@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/hdmi/tegrahdmi.c | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/sound/hda/codecs/hdmi/tegrahdmi.c b/sound/hda/codecs/hdmi/tegrahdmi.c index ebb6410a4831..171d1ee154d9 100644 --- a/sound/hda/codecs/hdmi/tegrahdmi.c +++ b/sound/hda/codecs/hdmi/tegrahdmi.c @@ -227,6 +227,37 @@ static int nvhdmi_chmap_validate(struct hdac_chmap *chmap, return 0; } +static int tegrahdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid, + int dev_id, bool hbr) +{ + int pinctl, new_pinctl; + + snd_hda_set_dev_select(codec, pin_nid, dev_id); + pinctl = snd_hda_codec_read(codec, pin_nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + + if (pinctl < 0) + return hbr ? -EINVAL : 0; + + new_pinctl = pinctl & ~AC_PINCTL_EPT; + if (hbr) + new_pinctl |= AC_PINCTL_EPT_HBR; + else + new_pinctl |= AC_PINCTL_EPT_NATIVE; + + codec_dbg(codec, "%s: NID=0x%x, %spinctl=0x%x\n", + __func__, pin_nid, + pinctl == new_pinctl ? "" : "new-", + new_pinctl); + + if (pinctl != new_pinctl) + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + new_pinctl); + + return 0; +} + static int tegra_hdmi_init(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; @@ -255,6 +286,10 @@ static int tegra_hdmi_init(struct hda_codec *codec) spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate; spec->nv_dp_workaround = true; + // Tegra 3 does not support HBR + if (codec->core.vendor_id != 0x10de0020) + spec->ops.pin_hbr_setup = tegrahdmi_pin_hbr_setup; + return 0; } From 20b9cae98ddcff87828d033e35b31f9e6a14dd6a Mon Sep 17 00:00:00 2001 From: Thomas Nemer Date: Mon, 19 Oct 2026 22:49:44 +0200 Subject: [PATCH 904/924] ALSA: usb-audio: Skip sample-rate read-back on PreSonus AudioBox USB The AudioBox USB stalls the UAC1 GET_CUR SAMPLING_FREQ request on both streaming endpoints. After the sample rate is set, the read-back verification in set_sample_rate_v1() fails and logs usb 1-3.1.2: 2:1: cannot get freq at ep 0x1 usb 1-3.1.2: 3:1: cannot get freq at ep 0x82 on each endpoint configuration until the sample_rate_read_error counter gives up. Set QUIRK_FLAG_GET_SAMPLE_RATE to skip the read-back on this device. Fixes: 1869c47e5d59 ("ALSA: usb-audio: Fix missing sample-rate control for PreSonus AudioBox USB") Cc: stable@vger.kernel.org Signed-off-by: Thomas Nemer Link: https://patch.msgid.link/20261019204944.28302-1-thomas.nemer@fortytwo.fr Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 4ac4aef47dd7..43eea724779a 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2731,6 +2731,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), DEVICE_FLG(0x1901, 0x0191, /* GE B850V3 CP2114 audio interface */ QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x194f, 0x0301, /* PreSonus AudioBox USB */ + QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x19f7, 0x0003, /* RODE NT-USB */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x19f7, 0x0035, /* RODE NT-USB+ */ From c739a2a548c84b515bb6a04ade8dfe7d1919961e Mon Sep 17 00:00:00 2001 From: Nika Sommer Date: Mon, 19 Oct 2026 20:32:26 +0200 Subject: [PATCH 905/924] ALSA: hda/realtek: Enable jack detection on Minisforum AI X1 The firmware of the Minisforum AI X1 leaves the headphone jack detector reset bit asserted on its ALC245 codec. As a result, pin sense on NID 0x21 always reports the jack as absent. Enable the existing fixup ALC245_FIXUP_MINISFORUM_JACK_DETECT from commit 6bd8a57c044f ("ALSA: hda/realtek: Enable jack detection on Minisforum AI X1 Pro") to the Minisforum AI X1, which requires the same workaround. Tested on a Minisforum AI X1 (AMD Ryzen 7 255), using NixOS 26.11.20261014.55bfb4d and kernel 7.2.10. Signed-off-by: Nika Sommer Link: https://patch.msgid.link/20261019183226.64985-1-nika.sommer@mailbox.org Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 2e99088eb7a8..bf53fdd3f6a1 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8703,6 +8703,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro", ALC245_FIXUP_MINISFORUM_JACK_DETECT), + SND_PCI_QUIRK(0x1f4c, 0xb022, "Minisforum AI X1", ALC245_FIXUP_MINISFORUM_JACK_DETECT), SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), From fbc74ca9fbf7e0b8f6872ccac62f9f4a1f3b5e53 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 19 Oct 2026 17:45:30 +0300 Subject: [PATCH 906/924] ALSA: compress: fix buffer leak on failed SET_PARAMS When the driver has neither ops->copy nor a preallocated dma_buffer_p, snd_compr_allocate_buffer() kmallocs runtime->buffer. If ops->set_params() then rejects the parameters, SET_PARAMS fails, the stream stays in the OPEN state and keeps the buffer. The next SET_PARAMS allocates a new buffer and overwrites the pointer, so repeated SET_PARAMS calls with parameters that the driver rejects leak one buffer per call. Release the framework-owned buffer when ops->set_params() fails on an OPEN stream, so that a failed SET_PARAMS leaves no buffer behind and the buffer is allocated only once per successful setup. Use the same ownership check as snd_compr_free(). Fixes: b21c60a4edd2 ("ALSA: core: add support for compress_offload") Cc: stable@vger.kernel.org Signed-off-by: Peter Ujfalusi Link: https://patch.msgid.link/20261019144533.30665-2-peter.ujfalusi@linux.intel.com Signed-off-by: Takashi Iwai --- sound/core/compress_offload.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 5c0db178d635..50301f72579e 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -702,8 +702,15 @@ snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) return -ENOMEM; retval = stream->ops->set_params(stream, params); - if (retval) + if (retval) { + /* the stream stays OPEN: drop the buffer of the rejected setup */ + if (stream->runtime->state == SNDRV_PCM_STATE_OPEN && + !stream->runtime->dma_buffer_p) { + kfree(stream->runtime->buffer); + stream->runtime->buffer = NULL; + } return retval; + } if (stream->next_track) return retval; From 82da1acad6fd60b6072dd5f3383e6a55efdd2ac7 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 19 Oct 2026 17:45:31 +0300 Subject: [PATCH 907/924] ALSA: compress: reject buffer geometry change on gapless next_track snd_compr_set_params() is invoked from the gapless next_track re-entry path, which calls snd_compr_allocate_buffer() again and lets buffer_size change while total_bytes_available and total_bytes_transferred still hold values computed for the previous buffer. snd_compr_calc_avail() then derives an avail/count value from those stale counters against the new buffer_size, and snd_compr_write_data() copy_from_user()s that many bytes into the new, potentially smaller buffer, overflowing it with user-controlled length and content. The buffer is set up once, with the initial SET_PARAMS, and neither the core nor the DSP expects it to change for a gapless track transition. Do not call snd_compr_allocate_buffer() on the next_track path and reject a request that changes the buffer geometry, so that the codec parameters that the driver receives match the buffer in use. Fixes: 9727b490e543 ("ALSA: compress: add support for gapless playback") Cc: stable@vger.kernel.org Signed-off-by: Peter Ujfalusi Link: https://patch.msgid.link/20261019144533.30665-3-peter.ujfalusi@linux.intel.com Signed-off-by: Takashi Iwai --- sound/core/compress_offload.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 50301f72579e..d6863336f21c 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -697,9 +697,19 @@ snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) if (retval) return retval; - retval = snd_compr_allocate_buffer(stream, params); - if (retval) - return -ENOMEM; + if (stream->next_track) { + /* + * total_bytes_available/transferred still refer to the + * current buffer; its geometry must not change. + */ + if (params->buffer.fragment_size != stream->runtime->fragment_size || + params->buffer.fragments != stream->runtime->fragments) + return -EINVAL; + } else { + retval = snd_compr_allocate_buffer(stream, params); + if (retval) + return retval; + } retval = stream->ops->set_params(stream, params); if (retval) { From 931f5714b7921290cd1a4742e185aeeae7270ee6 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 19 Oct 2026 17:45:32 +0300 Subject: [PATCH 908/924] ALSA: compress: add missing OPEN-state gate to SET_METADATA snd_compr_set_metadata() calls stream->ops->set_metadata() straight after copy_from_user(), without ever checking runtime->state, even though the function's own comment says a parameter change should only be allowed once the stream has left the OPEN state. Every other metadata/param entry point in this file (SET_PARAMS, WRITE, ...) enforces that gate. A codec driver's set_metadata callback can assume SET_PARAMS has already run and initialised its private state; calling it while the stream is still OPEN can operate on that uninitialised state. Add the OPEN-state check the existing comment already documents. Fixes: 9727b490e543 ("ALSA: compress: add support for gapless playback") Cc: stable@vger.kernel.org Signed-off-by: Peter Ujfalusi Acked-by: Vinod Koul Link: https://patch.msgid.link/20261019144533.30665-4-peter.ujfalusi@linux.intel.com Signed-off-by: Takashi Iwai --- sound/core/compress_offload.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index d6863336f21c..a250c6b53996 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -789,6 +789,9 @@ snd_compr_set_metadata(struct snd_compr_stream *stream, unsigned long arg) * we should allow parameter change only when stream has been * opened not in other cases */ + if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (copy_from_user(&metadata, (void __user *)arg, sizeof(metadata))) return -EFAULT; From 0ade01edd33181b65393cf3864cb2ac7c5eb1487 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 19 Oct 2026 17:45:33 +0300 Subject: [PATCH 909/924] ALSA: compress: reject restarting an ACTIVE task via origin_seqno snd_compr_task_start_prepare() only rejects tasks with state >= SND_COMPRESS_TASK_STATE_FINISHED. Since the enum is IDLE(0) < ACTIVE(1) < FINISHED(2), this accepts ACTIVE tasks and rejects FINISHED ones, the opposite of what the origin_seqno mechanism needs: origin_seqno is meant to let a caller reuse a FINISHED task's buffers for a new job, not to restart a task that is still queued with the driver. The seqno lookup path in snd_compr_task_start() has its own explicit "task->state != IDLE" guard, but the origin_seqno lookup path relies solely on this function, so it can restart an ACTIVE task, causing ops->task_start() to be called twice for the same task and runtime->active_tasks to be incremented twice without a matching finish/stop. Accept IDLE and FINISHED, reject ACTIVE, on both entry paths. Fixes: 04177158cf98 ("ALSA: compress_offload: introduce accel operation mode") Cc: stable@vger.kernel.org Signed-off-by: Peter Ujfalusi Acked-by: Vinod Koul Link: https://patch.msgid.link/20261019144533.30665-5-peter.ujfalusi@linux.intel.com Signed-off-by: Takashi Iwai --- sound/core/compress_offload.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index a250c6b53996..5e5e0f78f26d 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -1206,7 +1206,9 @@ static int snd_compr_task_start_prepare(struct snd_compr_task_runtime *task, { if (task == NULL) return -EINVAL; - if (task->state >= SND_COMPRESS_TASK_STATE_FINISHED) + /* only an unqueued or already finished task may be (re)started */ + if (task->state != SND_COMPRESS_TASK_STATE_IDLE && + task->state != SND_COMPRESS_TASK_STATE_FINISHED) return -EBUSY; if (utask->input_size > task->input->size) return -EINVAL; From 47c3d5b885a4fcf65238c0cc525dd3c1cc65aa89 Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Tue, 20 Oct 2026 17:09:59 +0800 Subject: [PATCH 910/924] ALSA: mtpav: restore negation dropped in guard() conversion commit 316e0074ecdc ("ALSA: mtpav: Use guard() for spin locks") claims to be "Merely code refactoring, and no behavior change", but it accidentally dropped the "!" from if (! (portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED)) in snd_mtpav_output_trigger(). Restore the negation. Fixes: 316e0074ecdc ("ALSA: mtpav: Use guard() for spin locks") Signed-off-by: Wang Yan Cc: Link: https://patch.msgid.link/20261020090959.570101-1-wangyan01@kylinos.cn Signed-off-by: Takashi Iwai --- sound/drivers/mtpav.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/drivers/mtpav.c b/sound/drivers/mtpav.c index 1ed6d4f0cb62..288a873a27e6 100644 --- a/sound/drivers/mtpav.c +++ b/sound/drivers/mtpav.c @@ -443,7 +443,7 @@ static void snd_mtpav_output_trigger(struct snd_rawmidi_substream *substream, in scoped_guard(spinlock_irqsave, &mtp_card->spinlock) { if (up) { - if ((portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED)) { + if (!(portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED)) { if (mtp_card->istimer++ == 0) snd_mtpav_add_output_timer(mtp_card); portp->mode |= MTPAV_MODE_OUTPUT_TRIGGERED; From 34e3e04b8a1731a773797542f0781e99ce7cd755 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 20 Oct 2026 12:16:20 +0200 Subject: [PATCH 911/924] ALSA: emux: Fix UAF of soundfont zones at note-on snd_emux_note_on() looks up the matching zones via snd_soundfont_search_zone(), which takes sflist->lock only during the lookup and returns the raw zone pointers after releasing it. The caller then stores them in vp->zone without any protection. Meanwhile, a concurrent SNDRV_EMUX_IOCTL_REMOVE_LAST_SAMPLES ioctl may unlink and free those zones in snd_soundfont_remove_unlocked(); this leads to a use-after-free in snd_emux_note_on(), and later at accessing the cached vp->zone, e.g. in snd_emux_send_effect(). Fix it by holding sflist->lock in snd_emux_note_on() over the whole zone lookup and voice setup, and let snd_soundfont_search_zone() require the caller to hold the lock, instead. snd_soundfont_lock_preset() takes sflist->lock for setting presets_locked, so the removal path now syncs with the note-on finish. Reported-by: Sashiko Closes: https://sashiko.dev/#/bug/linux-697212bb-a434-4ff5-8778-25a84f5dad89 Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261020101624.205638-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/synth/emux/emux_synth.c | 3 +++ sound/synth/emux/soundfont.c | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/sound/synth/emux/emux_synth.c b/sound/synth/emux/emux_synth.c index 6982e1317da5..2cbf12364a1e 100644 --- a/sound/synth/emux/emux_synth.c +++ b/sound/synth/emux/emux_synth.c @@ -60,6 +60,9 @@ snd_emux_note_on(void *p, int note, int vel, struct snd_midi_channel *chan) return; key = note; /* remember the original note */ + + /* hold sflist->lock for protecting against concurrent free */ + guard(spinlock_irqsave)(&emu->sflist->lock); nvoices = get_zone(emu, port, ¬e, vel, chan, table); if (! nvoices) return; diff --git a/sound/synth/emux/soundfont.c b/sound/synth/emux/soundfont.c index c172b53b17a4..039357d96e07 100644 --- a/sound/synth/emux/soundfont.c +++ b/sound/synth/emux/soundfont.c @@ -1225,6 +1225,9 @@ delete_preset(struct snd_sf_list *sflist, struct snd_sf_zone *zp) * The found zones are stored on 'table' array. max_layers defines * the maximum number of elements in this array. * This function returns the number of found zones. 0 if not found. + * + * The caller must hold sflist->lock, and keep holding it as long as + * the returned zones are accessed. */ int snd_soundfont_search_zone(struct snd_sf_list *sflist, int *notep, int vel, @@ -1234,11 +1237,11 @@ snd_soundfont_search_zone(struct snd_sf_list *sflist, int *notep, int vel, { int nvoices; - /* this function is supposed to be called atomically, - * so we check the lock. if it's busy, just returns 0 to + lockdep_assert_held(&sflist->lock); + + /* if the presets are being modified, just returns 0 to * tell the caller the busy state */ - guard(spinlock_irqsave)(&sflist->lock); if (sflist->presets_locked) return 0; nvoices = search_zones(sflist, notep, vel, preset, bank, From 8a5d87c2ba30e8de31cfbe888fd4d95f43c63db9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 20 Oct 2026 12:16:21 +0200 Subject: [PATCH 912/924] ALSA: emux: Stop voices before freeing zones in all cases The emux voice caches the soundfont zone pointer in vp->zone, which is referred to later, e.g. in snd_emux_send_effect(). For avoiding dangling pointers, the soundfont code calls the sample_reset callback to stop all voices before freeing zones. However, in a few cases (e.g. emu10k1's missing sample_reset or the call path via remove_info()), the sample reset isn't called properly, leaving the playing voices. Fix it by always registering the sample_reset callback, falling back to snd_emux_terminate_all() when the driver has no own ops, and by calling sample_reset in remove_info() before freeing the zones. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261020101624.205638-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/synth/emux/emux.c | 10 +++++++--- sound/synth/emux/soundfont.c | 9 +++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/sound/synth/emux/emux.c b/sound/synth/emux/emux.c index 450147b1abed..2a42d041e1da 100644 --- a/sound/synth/emux/emux.c +++ b/sound/synth/emux/emux.c @@ -71,7 +71,12 @@ static int sf_sample_free(void *private_data, struct snd_sf_sample *sp, static void sf_sample_reset(void *private_data) { struct snd_emux *emu = private_data; - emu->ops.sample_reset(emu); + + /* stop all voices that might refer to zones and samples to be freed */ + if (emu->ops.sample_reset) + emu->ops.sample_reset(emu); + else + snd_emux_terminate_all(emu); } int snd_emux_register(struct snd_emux *emu, struct snd_card *card, int index, char *name) @@ -95,8 +100,7 @@ int snd_emux_register(struct snd_emux *emu, struct snd_card *card, int index, ch sf_cb.private_data = emu; sf_cb.sample_new = sf_sample_new; sf_cb.sample_free = sf_sample_free; - if (emu->ops.sample_reset) - sf_cb.sample_reset = sf_sample_reset; + sf_cb.sample_reset = sf_sample_reset; emu->sflist = snd_sf_new(&sf_cb, emu->memhdr); if (emu->sflist == NULL) return -ENOMEM; diff --git a/sound/synth/emux/soundfont.c b/sound/synth/emux/soundfont.c index 039357d96e07..42d1f33fb2b5 100644 --- a/sound/synth/emux/soundfont.c +++ b/sound/synth/emux/soundfont.c @@ -443,6 +443,15 @@ remove_info(struct snd_sf_list *sflist, struct snd_soundfont *sf, struct snd_sf_zone *prev, *next, *p; int removed = 0; + /* stop voices before freeing the zones they might refer to */ + for (p = sf->zones; p; p = p->next) { + if (!p->mapped && p->bank == bank && p->instr == instr) { + if (sflist->callback.sample_reset) + sflist->callback.sample_reset(sflist->callback.private_data); + break; + } + } + prev = NULL; for (p = sf->zones; p; p = next) { next = p->next; From 443ed4edc2678cc51c5a01e7dffc4c890be40051 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 20 Oct 2026 13:31:17 +0200 Subject: [PATCH 913/924] ALSA: line6: Handle period elapsed calls outside the locks LINE6 driver may call snd_pcm_period_elapsed() for both playback and capture substreams at processing the playback substream, and this may lead to a ABBA deadlock. Move the actual calls of snd_pcm_period_elapsed() outside the locks for avoiding it. The capture period notification at submitting the initial playback URBs (only with the impulse mode) is simply skipped, as it's done in the lock context; it'll be caught up at the next period. Fixes: f56742cc4189 ("ALSA: line6: Add LINE6_CAP_IN_NEEDS_OUT, a void playback stream during capture") Reported-by: Sashiko Closes: https://sashiko.dev/#/bug/linux-5a9de389-e6ea-410d-8322-e74e14152603 Cc: Link: https://patch.msgid.link/20261020113118.234689-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/line6/capture.c | 25 ++++++++++++++++--------- sound/usb/line6/capture.h | 2 +- sound/usb/line6/playback.c | 31 +++++++++++++++++++++---------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 3e1f40d6b481..17f49f567efb 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -126,24 +126,21 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) line6pcm->in.pos_done -= runtime->buffer_size; } -void line6_capture_check_period(struct snd_line6_pcm *line6pcm, int length) +/* update the capture position and check whether we need period_elapsed call */ +bool line6_capture_check_period(struct snd_line6_pcm *line6pcm, int length) { - struct snd_pcm_substream *substream = - get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE); - line6pcm->in.bytes += length; if (line6pcm->in.bytes >= line6pcm->in.period) { line6pcm->in.bytes %= line6pcm->in.period; - spin_unlock(&line6pcm->in.lock); - snd_pcm_period_elapsed(substream); - spin_lock(&line6pcm->in.lock); + return true; } + return false; } /* * Callback for completed capture URB. */ -static void audio_in_callback(struct urb *urb) +static bool handle_audio_in(struct urb *urb) { int i, index, length = 0, shutdown = 0; struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; @@ -208,8 +205,18 @@ static void audio_in_callback(struct urb *urb) if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) && test_bit(LINE6_STREAM_PCM, &line6pcm->in.running)) - line6_capture_check_period(line6pcm, length); + return line6_capture_check_period(line6pcm, length); } + + return false; +} + +static void audio_in_callback(struct urb *urb) +{ + struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; + + if (handle_audio_in(urb)) + snd_pcm_period_elapsed(get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE)); } /* open capture callback */ diff --git a/sound/usb/line6/capture.h b/sound/usb/line6/capture.h index 90572dae134e..628315389efc 100644 --- a/sound/usb/line6/capture.h +++ b/sound/usb/line6/capture.h @@ -17,7 +17,7 @@ extern const struct snd_pcm_ops snd_line6_capture_ops; extern void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize); -extern void line6_capture_check_period(struct snd_line6_pcm *line6pcm, +extern bool line6_capture_check_period(struct snd_line6_pcm *line6pcm, int length); extern int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm); extern int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm); diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index aa6ddf8746a0..e93a16f6958e 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -136,6 +136,8 @@ static void add_monitor_signal(struct urb *urb_out, unsigned char *signal, /* Find a free URB, prepare audio data, and submit URB. must be called in line6pcm->out.lock context + Returns 1 if snd_pcm_period_elapsed() needs to be called for the + capture stream after releasing the lock, 0 if not, or a negative error */ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) { @@ -151,6 +153,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) line6pcm->properties->rates.rats[0].den * (line6pcm->line6->intervals_per_second / LINE6_ISO_INTERVAL); struct urb *urb_out; + bool capture_period_elapsed = false; index = find_first_zero_bit(&line6pcm->out.active_urbs, line6pcm->line6->iso_buffers); @@ -260,8 +263,9 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) urb_out->transfer_buffer, urb_out-> transfer_buffer_length); - line6_capture_check_period(line6pcm, - urb_out->transfer_buffer_length); + capture_period_elapsed = + line6_capture_check_period(line6pcm, + urb_out->transfer_buffer_length); } } else { if (!(line6pcm->line6->properties->capabilities & LINE6_CAP_HWMON) @@ -283,24 +287,26 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) dev_err(line6pcm->line6->ifcdev, "URB out #%d submission failed (%d)\n", index, ret); - return 0; + return capture_period_elapsed; } /* Submit all currently available playback URBs. must be called in line6pcm->out.lock context + The capture period notification is skipped here since we can't call + snd_pcm_period_elapsed() in the lock; it'll be caught up at the next one. */ int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm) { - int ret = 0, i; + int ret, i; for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { ret = submit_audio_out_urb(line6pcm); if (ret < 0) - break; + return ret; } - return ret; + return 0; } /* @@ -316,6 +322,8 @@ static void audio_out_callback(struct urb *urb) const int bytes_per_frame = line6pcm->properties->bytes_per_channel * line6pcm->properties->playback_hw.channels_max; + bool capture_period_elapsed = false; + bool playback_period_elapsed = false; #if USE_CLEAR_BUFFER_WORKAROUND memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); @@ -358,19 +366,22 @@ static void audio_out_callback(struct urb *urb) shutdown = 1; if (!shutdown) { - submit_audio_out_urb(line6pcm); + capture_period_elapsed = submit_audio_out_urb(line6pcm) > 0; if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running)) { line6pcm->out.bytes += length; if (line6pcm->out.bytes >= line6pcm->out.period) { line6pcm->out.bytes %= line6pcm->out.period; - spin_unlock(&line6pcm->out.lock); - snd_pcm_period_elapsed(substream); - spin_lock(&line6pcm->out.lock); + playback_period_elapsed = true; } } } spin_unlock_irqrestore(&line6pcm->out.lock, flags); + + if (capture_period_elapsed) + snd_pcm_period_elapsed(get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE)); + if (playback_period_elapsed) + snd_pcm_period_elapsed(substream); } /* open playback callback */ From 90b232581921badef35701d7168d887a0f3d43d4 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Fri, 9 Oct 2026 17:58:10 +0200 Subject: [PATCH 914/924] ALSA: hda/realtek: enable AW88399 on Lenovo Legion R9000P AFR10 Add codec SSID entries for the Lenovo Legion R9000P AFR10 (83F6), which uses the same ALC287 + AW88399 smart amplifier configuration as the existing supported Legion models. DSDT inspection confirms identical AWDZ8399 ACPI device layout with reversed I2C addresses. The channel swap quirk applies. Codec SSIDs (Lenovo vendor ID 0x17aa): * 0x3934: Legion R9000P AFR10 (AMD) * 0x3935: Legion R9000P AFR10 (AMD) Signed-off-by: Marco Giunta Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/SN6PR19MB2303D726352EEE508A70D6D4FC922@SN6PR19MB2303.namprd19.prod.outlook.com --- sound/hda/codecs/realtek/alc269.c | 2 ++ sound/hda/codecs/side-codecs/aw88399_hda.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index bf53fdd3f6a1..d31a5b04a4b7 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8612,6 +8612,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + HDA_CODEC_QUIRK(0x17aa, 0x3934, "Legion R9000P AFR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), + HDA_CODEC_QUIRK(0x17aa, 0x3935, "Legion R9000P AFR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), HDA_CODEC_QUIRK(0x17aa, 0x3937, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), HDA_CODEC_QUIRK(0x17aa, 0x3938, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c index 93fd09558ac9..00b78299fa56 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda.c @@ -172,6 +172,8 @@ static const struct aw88399_prop_model aw88399_prop_model_table[] = { { "17AA3907", aw88399_swap_channels }, { "17AA3927", aw88399_swap_channels }, { "17AA3928", aw88399_swap_channels }, + { "17AA3934", aw88399_swap_channels }, + { "17AA3935", aw88399_swap_channels }, { "17AA3936", aw88399_swap_channels }, { "17AA3937", aw88399_swap_channels }, { "17AA3938", aw88399_swap_channels }, From ecefaa2dc097e07c51231e6831c406d1fdb17557 Mon Sep 17 00:00:00 2001 From: Nazmi Maizan Date: Wed, 21 Oct 2026 00:23:23 +0800 Subject: [PATCH 915/924] ALSA: hda/realtek: Fix speakers on COLORFUL L1 Plus The COLORFUL L1 Plus (Rimbook L1 Plus) with an ALC233 codec has silent built-in speakers. Its firmware marks pin 0x1b as unused (0x411111f0), although it is connected to the speakers. Retasking pin 0x1b to an internal speaker (0x90170110) restores audible output. I tested this pin-only correction with the early HDA firmware patch loader on Fedora and confirmed that it still worked after reboot. Add the subsystem ID 2782:a248 to the quirk table and reuse ALC233_FIXUP_MEDION_MTL_SPK, which applies the same pin correction. Link: https://bugzilla.kernel.org/show_bug.cgi?id=222155 Signed-off-by: Nazmi Maizan Link: https://patch.msgid.link/20261020162323.23202-1-nazmi.maizan1@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index d31a5b04a4b7..f2ef2b265489 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8726,6 +8726,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x2782, 0x4900, "MEDION E15443", ALC233_FIXUP_MEDION_MTL_SPK), SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x2782, 0xa248, "COLORFUL L1 Plus", ALC233_FIXUP_MEDION_MTL_SPK), SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), SND_PCI_QUIRK(0x8086, 0x2073, "Intel NUC 8 Hades Canyon", ALC700_FIXUP_INTEL_HADES_CANYON), From 317f2ddb6662fca8954e381a1bc632667872c4a3 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 20 Oct 2026 19:18:36 +0900 Subject: [PATCH 916/924] selftests: ALSA: pcmtest: Check the frame count of each transfer The tests keep the result of snd_pcm_writei(), snd_pcm_readi() and their non-interleaved forms in a size_t. A size_t is never negative, so ASSERT_GE(res, 0) always passes. The capture tests still catch a failed read when they compare the data. But the playback tests do not. The driver sets pc_test to 1 when it saw no corrupted data, and that is also true when it saw no data at all. So a playback test passes even when every write fails. Store the result as snd_pcm_sframes_t and check that the whole request was transferred. The calls block, so a short count means an error. Fixes: 10b98a4db11a ("selftests: ALSA: Add test for the 'pcmtest' driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/6f16e2a01f18c0ea069164bab31999182ff6d49e.1792390197.git.sammiee5311@gmail.com --- .../selftests/alsa/test-pcmtest-driver.c | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/alsa/test-pcmtest-driver.c b/tools/testing/selftests/alsa/test-pcmtest-driver.c index 95065ef3b441..5bba17434a6b 100644 --- a/tools/testing/selftests/alsa/test-pcmtest-driver.c +++ b/tools/testing/selftests/alsa/test-pcmtest-driver.c @@ -166,7 +166,7 @@ FIXTURE_SETUP(pcmtest) { TEST_F(pcmtest, playback) { snd_pcm_t *handle; unsigned char *it; - size_t write_res; + snd_pcm_sframes_t write_res; int test_results; int i, cur_ch, pos_in_ch; void *samples; @@ -190,7 +190,7 @@ TEST_F(pcmtest, playback) { it[i] = patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]; } write_res = snd_pcm_writei(handle, samples, params->rate * params->time); - ASSERT_GE(write_res, 0); + ASSERT_EQ(write_res, params->rate * params->time); snd_pcm_close(handle); free(samples); @@ -206,7 +206,7 @@ TEST_F(pcmtest, playback) { TEST_F(pcmtest, capture) { snd_pcm_t *handle; unsigned char *it; - size_t read_res; + snd_pcm_sframes_t read_res; int i, cur_ch, pos_in_ch; void *samples; struct pcmtest_test_params *params = &self->params; @@ -222,7 +222,7 @@ TEST_F(pcmtest, capture) { snd_pcm_format_set_silence(params->format, samples, params->rate * params->channels * params->time); read_res = snd_pcm_readi(handle, samples, params->rate * params->time); - ASSERT_GE(read_res, 0); + ASSERT_EQ(read_res, params->rate * params->time); snd_pcm_close(handle); it = (unsigned char *)samples; for (i = 0; i < self->params.sec_buf_len * self->params.time; i++) { @@ -239,7 +239,8 @@ TEST_F(pcmtest, ni_capture) { snd_pcm_t *handle; struct pcmtest_test_params params = self->params; char **chan_samples; - size_t i, j, read_res; + snd_pcm_sframes_t read_res; + size_t i, j; chan_samples = calloc(CH_NUM, sizeof(*chan_samples)); ASSERT_NE(chan_samples, NULL); @@ -257,7 +258,7 @@ TEST_F(pcmtest, ni_capture) { for (i = 0; i < 1; i++) { read_res = snd_pcm_readn(handle, (void **)chan_samples, params.rate * params.time); - ASSERT_GE(read_res, 0); + ASSERT_EQ(read_res, params.rate * params.time); } snd_pcm_close(handle); @@ -273,7 +274,8 @@ TEST_F(pcmtest, ni_playback) { snd_pcm_t *handle; struct pcmtest_test_params params = self->params; char **chan_samples; - size_t i, j, read_res; + snd_pcm_sframes_t write_res; + size_t i, j; int test_res; chan_samples = calloc(CH_NUM, sizeof(*chan_samples)); @@ -294,8 +296,9 @@ TEST_F(pcmtest, ni_playback) { } for (i = 0; i < 1; i++) { - read_res = snd_pcm_writen(handle, (void **)chan_samples, params.rate * params.time); - ASSERT_GE(read_res, 0); + write_res = snd_pcm_writen(handle, (void **)chan_samples, + params.rate * params.time); + ASSERT_EQ(write_res, params.rate * params.time); } snd_pcm_close(handle); From c338e77291d6b1b6aee6d45b3cc523f97199a011 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 20 Oct 2026 19:18:37 +0900 Subject: [PATCH 917/924] selftests: ALSA: pcmtest: Make room for the longest fill pattern The driver accepts fill patterns of up to 4096 bytes, as pcmtest.rst documents. The test reads each pattern into a 1024-byte buffer, using the length the driver reports. So a pattern longer than 1024 bytes overflows the buffer into the next pattern and past the array. Size the buffer to the driver's maximum. Fixes: 10b98a4db11a ("selftests: ALSA: Add test for the 'pcmtest' driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/7cbbcef84d2c14a2f0ec0ad2ba55c6ba0a15a8d8.1792390197.git.sammiee5311@gmail.com --- tools/testing/selftests/alsa/test-pcmtest-driver.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/alsa/test-pcmtest-driver.c b/tools/testing/selftests/alsa/test-pcmtest-driver.c index 5bba17434a6b..e025a7bac26b 100644 --- a/tools/testing/selftests/alsa/test-pcmtest-driver.c +++ b/tools/testing/selftests/alsa/test-pcmtest-driver.c @@ -10,9 +10,11 @@ #include "kselftest_harness.h" #define CH_NUM 4 +/* The driver accepts patterns of up to 4096 bytes */ +#define MAX_PATTERN_LEN 4096 struct pattern_buf { - char buf[1024]; + char buf[MAX_PATTERN_LEN]; int len; }; From c3bde2beacbcb40790f0e3c3f909f9e16bbced9c Mon Sep 17 00:00:00 2001 From: Henrik Enquist Date: Wed, 21 Oct 2026 09:46:45 +0200 Subject: [PATCH 918/924] ALSA: aloop: Update the PCM Slave controls for a matching playback The "PCM Slave" format, rate, channels and access controls report the parameters of the playback side of a cable. loopback_check_format() updates them when a playback starts, but returns early when a capture is open and its parameters match. The controls then keep the module defaults, or the parameters of an earlier stream. A playback that joins an open capture with the same parameters is the normal case without notify, where it is constrained to the capture's parameters, and also with notify whenever the player happens to use them. An application that reads the controls to find the playback's parameters, for example to reopen the capture side at the right rate after a restart, then gets the wrong values. Only skip the capture stop when the parameters match, and update the controls in every case. A value event is still sent only for a control whose value changes. Reproducer: arecord -D hw:Loopback,1,0 -r 44100 -f S16_LE -c 2 /dev/null & aplay -D hw:Loopback,0,0 -t raw -r 44100 -f S16_LE -c 2 /dev/zero & amixer -c Loopback cget "iface=PCM,name='PCM Slave Rate',device=1" Before: the control reports 48000, the module default. After: it reports 44100. Fixes: 597603d615d2 ("ALSA: introduce the snd-aloop module for the PCM loopback") Cc: stable@vger.kernel.org Signed-off-by: Henrik Enquist Link: https://patch.msgid.link/20261021-aloop-slave-controls-v1-1-62297727177f@gmail.com Signed-off-by: Takashi Iwai --- sound/drivers/aloop.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index d6a059ab3747..824d320b665b 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -412,11 +412,14 @@ static int loopback_check_format(struct loopback_cable *cable, int stream) runtime->channels != cruntime->channels || is_access_interleaved(runtime->access) != is_access_interleaved(cruntime->access); - if (!check) - return 0; - if (stream == SNDRV_PCM_STREAM_CAPTURE) + /* + * Matching params also go on to the update below, so that + * the setup controls follow a playback that joins an open + * capture. + */ + if (check && stream == SNDRV_PCM_STREAM_CAPTURE) return -EIO; - else if (cruntime->state == SNDRV_PCM_STATE_RUNNING) { + else if (check && cruntime->state == SNDRV_PCM_STATE_RUNNING) { /* close must not free the peer runtime below */ snd_refcount_get(&cable->stop_count); stop_capture = true; From d0bee96f846ff56e0ab93030b5f187550b882977 Mon Sep 17 00:00:00 2001 From: Henrik Enquist Date: Wed, 21 Oct 2026 09:46:46 +0200 Subject: [PATCH 919/924] selftests/alsa: Check the aloop PCM Slave controls for a matching playback Add a test that starts a playback with the same parameters as a running capture, and checks that the capture keeps running and that the "PCM Slave" controls report the playback's parameters, with a value event for each control that changed. Before the previous patch, the controls keep the parameters of the earlier stream and the test fails. Signed-off-by: Henrik Enquist Link: https://patch.msgid.link/20261021-aloop-slave-controls-v1-2-62297727177f@gmail.com Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/aloop-test.c | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tools/testing/selftests/alsa/aloop-test.c b/tools/testing/selftests/alsa/aloop-test.c index a58b9fdbdc17..74d6ccc12724 100644 --- a/tools/testing/selftests/alsa/aloop-test.c +++ b/tools/testing/selftests/alsa/aloop-test.c @@ -323,6 +323,46 @@ TEST_F(aloop, params_change_stops_capture_with_notify) { snd_pcm_close(capt); } +/* + * A playback that joins a running capture with the same parameters doesn't + * stop it, and the "PCM Slave" controls still report the playback's + * parameters rather than those of an earlier stream. + */ +TEST_F(aloop, matching_playback_updates_controls) { + snd_pcm_t *capt, *play; + unsigned int events; + + /* Leave the controls at other parameters than the ones used below. */ + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0); + ASSERT_EQ(start_playback(play, &playback_params), 0); + snd_pcm_close(play); + + ASSERT_EQ(set_notify(self->ctl, false), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + ASSERT_EQ(snd_pcm_start(capt), 0); + read_events(self->ctl); + + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &capture_params), 0); + ASSERT_EQ(start_playback(play, &capture_params), 0); + + EXPECT_EQ(snd_pcm_state(capt), SND_PCM_STATE_RUNNING); + + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 1); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Format"), capture_params.format); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Rate"), capture_params.rate); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Channels"), capture_params.channels); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Access Mode"), 0); + + events = read_events(self->ctl); + EXPECT_TRUE(events & EV_FORMAT); + EXPECT_TRUE(events & EV_RATE); + EXPECT_TRUE(events & EV_CHANNELS); + EXPECT_TRUE(events & EV_ACCESS); + + snd_pcm_close(play); + snd_pcm_close(capt); +} + /* With notify, a capture opened second is still pinned to the playback's parameters. */ TEST_F(aloop, capture_constrained_with_notify) { struct probe_result res; From 87bc966dc59ff43694f201a39983963a66be6102 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 21 Oct 2026 11:40:01 +0200 Subject: [PATCH 920/924] ALSA: hiface: Use scnprintf() in hiface_chip_create() To prepare for removing strlcat() from the kernel [1], use scnprintf() instead. Since scnprintf() returns the number of characters actually written, drop the length check before usb_make_path(). Also drop the explicit size arguments from strscpy() since they can be inferred from the destination arrays. No functional change. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20261021094002.114508-2-blum@kernel.org Signed-off-by: Takashi Iwai --- sound/usb/hiface/chip.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c index bce28f683666..41a43bc59397 100644 --- a/sound/usb/hiface/chip.c +++ b/sound/usb/hiface/chip.c @@ -63,18 +63,15 @@ static int hiface_chip_create(struct usb_interface *intf, return ret; } - strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); + strscpy(card->driver, DRIVER_NAME); if (quirk && quirk->device_name) - strscpy(card->shortname, quirk->device_name, sizeof(card->shortname)); + strscpy(card->shortname, quirk->device_name); else - strscpy(card->shortname, "M2Tech generic audio", sizeof(card->shortname)); + strscpy(card->shortname, "M2Tech generic audio"); - strlcat(card->longname, card->shortname, sizeof(card->longname)); - len = strlcat(card->longname, " at ", sizeof(card->longname)); - if (len < sizeof(card->longname)) - usb_make_path(device, card->longname + len, - sizeof(card->longname) - len); + len = scnprintf(card->longname, sizeof(card->longname), "%s at ", card->shortname); + usb_make_path(device, card->longname + len, sizeof(card->longname) - len); chip = card->private_data; chip->dev = device; From 8045043c9d66508b5ebfd702e555b9642b5aa805 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 21 Oct 2026 12:39:29 +0200 Subject: [PATCH 921/924] ALSA: caiaq: Use strscpy() in snd_usb_caiaq_input_init() To prepare for removing strlcat() from the kernel [1], use strscpy() instead with the length returned by usb_make_path() as the append offset. No functional change. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20261021103928.131918-2-blum@kernel.org Signed-off-by: Takashi Iwai --- sound/usb/caiaq/input.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index 856235c3398e..2830a1da067d 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -620,13 +620,15 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev) struct usb_device *usb_dev = cdev->chip.dev; struct input_dev *input; int i, ret = 0; + int len; input = input_allocate_device(); if (!input) return -ENOMEM; - usb_make_path(usb_dev, cdev->phys, sizeof(cdev->phys)); - strlcat(cdev->phys, "/input0", sizeof(cdev->phys)); + len = usb_make_path(usb_dev, cdev->phys, sizeof(cdev->phys)); + if (len >= 0) + strscpy(cdev->phys + len, "/input0", sizeof(cdev->phys) - len); input->name = cdev->product_name; input->phys = cdev->phys; From d158ee133e3af1f11739b9d37dde754fdeb62a9f Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 21 Oct 2026 12:58:26 +0200 Subject: [PATCH 922/924] ALSA: usb-audio: Use strscpy() in append_ctl_name() To prepare for removing strlcat() from the kernel [1], use strscpy() instead with the current name length as the append offset. Change append_ctl_name() to return void since no caller uses its return value. Use append_ctl_name() to append " Jack" in build_connector_control(). No functional change. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20261021105826.147060-3-blum@kernel.org Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index a95f26a3cfa7..95209ec006b3 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1781,9 +1781,11 @@ const struct snd_kcontrol_new *snd_usb_feature_unit_ctl = &usb_feature_unit_ctl; /* * build a feature control */ -static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str) +static void append_ctl_name(struct snd_kcontrol *kctl, const char *str) { - return strlcat(kctl->id.name, str, sizeof(kctl->id.name)); + size_t len = strlen(kctl->id.name); + + strscpy(kctl->id.name + len, str, sizeof(kctl->id.name) - len); } /* @@ -2151,7 +2153,7 @@ static void build_connector_control(struct usb_mixer_interface *mixer, } if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) - strlcat(kctl->id.name, " Jack", sizeof(kctl->id.name)); + append_ctl_name(kctl, " Jack"); else get_connector_control_name(mixer, term, is_input, kctl->id.name, sizeof(kctl->id.name)); From c8c199e1b1b3bb0ef52a2a02d1ec87bee574ce13 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 21 Oct 2026 20:25:54 +0900 Subject: [PATCH 923/924] selftests: ALSA: pcmtest: Compare all captured bytes in ni_capture The ni_capture test reads rate * time frames into each channel buffer and then compares rate * time bytes. A frame is sample_size bytes per channel, so with S16_LE only the first half of each channel is checked. If the second half is wrong the test still passes. Compare rate * time * sample_size bytes per channel. Fixes: 10b98a4db11a ("selftests: ALSA: Add test for the 'pcmtest' driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20261021112554.4037107-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/test-pcmtest-driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/alsa/test-pcmtest-driver.c b/tools/testing/selftests/alsa/test-pcmtest-driver.c index e025a7bac26b..18ee72d5d4c9 100644 --- a/tools/testing/selftests/alsa/test-pcmtest-driver.c +++ b/tools/testing/selftests/alsa/test-pcmtest-driver.c @@ -265,7 +265,7 @@ TEST_F(pcmtest, ni_capture) { snd_pcm_close(handle); for (i = 0; i < CH_NUM; i++) { - for (j = 0; j < params.rate * params.time; j++) + for (j = 0; j < params.rate * params.time * params.sample_size; j++) ASSERT_EQ(chan_samples[i][j], patterns[i].buf[j % patterns[i].len]); free(chan_samples[i]); } From ed7995e4ec3789a90e37eb826bfccf43cc85d2fd Mon Sep 17 00:00:00 2001 From: Ajanesh Lal Date: Thu, 22 Oct 2026 02:24:53 +1200 Subject: [PATCH 924/924] ALSA: hda/realtek: Add mute and mic-mute LEDs for HP ENVY 16 (103c:8be5) The HP ENVY Laptop 16-h1xxx (PCI SSID 103c:8be5) uses an ALC245 codec with CS35L41 I2C speaker amplifiers. No LED fixup is applied for this subsystem ID, so the speaker mute LED and the microphone mute LED are never driven and do not follow their keys. Add a fixup that calls the existing ALC245 mute LED COEF helper and the GPIO mic-mute LED helper directly, then chains to the CS35L41 I2C amplifier setup so the speakers keep working. Use it for 103c:8be5. Tested on an HP ENVY Laptop 16-h1xxx running kernel 7.2.11: the hda::mute and hda::micmute LEDs appear and follow their keys, and the speaker amplifiers still bind (CS35L41 Bound in dmesg). The mic LED GPIO pin and polarity are not verified on other hardware. Fixes: aa8e3ef4fe53 ("ALSA: hda/realtek: Add quirks for various HP ENVY models") Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ajanesh Lal Link: https://patch.msgid.link/20261021142453.177174-1-ajaneshlal3@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index f2ef2b265489..23aa175faedd 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -1642,6 +1642,14 @@ static void alc245_fixup_hp_mute_led_coefbit(struct hda_codec *codec, } } +/* mute LED via COEF bit and mic-mute LED via GPIO, called directly */ +static void alc245_fixup_cs35l41_mute_micmute(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc245_fixup_hp_mute_led_coefbit(codec, fix, action); + alc245_fixup_hp_gpio_led(codec, fix, action); +} + static void alc245_fixup_hp_mute_led_v1_coefbit(struct hda_codec *codec, const struct hda_fixup *fix, int action) @@ -4526,6 +4534,7 @@ enum { ALC245_FIXUP_BASS_HP_DAC, ALC245_FIXUP_ACER_MICMUTE_LED, ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED, + ALC245_FIXUP_CS35L41_I2C_2_MUTE_MICMUTE_LED, ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS, ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE, @@ -7333,6 +7342,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_CS35L41_I2C_2, }, + [ALC245_FIXUP_CS35L41_I2C_2_MUTE_MICMUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc245_fixup_cs35l41_mute_micmute, + .chained = true, + .chain_id = ALC287_FIXUP_CS35L41_I2C_2, + }, [ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -7922,7 +7937,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8be1, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be2, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be3, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8be5, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8be5, "HP Envy 16", ALC245_FIXUP_CS35L41_I2C_2_MUTE_MICMUTE_LED), SND_PCI_QUIRK(0x103c, 0x8be6, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be7, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be8, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS),