From b34c325539ca9e473a754685b3ca866efe21bea1 Mon Sep 17 00:00:00 2001 From: Francesco Valla Date: Sun, 30 Aug 2026 15:48:35 +0200 Subject: [PATCH 001/724] spi: virtio: drop duplicate completion init The completion iused for SPI transfers is initialized twice in the same function, without it being used in between. Drop the redundant initialization. Signed-off-by: Francesco Valla Link: https://patch.msgid.link/20260830-virtio-spi-fix-v1-1-62f486f4acdc@valla.it Signed-off-by: Mark Brown --- drivers/spi/spi-virtio.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/spi/spi-virtio.c b/drivers/spi/spi-virtio.c index 2256dfec5407..87ce32b6d133 100644 --- a/drivers/spi/spi-virtio.c +++ b/drivers/spi/spi-virtio.c @@ -162,8 +162,6 @@ static int virtio_spi_transfer_one(struct spi_controller *ctrl, if (!spi_req) return -ENOMEM; - init_completion(&spi_req->completion); - th = &spi_req->transfer_head; /* Fill struct spi_transfer_head */ From 420e2f8c69d110d34da2464dd53a24f546458e51 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Sat, 15 Aug 2026 10:55:10 +0800 Subject: [PATCH 002/724] 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 003/724] 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 004/724] 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 005/724] 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 006/724] 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 007/724] 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 008/724] 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 009/724] 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 010/724] 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 011/724] 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 012/724] 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 013/724] 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 014/724] 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 015/724] 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 016/724] 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 017/724] 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 018/724] 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 019/724] 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 dd007297551f20874124f6a703aa2f98ffd72084 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 21 Aug 2026 12:54:52 +0200 Subject: [PATCH 020/724] spi: Fix tab and space mixture in Kconfig There are a couple of sections which indented using spaces and not tabs. Fix them. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260821105541.1432348-2-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index a0f37e2df716..93c3d9e1416a 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -388,14 +388,14 @@ config SPI_DW_MMIO endif config SPI_DLN2 - tristate "Diolan DLN-2 USB SPI adapter" - depends on MFD_DLN2 - help - If you say yes to this option, support will be included for Diolan - DLN2, a USB to SPI interface. + tristate "Diolan DLN-2 USB SPI adapter" + depends on MFD_DLN2 + help + If you say yes to this option, support will be included for Diolan + DLN2, a USB to SPI interface. - This driver can also be built as a module. If so, the module - will be called spi-dln2. + This driver can also be built as a module. If so, the module + will be called spi-dln2. config SPI_EP93XX tristate "Cirrus Logic EP93xx SPI controller" @@ -475,7 +475,7 @@ config SPI_NXP_FLEXSPI supports the high-level SPI memory interface. config SPI_NXP_XSPI - tristate "NXP xSPI controller" + tristate "NXP xSPI controller" depends on ARCH_MXC || COMPILE_TEST depends on HAS_IOMEM help From 4895d7068723e6a2e575603a5764b4db07041cc2 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 21 Aug 2026 12:54:53 +0200 Subject: [PATCH 021/724] spi: Drop redundant dependency on SPI_MASTER The sections defined under 'if SPI_MASTER' already imply that the SPI_MASTER is selected. Drop redundant dependencies. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260821105541.1432348-3-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 93c3d9e1416a..f53c0ece5d96 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -63,7 +63,6 @@ comment "SPI Master Controller Drivers" config SPI_AIROHA_SNFI tristate "Airoha SPI NAND Flash Interface" depends on ARCH_AIROHA || COMPILE_TEST - depends on SPI_MASTER select REGMAP_MMIO help This enables support for SPI-NAND mode on the Airoha NAND @@ -333,7 +332,7 @@ config SPI_CADENCE_XSPI config SPI_CH341 tristate "CH341 USB2SPI adapter" - depends on SPI_MASTER && USB + depends on USB help Enables the SPI controller on the CH341a USB to serial chip @@ -722,7 +721,6 @@ config SPI_MESON_SPIFC config SPI_MICROCHIP_CORE_QSPI tristate "Microchip FPGA QSPI controllers" - depends on SPI_MASTER help This enables the QSPI driver for Microchip FPGA QSPI controllers. Say Y or M here if you want to use the QSPI controllers on @@ -731,7 +729,6 @@ config SPI_MICROCHIP_CORE_QSPI config SPI_MICROCHIP_CORE_SPI tristate "Microchip FPGA CoreSPI controller" - depends on SPI_MASTER help This enables the SPI driver for Microchip FPGA CoreSPI controller. Say Y or M here if you want to use the "soft" controllers on @@ -887,7 +884,6 @@ config SPI_PL022 config SPI_POLARFIRE_SOC tristate "Microchip FPGA SPI controllers" - depends on SPI_MASTER depends on ARCH_MICROCHIP || COMPILE_TEST help This enables the SPI driver for Microchip FPGA SPI controllers. @@ -950,7 +946,7 @@ config SPI_ROCKCHIP_SFC config SPI_RB4XX tristate "Mikrotik RB4XX SPI master" - depends on SPI_MASTER && (ATH79 || COMPILE_TEST) + depends on ATH79 || COMPILE_TEST depends on OF help SPI controller driver for the Mikrotik RB4xx series boards. @@ -1191,7 +1187,6 @@ config SPI_SYNQUACER config SPI_MXIC tristate "Macronix MX25F0A SPI controller" - depends on SPI_MASTER imply MTD_NAND_ECC_MXIC help This selects the Macronix MX25F0A SPI controller driver. @@ -1270,7 +1265,7 @@ config SPI_UNIPHIER config SPI_VIRTIO tristate "Virtio SPI Controller" - depends on SPI_MASTER && VIRTIO + depends on VIRTIO help If you say yes to this option, support will be included for the virtio SPI controller driver. The hardware can be emulated by any device model @@ -1339,7 +1334,7 @@ config SPI_ZYNQMP_GQSPI config SPI_AMD tristate "AMD SPI controller" depends on PCI - depends on SPI_MASTER || X86 || COMPILE_TEST + depends on X86 || COMPILE_TEST depends on SPI_MEM help Enables SPI controller driver for AMD SoC. From d696b9109d2315fc5f664766628337489a2b828f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 21 Aug 2026 12:54:54 +0200 Subject: [PATCH 022/724] spi: Fix the section ordering in accordance with the comment The comments in all sections tell that the list of the sections should be alphabetically ordered. With time this went apart, mostly for Freescale entries. So, put the things into order again. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260821105541.1432348-4-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 339 ++++++++++++++++++++++---------------------- 1 file changed, 169 insertions(+), 170 deletions(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index f53c0ece5d96..357cebef9917 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -91,6 +91,14 @@ config SPI_ALTERA_DFL Altera SPI master controller. The SPI master is connected to a SPI slave to Avalon bridge in a Intel MAX BMC. +config SPI_AMD + tristate "AMD SPI controller" + depends on PCI + depends on X86 || COMPILE_TEST + depends on SPI_MEM + help + Enables SPI controller driver for AMD SoC. + config SPI_AMLOGIC_SPIFC_A1 tristate "Amlogic A1 SPIFC controller" depends on ARCH_MESON || COMPILE_TEST @@ -135,6 +143,13 @@ config SPI_AR934X This enables support for the SPI controller present on the Qualcomm Atheros AR934X/QCA95XX SoCs. +config SPI_ARMADA_3700 + tristate "Marvell Armada 3700 SPI Controller" + depends on (ARCH_MVEBU && OF) || COMPILE_TEST + help + This enables support for the SPI controller present on the + Marvell Armada 3700 SoCs. + config SPI_ATCSPI200 tristate "Andes ATCSPI200 SPI controller" depends on ARCH_ANDES || COMPILE_TEST @@ -152,13 +167,6 @@ config SPI_ATH79 This enables support for the SPI controller present on the Atheros AR71XX/AR724X/AR913X SoCs. -config SPI_ARMADA_3700 - tristate "Marvell Armada 3700 SPI Controller" - depends on (ARCH_MVEBU && OF) || COMPILE_TEST - help - This enables support for the SPI controller present on the - Marvell Armada 3700 SoCs. - config SPI_ASPEED_SMC tristate "Aspeed flash controllers in SPI mode" depends on ARCH_ASPEED || COMPILE_TEST @@ -419,6 +427,22 @@ config SPI_FSI This enables support for the driver for FSI bus attached SPI controllers. +config SPI_FSL_DSPI + tristate "Freescale DSPI controller" + select REGMAP_MMIO + depends on ARCH_MXC || ARCH_NXP || M5441x || COMPILE_TEST + help + This enables support for the Freescale DSPI controller in master + mode. S32, VF610, LS1021A and ColdFire platforms uses the controller. + +config SPI_FSL_ESPI + tristate "Freescale eSPI controller" + depends on FSL_SOC + help + This enables using the Freescale eSPI controllers in master mode. + From MPC8536, 85xx platform uses the controller, and all P10xx, + P20xx, P30xx,P40xx, P50xx uses this controller. + config SPI_FSL_LPSPI tristate "Freescale i.MX LPSPI controller" depends on ARCH_MXC || COMPILE_TEST @@ -437,6 +461,26 @@ config SPI_FSL_QUADSPI This controller does not support generic SPI messages. It only supports the high-level SPI memory interface. +config SPI_FSL_LIB + tristate + depends on OF + +config SPI_FSL_CPM + tristate + depends on FSL_SOC + +config SPI_FSL_SPI + tristate "Freescale SPI controller and Aeroflex Gaisler GRLIB SPI controller" + depends on OF + select SPI_FSL_LIB + select SPI_FSL_CPM if FSL_SOC + help + This enables using the Freescale SPI controllers in master mode. + MPC83xx platform uses the controller in cpu mode or CPM/QE mode. + MPC8569 uses the controller in QE mode, MPC8610 in cpu mode. + This also enables using the Aeroflex Gaisler GRLIB SPI controller in + master mode. + config SPI_GXP tristate "GXP SPI driver" depends on ARCH_HPE || COMPILE_TEST @@ -462,27 +506,6 @@ config SPI_HISI_SFC_V3XX This enables support for HiSilicon v3xx SPI NOR flash controller found in hi16xx chipsets. -config SPI_NXP_FLEXSPI - tristate "NXP Flex SPI controller" - depends on ARCH_LAYERSCAPE || ARCH_MXC || COMPILE_TEST - depends on HAS_IOMEM - help - This enables support for the Flex SPI controller in master mode. - Up to four slave devices can be connected on two buses with two - chipselects each. - This controller does not support generic SPI messages and only - supports the high-level SPI memory interface. - -config SPI_NXP_XSPI - tristate "NXP xSPI controller" - depends on ARCH_MXC || COMPILE_TEST - depends on HAS_IOMEM - help - This enables support for the xSPI controller. Up to two devices - can be connected to one host. - This controller does not support generic SPI messages and only - supports the high-level SPI memory interface. - config SPI_GPIO tristate "GPIO-based bitbanging SPI Master" depends on GPIOLIB || COMPILE_TEST @@ -580,6 +603,25 @@ config SPI_KSPI2 This driver can also be built as a module. If so, the module will be called spi-kspi2. +config SPI_LANTIQ_SSC + tristate "Lantiq SSC SPI controller" + depends on LANTIQ || X86 || COMPILE_TEST + help + This driver supports the Lantiq SSC SPI controller in master + mode. This controller is found on Intel (former Lantiq) SoCs like + the Danube, Falcon, xRX200, xRX300, Lightning Mountain. + +config SPI_LJCA + tristate "Intel La Jolla Cove Adapter SPI support" + depends on USB_LJCA + default USB_LJCA + help + Select this option to enable SPI driver for the Intel + La Jolla Cove Adapter (LJCA) board. + + This driver can also be built as a module. If so, the module + will be called spi-ljca. + config SPI_LM70_LLP tristate "Parallel port adapter for LM70 eval board (DEVELOPMENT)" depends on PARPORT @@ -625,74 +667,6 @@ config SPI_LP8841_RTC Say N here unless you plan to run the kernel on an ICP DAS LP-8x4x industrial computer. -config SPI_MPC52xx - tristate "Freescale MPC52xx SPI (non-PSC) controller support" - depends on PPC_MPC52xx - help - This drivers supports the MPC52xx SPI controller in master SPI - mode. - -config SPI_MPC52xx_PSC - tristate "Freescale MPC52xx PSC SPI controller" - depends on PPC_MPC52xx - help - This enables using the Freescale MPC52xx Programmable Serial - Controller in master SPI mode. - -config SPI_MPC512x_PSC - tristate "Freescale MPC512x PSC SPI controller" - depends on PPC_MPC512x - help - This enables using the Freescale MPC5121 Programmable Serial - Controller in SPI master mode. - -config SPI_FSL_LIB - tristate - depends on OF - -config SPI_FSL_CPM - tristate - depends on FSL_SOC - -config SPI_FSL_SPI - tristate "Freescale SPI controller and Aeroflex Gaisler GRLIB SPI controller" - depends on OF - select SPI_FSL_LIB - select SPI_FSL_CPM if FSL_SOC - help - This enables using the Freescale SPI controllers in master mode. - MPC83xx platform uses the controller in cpu mode or CPM/QE mode. - MPC8569 uses the controller in QE mode, MPC8610 in cpu mode. - This also enables using the Aeroflex Gaisler GRLIB SPI controller in - master mode. - -config SPI_FSL_DSPI - tristate "Freescale DSPI controller" - select REGMAP_MMIO - depends on ARCH_MXC || ARCH_NXP || M5441x || COMPILE_TEST - help - This enables support for the Freescale DSPI controller in master - mode. S32, VF610, LS1021A and ColdFire platforms uses the controller. - -config SPI_FSL_ESPI - tristate "Freescale eSPI controller" - depends on FSL_SOC - help - This enables using the Freescale eSPI controllers in master mode. - From MPC8536, 85xx platform uses the controller, and all P10xx, - P20xx, P30xx,P40xx, P50xx uses this controller. - -config SPI_LJCA - tristate "Intel La Jolla Cove Adapter SPI support" - depends on USB_LJCA - default USB_LJCA - help - Select this option to enable SPI driver for the Intel - La Jolla Cove Adapter (LJCA) board. - - This driver can also be built as a module. If so, the module - will be called spi-ljca. - config SPI_MA35D1_QSPI tristate "Nuvoton MA35D1 QSPI controller" depends on ARCH_MA35 || COMPILE_TEST @@ -735,6 +709,27 @@ config SPI_MICROCHIP_CORE_SPI PolarFire SoC. If built as a module, it will be called spi-microchip-core-spi. +config SPI_MPC52xx + tristate "Freescale MPC52xx SPI (non-PSC) controller support" + depends on PPC_MPC52xx + help + This drivers supports the MPC52xx SPI controller in master SPI + mode. + +config SPI_MPC52xx_PSC + tristate "Freescale MPC52xx PSC SPI controller" + depends on PPC_MPC52xx + help + This enables using the Freescale MPC52xx Programmable Serial + Controller in master SPI mode. + +config SPI_MPC512x_PSC + tristate "Freescale MPC512x PSC SPI controller" + depends on PPC_MPC512x + help + This enables using the Freescale MPC5121 Programmable Serial + Controller in SPI master mode. + config SPI_MT65XX tristate "MediaTek SPI controller" depends on ARCH_MEDIATEK || COMPILE_TEST @@ -770,6 +765,19 @@ config SPI_MTK_SNFI is implemented as a SPI-MEM controller with pipelined ECC capability. +config SPI_MXIC + tristate "Macronix MX25F0A SPI controller" + imply MTD_NAND_ECC_MXIC + help + This selects the Macronix MX25F0A SPI controller driver. + +config SPI_MXS + tristate "Freescale MXS SPI controller" + depends on ARCH_MXS + select STMP_DEVICE + help + SPI driver for Freescale MXS devices. + config SPI_WPCM_FIU tristate "Nuvoton WPCM450 Flash Interface Unit" depends on ARCH_NPCM || COMPILE_TEST @@ -798,13 +806,26 @@ config SPI_NPCM_PSPI This driver provides support for Nuvoton NPCM BMC Peripheral SPI controller in master mode. -config SPI_LANTIQ_SSC - tristate "Lantiq SSC SPI controller" - depends on LANTIQ || X86 || COMPILE_TEST +config SPI_NXP_FLEXSPI + tristate "NXP Flex SPI controller" + depends on ARCH_LAYERSCAPE || ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM help - This driver supports the Lantiq SSC SPI controller in master - mode. This controller is found on Intel (former Lantiq) SoCs like - the Danube, Falcon, xRX200, xRX300, Lightning Mountain. + This enables support for the Flex SPI controller in master mode. + Up to four slave devices can be connected on two buses with two + chipselects each. + This controller does not support generic SPI messages and only + supports the high-level SPI memory interface. + +config SPI_NXP_XSPI + tristate "NXP xSPI controller" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM + help + This enables support for the xSPI controller. Up to two devices + can be connected to one host. + This controller does not support generic SPI messages and only + supports the high-level SPI memory interface. config SPI_OC_TINY tristate "OpenCores tiny SPI" @@ -835,14 +856,6 @@ config SPI_OMAP24XX SPI master controller for OMAP24XX and later Multichannel SPI (McSPI) modules. -config SPI_TI_QSPI - tristate "DRA7xxx QSPI controller support" - depends on ARCH_OMAP2PLUS || COMPILE_TEST - help - QSPI master controller for DRA7xxx used for flash devices. - This device supports single, dual and quad read support, while - it only supports single write mode. - config SPI_ORION tristate "Orion SPI master" depends on PLAT_ORION || ARCH_MVEBU || COMPILE_TEST @@ -1031,12 +1044,14 @@ config SPI_SC18IS602 help SPI driver for NXP SC18IS602/602B/603 I2C to SPI bridge. -config SPI_SH_MSIOF - tristate "SuperH MSIOF SPI controller" - depends on HAVE_CLK - depends on ARCH_SHMOBILE || ARCH_RENESAS || COMPILE_TEST +config SPI_SG2044_NOR + tristate "SG2044 SPI NOR Controller" + depends on ARCH_SOPHGO || COMPILE_TEST help - SPI driver for SuperH and SH Mobile MSIOF blocks. + This enables support for the SG2044 SPI NOR controller, + which supports Dual/Quad read and write operations while + also supporting 3Byte address devices and 4Byte address + devices. config SPI_SH tristate "SuperH SPI controller" @@ -1044,6 +1059,19 @@ config SPI_SH help SPI driver for SuperH SPI blocks. +config SPI_SH_HSPI + tristate "SuperH HSPI controller" + depends on ARCH_RENESAS || COMPILE_TEST + help + SPI driver for SuperH HSPI blocks. + +config SPI_SH_MSIOF + tristate "SuperH MSIOF SPI controller" + depends on HAVE_CLK + depends on ARCH_SHMOBILE || ARCH_RENESAS || COMPILE_TEST + help + SPI driver for SuperH and SH Mobile MSIOF blocks. + config SPI_SH_SCI tristate "SuperH SCI SPI controller" depends on SUPERH @@ -1051,28 +1079,12 @@ config SPI_SH_SCI help SPI driver for SuperH SCI blocks. -config SPI_SH_HSPI - tristate "SuperH HSPI controller" - depends on ARCH_RENESAS || COMPILE_TEST - help - SPI driver for SuperH HSPI blocks. - config SPI_SIFIVE tristate "SiFive SPI controller" depends on HAS_IOMEM help This exposes the SPI controller IP from SiFive. -config SPI_SLAVE_MT27XX - tristate "MediaTek SPI slave device" - depends on ARCH_MEDIATEK || COMPILE_TEST - depends on SPI_SLAVE - help - This selects the MediaTek(R) SPI slave device driver. - If you want to use MediaTek(R) SPI slave interface, - say Y or M here.If you are not sure, say N. - SPI slave drivers for Mediatek MT27XX series ARM SoCs. - config SPI_SN_F_OSPI tristate "Socionext F_OSPI SPI flash controller" depends on OF && HAS_IOMEM @@ -1082,15 +1094,6 @@ config SPI_SN_F_OSPI for connecting an SPI Flash memory over up to 8-bit wide bus. It supports indirect access mode only. -config SPI_SG2044_NOR - tristate "SG2044 SPI NOR Controller" - depends on ARCH_SOPHGO || COMPILE_TEST - help - This enables support for the SG2044 SPI NOR controller, - which supports Dual/Quad read and write operations while - also supporting 3Byte address devices and 4Byte address - devices. - config SPI_SPACEMIT_K1 tristate "K1 SPI Controller" depends on ARCH_SPACEMIT || COMPILE_TEST @@ -1185,19 +1188,6 @@ config SPI_SYNQUACER It also supports the new dual-bit and quad-bit SPI protocol. -config SPI_MXIC - tristate "Macronix MX25F0A SPI controller" - imply MTD_NAND_ECC_MXIC - help - This selects the Macronix MX25F0A SPI controller driver. - -config SPI_MXS - tristate "Freescale MXS SPI controller" - depends on ARCH_MXS - select STMP_DEVICE - help - SPI driver for Freescale MXS devices. - config SPI_TEGRA210_QUAD tristate "NVIDIA Tegra QSPI Controller" depends on ARCH_TEGRA || COMPILE_TEST @@ -1239,6 +1229,23 @@ config SPI_THUNDERX SPI host driver for the hardware found on Cavium ThunderX SOCs. +config SPI_TI_QSPI + tristate "DRA7xxx QSPI controller support" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + help + QSPI master controller for DRA7xxx used for flash devices. + This device supports single, dual and quad read support, while + it only supports single write mode. + +config SPI_TLE62X0 + tristate "Infineon TLE62X0 (for power switching)" + depends on SYSFS + help + SPI driver for Infineon TLE62X0 series line driver chips, + such as the TLE6220, TLE6230 and TLE6240. This provides a + sysfs interface, with each line presented as a kind of GPIO + exposing both switch control and diagnostic feedback. + config SPI_TOPCLIFF_PCH tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) SPI" depends on PCI && (X86_32 || MIPS || COMPILE_TEST) @@ -1331,14 +1338,6 @@ config SPI_ZYNQMP_GQSPI Enables Xilinx GQSPI controller driver for Zynq UltraScale+ MPSoC. This controller only supports SPI memory interface. -config SPI_AMD - tristate "AMD SPI controller" - depends on PCI - depends on X86 || COMPILE_TEST - depends on SPI_MEM - help - Enables SPI controller driver for AMD SoC. - # # Add new SPI master controllers in alphabetical order above this line # @@ -1387,15 +1386,6 @@ config SPI_LOOPBACK_TEST primarily used for development of spi_master drivers and to detect regressions -config SPI_TLE62X0 - tristate "Infineon TLE62X0 (for power switching)" - depends on SYSFS - help - SPI driver for Infineon TLE62X0 series line driver chips, - such as the TLE6220, TLE6230 and TLE6240. This provides a - sysfs interface, with each line presented as a kind of GPIO - exposing both switch control and diagnostic feedback. - # # Add new SPI protocol masters in alphabetical order above this line # @@ -1426,6 +1416,15 @@ config SPI_SLAVE_SYSTEM_CONTROL SPI slave handler to allow remote control of system reboot, power off, halt, and suspend. +config SPI_SLAVE_MT27XX + tristate "MediaTek SPI slave device" + depends on ARCH_MEDIATEK || COMPILE_TEST + help + This selects the MediaTek(R) SPI slave device driver. + If you want to use MediaTek(R) SPI slave interface, + say Y or M here.If you are not sure, say N. + SPI slave drivers for Mediatek MT27XX series ARM SoCs. + endif # SPI_SLAVE config SPI_DYNAMIC From 2cf55c64e64e1426def6f358d655ed036f2b9250 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 10:52:55 +0200 Subject: [PATCH 023/724] 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 024/724] 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 025/724] 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 152867998da9f331d6235d76aae1c7e1650708a9 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 22:01:33 +0800 Subject: [PATCH 026/724] spi: sh-msiof: propagate setup runtime-PM errors sh_msiof_spi_setup() ignores pm_runtime_get_sync() before programming native chip-select registers and marking the configuration initialized. Use the checked runtime-PM helper and return failure before register access. Fixes: 7ff0b53c4051 ("spi: sh-msiof: Avoid writing to registers from spi_master.setup()") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830140133.24156-1-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- drivers/spi/spi-sh-msiof.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c index 1aeab7ec0bc8..4c425ec62776 100644 --- a/drivers/spi/spi-sh-msiof.c +++ b/drivers/spi/spi-sh-msiof.c @@ -453,6 +453,7 @@ static int sh_msiof_spi_setup(struct spi_device *spi) struct sh_msiof_spi_priv *p = spi_controller_get_devdata(spi->controller); u32 clr, set, tmp; + int ret; if (spi_get_csgpiod(spi, 0) || spi_controller_is_target(p->ctlr)) return 0; @@ -468,7 +469,9 @@ static int sh_msiof_spi_setup(struct spi_device *spi) clr |= SIMDR1_SYNCAC; else set |= SIMDR1_SYNCAC; - pm_runtime_get_sync(&p->pdev->dev); + ret = pm_runtime_resume_and_get(&p->pdev->dev); + if (ret < 0) + return ret; tmp = sh_msiof_read(p, SITMDR1) & ~clr; sh_msiof_write(p, SITMDR1, tmp | set | SIMDR1_TRMD | SITMDR1_PCON); tmp = sh_msiof_read(p, SIRMDR1) & ~clr; From a99aacfb67df669aafd16bef787b68fb58442e9a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 18 Aug 2026 22:50:44 +0100 Subject: [PATCH 027/724] regmap: Allow a base register to be specified for the RAM regmap To allow testing of register maps with very large register values add support for specifying base address to the RAM regmap. Since the config struct is shared with the raw RAM regmap also update that, though we will not use it. Link: https://patch.msgid.link/20260818-regmap-kunit-cache-high-bit-v1-1-7e790676dea3@kernel.org Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 1 + drivers/base/regmap/regmap-ram.c | 26 ++++++++++++++++++++------ drivers/base/regmap/regmap-raw-ram.c | 21 +++++++++++++-------- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index a6e4689000af..c73036744468 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -322,6 +322,7 @@ static inline unsigned int regcache_get_index_by_order(const struct regmap *map, } struct regmap_ram_data { + unsigned int base_reg; unsigned int *vals; /* Allocatd by caller */ bool *read; bool *written; diff --git a/drivers/base/regmap/regmap-ram.c b/drivers/base/regmap/regmap-ram.c index c7356b0d8c83..a666bfb4d667 100644 --- a/drivers/base/regmap/regmap-ram.c +++ b/drivers/base/regmap/regmap-ram.c @@ -20,8 +20,11 @@ static int regmap_ram_write(void *context, unsigned int reg, unsigned int val) { struct regmap_ram_data *data = context; - data->vals[reg] = val; - data->written[reg] = true; + if (reg < data->base_reg) + return -EINVAL; + + data->vals[reg - data->base_reg] = val; + data->written[reg - data->base_reg] = true; return 0; } @@ -30,8 +33,11 @@ static int regmap_ram_read(void *context, unsigned int reg, unsigned int *val) { struct regmap_ram_data *data = context; - *val = data->vals[reg]; - data->read[reg] = true; + if (reg < data->base_reg) + return -EINVAL; + + *val = data->vals[reg - data->base_reg]; + data->read[reg - data->base_reg] = true; return 0; } @@ -60,17 +66,25 @@ struct regmap *__regmap_init_ram(struct device *dev, const char *lock_name) { struct regmap *map; + unsigned int num_regs; if (!config->max_register) { pr_crit("No max_register specified for RAM regmap\n"); return ERR_PTR(-EINVAL); } - data->read = kzalloc_objs(bool, config->max_register + 1); + if (data->base_reg > config->max_register) { + pr_crit("base_reg above max_register for RAM regmap\n"); + return ERR_PTR(-EINVAL); + } + + num_regs = config->max_register - data->base_reg + 1; + + data->read = kzalloc_objs(bool, num_regs); if (!data->read) return ERR_PTR(-ENOMEM); - data->written = kzalloc_objs(bool, config->max_register + 1); + data->written = kzalloc_objs(bool, num_regs); if (!data->written) { kfree(data->read); return ERR_PTR(-ENOMEM); diff --git a/drivers/base/regmap/regmap-raw-ram.c b/drivers/base/regmap/regmap-raw-ram.c index 60d6e95cdd1b..c63c464549f9 100644 --- a/drivers/base/regmap/regmap-raw-ram.c +++ b/drivers/base/regmap/regmap-raw-ram.c @@ -16,14 +16,17 @@ #include "internal.h" -static unsigned int decode_reg(enum regmap_endian endian, const void *reg) +static unsigned int decode_reg(struct regmap_ram_data *data, const void *reg) { const u16 *r = reg; + unsigned int decode; - if (endian == REGMAP_ENDIAN_BIG) - return be16_to_cpu(*r); + if (data->reg_endian == REGMAP_ENDIAN_BIG) + decode = be16_to_cpu(*r); else - return le16_to_cpu(*r); + decode = le16_to_cpu(*r); + + return decode - data->base_reg; } static int regmap_raw_ram_gather_write(void *context, @@ -40,7 +43,7 @@ static int regmap_raw_ram_gather_write(void *context, if (val_len % 2) return -EINVAL; - r = decode_reg(data->reg_endian, reg); + r = decode_reg(data, reg); if (data->noinc_reg && data->noinc_reg(data, r)) { memcpy(&our_buf[r], val + val_len - 2, 2); data->written[r] = true; @@ -74,7 +77,7 @@ static int regmap_raw_ram_read(void *context, if (val_len % 2) return -EINVAL; - r = decode_reg(data->reg_endian, reg); + r = decode_reg(data, reg); if (data->noinc_reg && data->noinc_reg(data, r)) { for (i = 0; i < val_len; i += 2) memcpy(val + i, &our_buf[r], 2); @@ -114,6 +117,7 @@ struct regmap *__regmap_init_raw_ram(struct device *dev, const char *lock_name) { struct regmap *map; + unsigned int regs; if (config->reg_bits != 16) return ERR_PTR(-EINVAL); @@ -123,11 +127,12 @@ struct regmap *__regmap_init_raw_ram(struct device *dev, return ERR_PTR(-EINVAL); } - data->read = kzalloc_objs(bool, config->max_register + 1); + regs = config->max_register - data->base_reg + 1; + data->read = kzalloc_objs(bool, regs); if (!data->read) return ERR_PTR(-ENOMEM); - data->written = kzalloc_objs(bool, config->max_register + 1); + data->written = kzalloc_objs(bool, regs); if (!data->written) return ERR_PTR(-ENOMEM); From b26da6db3da90a2ad84f686c6635c45697c3fb06 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 18 Aug 2026 22:50:45 +0100 Subject: [PATCH 028/724] regmap: Test rbtree and maple caches for very high register numbers We've had bugs in the cache code with handling of register numbers with the top bit set before, add some test cases for this. It's not really worth fixing up all the individual tests to support a base address, instead write a specific test that reproduces a bunch of the coverge. Run this on the rbtree and maple caches which are the only ones that can reasonably support this case. Link: https://patch.msgid.link/20260818-regmap-kunit-cache-high-bit-v1-2-7e790676dea3@kernel.org Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-kunit.c | 98 +++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/drivers/base/regmap/regmap-kunit.c b/drivers/base/regmap/regmap-kunit.c index 2999d9c185d5..c78772b660d6 100644 --- a/drivers/base/regmap/regmap-kunit.c +++ b/drivers/base/regmap/regmap-kunit.c @@ -11,6 +11,8 @@ #define BLOCK_TEST_SIZE 12 +#define HIGH_BIT_REG ((UINT_MAX / 2) - 3) + KUNIT_DEFINE_ACTION_WRAPPER(regmap_exit_action, regmap_exit, struct regmap *); struct regmap_test_priv { @@ -23,6 +25,7 @@ struct regmap_test_param { enum regcache_type cache; enum regmap_endian val_endian; + unsigned int base_reg; unsigned int from_reg; bool fast_io; }; @@ -204,7 +207,8 @@ static struct regmap *gen_regmap(struct kunit *test, config->max_register += (BLOCK_TEST_SIZE * config->reg_stride); } - size = array_size(config->max_register + 1, sizeof(*buf)); + size = array_size(config->max_register - param->base_reg + 1, + sizeof(*buf)); buf = kmalloc(size, GFP_KERNEL); if (!buf) return ERR_PTR(-ENOMEM); @@ -215,6 +219,7 @@ static struct regmap *gen_regmap(struct kunit *test, if (!(*data)) goto out_free; (*data)->vals = buf; + (*data)->base_reg = param->base_reg; if (config->num_reg_defaults) { defaults = kunit_kcalloc(test, @@ -227,8 +232,8 @@ static struct regmap *gen_regmap(struct kunit *test, config->reg_defaults = defaults; for (i = 0; i < config->num_reg_defaults; i++) { - defaults[i].reg = param->from_reg + (i * config->reg_stride); - defaults[i].def = buf[param->from_reg + (i * config->reg_stride)]; + defaults[i].reg = param->from_reg - param->base_reg + (i * config->reg_stride); + defaults[i].def = buf[param->from_reg - param->base_reg + (i * config->reg_stride)]; } } @@ -1648,6 +1653,92 @@ static void cache_write_zero(struct kunit *test) KUNIT_ASSERT_FALSE(test, regcache_reg_cached(map, 1)); } +static const struct regmap_test_param high_bit_cache_types_list[] = { + { .cache = REGCACHE_RBTREE, .from_reg = HIGH_BIT_REG, + .base_reg = HIGH_BIT_REG }, + { .cache = REGCACHE_MAPLE, .from_reg = HIGH_BIT_REG, + .base_reg = HIGH_BIT_REG }, +}; + +KUNIT_ARRAY_PARAM(high_bit_cache_types, high_bit_cache_types_list, param_to_desc); + +/* + * Dynamically allocated cache types should support registers with the + * high bit set, the backend will return an error for any operation on + * a register below the expected window. + */ +static void cache_high_bit_reg(struct kunit *test) +{ + const struct regmap_test_param *param = test->param_value; + struct regmap_ram_data *data; + struct regmap_config config; + struct regmap *map; + unsigned int val[BLOCK_TEST_SIZE], rval; + int i; + + config = test_regmap_config; + + map = gen_regmap(test, &config, &data); + KUNIT_ASSERT_FALSE(test, IS_ERR(map)); + if (IS_ERR(map)) + return; + + get_random_bytes(&val, sizeof(val)); + + /* No defaults so no registers cached */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) + KUNIT_EXPECT_FALSE(test, regcache_reg_cached(map, param->from_reg + i)); + + /* Writes should reach the device */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) + KUNIT_EXPECT_EQ(test, 0, regmap_write(map, param->from_reg + i, + val[i])); + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + KUNIT_EXPECT_TRUE(test, data->written[i]); + KUNIT_EXPECT_EQ(test, val[i], data->vals[i]); + } + + /* Reads should be satisfied from the cache */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + KUNIT_EXPECT_TRUE(test, regcache_reg_cached(map, param->from_reg + i)); + KUNIT_EXPECT_EQ(test, 0, regmap_read(map, param->from_reg + i, + &rval)); + KUNIT_EXPECT_EQ(test, val[i], rval); + KUNIT_EXPECT_FALSE(test, data->read[i]); + } + + /* Trash the data on the device then resync */ + regcache_mark_dirty(map); + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + data->vals[i] = 0; + data->written[i] = false; + } + KUNIT_EXPECT_EQ(test, 0, regcache_sync(map)); + + /* Did we just write the correct data out? */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + KUNIT_EXPECT_TRUE(test, data->written[i]); + KUNIT_EXPECT_EQ(test, val[i], data->vals[i]); + } + + /* Drop some registers */ + KUNIT_EXPECT_EQ(test, 0, regcache_drop_region(map, param->from_reg + 3, + param->from_reg + 5)); + for (i = 0; i < BLOCK_TEST_SIZE; i++) + KUNIT_EXPECT_EQ(test, i < 3 || i > 5, + regcache_reg_cached(map, param->from_reg + i)); + + /* Reread and check only the dropped registers hit the device */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) + data->read[i] = false; + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + KUNIT_EXPECT_EQ(test, 0, regmap_read(map, param->from_reg + i, + &rval)); + KUNIT_EXPECT_EQ(test, val[i], rval); + KUNIT_EXPECT_EQ(test, i >= 3 && i <= 5, data->read[i]); + } +} + /* Check that caching the window register works with sync */ static void cache_range_window_reg(struct kunit *test) { @@ -2168,6 +2259,7 @@ static struct kunit_case regmap_test_cases[] = { KUNIT_CASE_PARAM(cache_drop_all_and_sync_has_defaults, sparse_cache_types_gen_params), KUNIT_CASE_PARAM(cache_present, sparse_cache_types_gen_params), KUNIT_CASE_PARAM(cache_write_zero, sparse_cache_types_gen_params), + KUNIT_CASE_PARAM(cache_high_bit_reg, high_bit_cache_types_gen_params), KUNIT_CASE_PARAM(cache_range_window_reg, real_cache_types_only_gen_params), KUNIT_CASE_PARAM(raw_read_defaults_single, raw_test_types_gen_params), From e152ac60589bcb6a5743930fe7a83ea2e9ffbf4f Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Mon, 31 Aug 2026 21:32:40 +1000 Subject: [PATCH 029/724] 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 030/724] 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 031/724] 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 7fe2098d612b35fc0aaa7a550b0adca326fedcee Mon Sep 17 00:00:00 2001 From: Andrew Gaylard Date: Thu, 20 Aug 2026 14:58:35 +0200 Subject: [PATCH 032/724] spi: sunplus: handle signal interruption in transfer wait wait_for_completion_interruptible_timeout() returns -ERESTARTSYS when interrupted by a signal, 0 on timeout, and positive on success. The previous check was: if (!wait_for_completion_interruptible_timeout(...)) SIGKILL caused the interrupted path to fall through as if the transfer succeeded. The loop then re-entered mutex_lock() on the next iteration, which is TASK_UNINTERRUPTIBLE. The process could not be killed while blocked there. Check ret <= 0 and return -EINTR for the interrupted case so the process can exit promptly on SIGKILL. Signed-off-by: Andrew Gaylard Link: https://patch.msgid.link/20260820125835.1584270-1-ag@ffroot.co.za Signed-off-by: Mark Brown --- drivers/spi/spi-sunplus-sp7021.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-sunplus-sp7021.c b/drivers/spi/spi-sunplus-sp7021.c index f16fcda187dd..54431c363bbc 100644 --- a/drivers/spi/spi-sunplus-sp7021.c +++ b/drivers/spi/spi-sunplus-sp7021.c @@ -337,10 +337,15 @@ static int sp7021_spi_host_transfer_one(struct spi_controller *ctlr, struct spi_ SP7021_SPI_START_FD; writel(reg_temp, pspim->m_base + SP7021_SPI_STATUS_REG); - if (!wait_for_completion_interruptible_timeout(&pspim->isr_done, timeout)) { - dev_err(&spi->dev, "wait_for_completion err\n"); - mutex_unlock(&pspim->buf_lock); - return -ETIMEDOUT; + { + long ret = wait_for_completion_interruptible_timeout( + &pspim->isr_done, timeout); + if (ret <= 0) { + dev_err(&spi->dev, ret == 0 ? "SPI transfer timeout\n" + : "SPI transfer interrupted\n"); + mutex_unlock(&pspim->buf_lock); + return ret == 0 ? -ETIMEDOUT : -EINTR; + } } reg_temp = readl(pspim->m_base + SP7021_SPI_STATUS_REG); From ed07cdb2b386d53d1ba4abbb4154bcec7bd9488a Mon Sep 17 00:00:00 2001 From: Viken Dadhaniya Date: Mon, 24 Aug 2026 15:43:58 +0530 Subject: [PATCH 033/724] spi: qcom-geni: rename setup_fifo_params() to setup_spi_params() and make it void The function always returned 0 and had no error paths, so change its return type to void. Drop the now-dead ret variable and error check in spi_geni_prepare_message(). setup_fifo_params() is called for both GENI_SE_FIFO and GENI_SE_DMA modes, so the "fifo" in the name is misleading. Rename it to setup_spi_params() to better reflect its purpose of configuring SPI mode parameters (CS, CPHA, CPOL, loopback, LSB-first). No functional change. Signed-off-by: Viken Dadhaniya Reviewed-by: Konrad Dybcio Reviewed-by: Mukesh Kumar Savaliya Link: https://patch.msgid.link/20260824-spi-qcom-geni-cleanup-setup-fifo-params-v1-1-bdf98ae62953@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 6c57f8309a3b..05abaf18a0f4 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -363,8 +363,8 @@ static int geni_spi_set_clock_and_bw(struct geni_se *se, return 0; } -static int setup_fifo_params(struct spi_device *spi_slv, - struct spi_controller *spi) +static void setup_spi_params(struct spi_device *spi_slv, + struct spi_controller *spi) { struct spi_geni_master *mas = spi_controller_get_devdata(spi); struct geni_se *se = &mas->se; @@ -390,8 +390,6 @@ static int setup_fifo_params(struct spi_device *spi_slv, trace_geni_spi_setup_params(mas->dev, chipselect, spi_slv->mode, mode_changed, cs_changed); - - return 0; } static void @@ -554,17 +552,14 @@ static int spi_geni_prepare_message(struct spi_controller *spi, struct spi_message *spi_msg) { struct spi_geni_master *mas = spi_controller_get_devdata(spi); - int ret; switch (mas->cur_xfer_mode) { case GENI_SE_FIFO: case GENI_SE_DMA: if (spi_geni_is_abort_still_pending(mas)) return -EBUSY; - ret = setup_fifo_params(spi_msg->spi, spi); - if (ret) - dev_err(mas->dev, "Couldn't select mode %d\n", ret); - return ret; + setup_spi_params(spi_msg->spi, spi); + return 0; case GENI_GPI_DMA: /* nothing to do for GPI DMA */ @@ -700,7 +695,7 @@ static int spi_geni_init(struct spi_geni_master *mas) case 0: mas->cur_xfer_mode = GENI_SE_FIFO; geni_se_select_mode(se, GENI_SE_FIFO); - /* setup_fifo_params assumes that these registers start with a zero value */ + /* setup_spi_params assumes that these registers start with a zero value */ writel(0, se->base + SE_SPI_LOOPBACK); writel(0, se->base + SE_SPI_DEMUX_SEL); writel(0, se->base + SE_SPI_CPHA); From 17bca5d55446c1e21944d47354ee2f0b80db36d6 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Sun, 23 Aug 2026 11:09:55 +0200 Subject: [PATCH 034/724] 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 035/724] 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 5f01cb141169fe422c42279f7d502479b1ff9fd2 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Wed, 26 Aug 2026 01:08:40 +0800 Subject: [PATCH 036/724] spi: omap2-mcspi: Remove unbalanced pm_runtime_put_sync() calls pm_runtime_put_sync() is called in the probe error path and in remove when nothing holds a runtime PM reference anymore, so it underflows the usage count. Fixes: 0e6f357a5deb ("spi: omap2-mcspi: Fix PM regression with deferred probe for pm_runtime_reinit") Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260826-mcspi-v1-1-0a8dd7f6dd56@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-omap2-mcspi.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index a3355147f5de..82c8b91e504e 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -1595,7 +1595,6 @@ static int omap2_mcspi_probe(struct platform_device *pdev) err_disable_rpm: pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); err_release_dma: omap2_mcspi_release_dma(ctlr); @@ -1613,7 +1612,6 @@ static void omap2_mcspi_remove(struct platform_device *pdev) omap2_mcspi_release_dma(ctlr); pm_runtime_dont_use_autosuspend(mcspi->dev); - pm_runtime_put_sync(mcspi->dev); pm_runtime_disable(&pdev->dev); } From caae0e2d17185a2489365f3f97d8a5b09a9da638 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 14 Aug 2026 13:50:18 -0700 Subject: [PATCH 037/724] 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 038/724] 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 039/724] 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 040/724] 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 041/724] 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 042/724] 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 043/724] 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 044/724] 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 045/724] 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 046/724] 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 2e42cade8ff1ff579e77976d9869db4df1feaf74 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 1 Sep 2026 22:55:42 +0100 Subject: [PATCH 047/724] regmap: irq: Free the irqdomain we create When domain support was added to regmap-irq it was not possible to remove domains, this was added later by 8ee99ada293b (irqdomain: Support removal of IRQ domains.). We did update the main removal path to free the domain but forgot the error unwinding case during creation that is now in regmap_add_irq_chip_fwnode() after some code motion, meaning that errors during instantiation result in an unused irqchip being left hanging around. Add the missing irq_remove_domain() call where the comment says it should be. Reported-by: Farhad Alemi Reported-by: Thomas Gleixner Link: https://patch.msgid.link/20260901-regmap-irq-deallocate-domain-v1-1-4bfed4aff559@kernel.org Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c index 99b55b1053ee..715eb9e7aa2a 100644 --- a/drivers/base/regmap/regmap-irq.c +++ b/drivers/base/regmap/regmap-irq.c @@ -963,7 +963,7 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode, return 0; err_domain: - /* Should really dispose of the domain but... */ + irq_domain_remove(d->domain); err_mutex: mutex_destroy(&d->lock); lockdep_unregister_key(&d->lock_key); 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 048/724] 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 9e6ad2ba055f89df9f14930e50dc6a997b71e9ad Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 30 Aug 2026 10:40:47 +0200 Subject: [PATCH 049/724] 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 050/724] 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 051/724] 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 052/724] 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 8e51dd6db0bc7fa888326c913edcb8fe8c1fdc16 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:31:09 +0530 Subject: [PATCH 053/724] regmap: fix typo "asynchrnous" in comment Correct "asynchrnous" to "asynchronous", 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 Link: https://patch.msgid.link/20260904110110.8823-1-hemanth.selam@gmail.com Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index e6e022b02637..98184a00097a 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -2722,7 +2722,7 @@ EXPORT_SYMBOL_GPL(regmap_multi_reg_write_bypassed); * If supported by the underlying bus the write will be scheduled * asynchronously, helping maximise I/O speed on higher speed buses * like SPI. regmap_async_complete() can be called to ensure that all - * asynchrnous writes have been completed. + * asynchronous writes have been completed. * * A value of zero will be returned on success, a negative errno will * be returned in error cases. From 3958b39fa5b76d692ed548ce6c7c563f465ff8fb Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Tue, 18 Aug 2026 18:58:07 +0530 Subject: [PATCH 054/724] spi: qcom-geni: Add shutdown callback to quiesce hardware on reboot During system reboot, an active SPI transfer can leave the GENI Serial Engine in an indeterminate state. On VM-based platforms, if a DMA transfer is in progress when the VM is shut down, the SMMU can raise context faults as the DMA engine continues to access IOVAs that have already been invalidated during VM teardown. Add a shutdown callback to suspend the SPI controller and abort any in-progress transfer, ensuring the DMA engine is idle and all IOVA mappings are retired before the system resets. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260818-add-shutdown-and-panic-notifier-for-spi-v3-1-8b62c4bc2d21@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 6c57f8309a3b..c65c1788325d 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -1066,6 +1066,13 @@ static int spi_geni_target_abort(struct spi_controller *spi) return 0; } +static void spi_geni_shutdown(struct platform_device *pdev) +{ + struct spi_controller *spi = platform_get_drvdata(pdev); + + spi_controller_suspend(spi); +} + static int spi_geni_probe(struct platform_device *pdev) { int ret, irq; @@ -1241,7 +1248,8 @@ static const struct of_device_id spi_geni_dt_match[] = { MODULE_DEVICE_TABLE(of, spi_geni_dt_match); static struct platform_driver spi_geni_driver = { - .probe = spi_geni_probe, + .probe = spi_geni_probe, + .shutdown = spi_geni_shutdown, .driver = { .name = "geni_spi", .pm = pm_ptr(&spi_geni_pm_ops), From b7b192e58f353424aa26bef5da88b2a3cb20049e Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Tue, 18 Aug 2026 18:58:08 +0530 Subject: [PATCH 055/724] spi: qcom-geni: Add panic notifier to cancel and reset DMA during panic When a VM crashes with an active SPI DMA transfer in progress, the SMMU raises context faults as the DMA engine continues to access IOVAs that are invalidated when the VM's memory context is torn down. These faults can affect other VMs sharing the same SMMU instance and obscure the root cause of the crash. Register a panic notifier that cancels (or aborts, if cancel doesn't complete) the in-flight command and resets the TX/RX DMA FSMs, so the DMA engine stops issuing transactions against invalidated IOVAs before the system halts. For GPI DMA mode, the DMA channels are terminated directly via dmaengine_terminate_async(). The notifier bails out early if the device is not runtime-active or if there's no active GENI command, avoiding unnecessary register accesses while the SE is clock-gated or idle. Since panic notifiers run with IRQs and preemption disabled, completion-based waits used by the regular error-handling path (handle_se_timeout()) cannot be reused here. Instead, the relevant status registers are polled directly with readl_poll_timeout_atomic(), which is safe to call in this context. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260818-add-shutdown-and-panic-notifier-for-spi-v3-2-8b62c4bc2d21@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/spi/spi-geni-qcom.c | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index c65c1788325d..f272bd0cb640 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -12,8 +12,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -115,6 +117,7 @@ struct spi_geni_master { struct dma_chan *rx; int cur_xfer_mode; const struct geni_spi_desc *dev_data; + struct notifier_block panic_nb; }; static void spi_slv_setup(struct spi_geni_master *mas) @@ -1073,6 +1076,62 @@ static void spi_geni_shutdown(struct platform_device *pdev) spi_controller_suspend(spi); } +static int spi_geni_panic_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct spi_geni_master *mas = container_of(nb, struct spi_geni_master, panic_nb); + struct spi_controller *spi = dev_get_drvdata(mas->dev); + struct geni_se *se = &mas->se; + u32 val; + + if (!pm_runtime_active(mas->dev)) + return NOTIFY_OK; + + if (mas->cur_xfer_mode == GENI_GPI_DMA) { + dmaengine_terminate_async(mas->tx); + dmaengine_terminate_async(mas->rx); + return NOTIFY_OK; + } + + if (!(readl_relaxed(se->base + SE_GENI_STATUS) & M_GENI_CMD_ACTIVE)) + return NOTIFY_OK; + + if (!spi->target) { + geni_se_cancel_m_cmd(se); + if (!readl_poll_timeout_atomic(se->base + SE_GENI_M_IRQ_STATUS, val, + val & M_CMD_CANCEL_EN, 10, 50000)) { + writel_relaxed(M_CMD_CANCEL_EN, se->base + SE_GENI_M_IRQ_CLEAR); + return NOTIFY_OK; + } + } + + geni_se_abort_m_cmd(se); + if (!readl_poll_timeout_atomic(se->base + SE_GENI_M_IRQ_STATUS, val, + val & M_CMD_ABORT_EN, 10, 50000)) + writel_relaxed(M_CMD_ABORT_EN, se->base + SE_GENI_M_IRQ_CLEAR); + + if (mas->cur_xfer_mode == GENI_SE_DMA) { + writel_relaxed(1, se->base + SE_DMA_TX_FSM_RST); + readl_poll_timeout_atomic(se->base + SE_DMA_TX_IRQ_STAT, val, + val & TX_RESET_DONE, 10, 50000); + writel_relaxed(val, se->base + SE_DMA_TX_IRQ_CLR); + + writel_relaxed(1, se->base + SE_DMA_RX_FSM_RST); + readl_poll_timeout_atomic(se->base + SE_DMA_RX_IRQ_STAT, val, + val & RX_RESET_DONE, 10, 50000); + writel_relaxed(val, se->base + SE_DMA_RX_IRQ_CLR); + } + + return NOTIFY_OK; +} + +static void spi_geni_unregister_notifiers(void *data) +{ + struct spi_geni_master *mas = data; + + atomic_notifier_chain_unregister(&panic_notifier_list, &mas->panic_nb); +} + static int spi_geni_probe(struct platform_device *pdev) { int ret, irq; @@ -1161,6 +1220,15 @@ static int spi_geni_probe(struct platform_device *pdev) if (ret) return ret; + mas->panic_nb.notifier_call = spi_geni_panic_notifier; + ret = atomic_notifier_chain_register(&panic_notifier_list, &mas->panic_nb); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, spi_geni_unregister_notifiers, mas); + if (ret) + return ret; + return devm_spi_register_controller(dev, spi); } From 29359ee126bdb8222eb1b74c32dc63ab1f921584 Mon Sep 17 00:00:00 2001 From: Xianwei Zhao Date: Fri, 31 Jul 2026 09:08:25 +0000 Subject: [PATCH 056/724] spi: dt-bindings: amlogic: spisg: Document A9-specific properties Document the Amlogic A9 SPISG controller and its additional Device Tree properties. Add the "amlogic,a9-spisg" compatible string and define the A9-specific "amlogic,mo-idle-output" property, which configure the MOSI idle output level. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Xianwei Zhao Link: https://patch.msgid.link/20260731-a9-spisg-v3-1-a15da3f70029@amlogic.com Signed-off-by: Mark Brown --- .../bindings/spi/amlogic,a4-spisg.yaml | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml b/Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml index 9bfb8089f7ea..427d8883009c 100644 --- a/Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml +++ b/Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml @@ -11,12 +11,11 @@ maintainers: - Xianwei Zhao - Sunny Luo -allOf: - - $ref: spi-controller.yaml# - properties: compatible: - const: amlogic,a4-spisg + enum: + - amlogic,a4-spisg + - amlogic,a9-spisg reg: maxItems: 1 @@ -35,6 +34,16 @@ properties: resets: maxItems: 1 + amlogic,mo-idle-output: + description: | + Controls the MOSI output level when the controller is idle. + 0 - Drive MOSI low + 1 - Drive MOSI high + 2 - Follow the last transmitted bit + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2] + default: 0 + required: - compatible - reg @@ -42,6 +51,19 @@ required: - clocks - clock-names +allOf: + - $ref: spi-controller.yaml# + - if: + not: + properties: + compatible: + contains: + enum: + - amlogic,a9-spisg + then: + properties: + amlogic,mo-idle-output: false + unevaluatedProperties: false examples: From 754d2bbd92f5ecef0eb4f5b1ac7b1417320a56ca Mon Sep 17 00:00:00 2001 From: Xianwei Zhao Date: Fri, 31 Jul 2026 09:08:26 +0000 Subject: [PATCH 057/724] spi: amlogic: spisg: Fix transfer size limit and timeout handling The CFG_BLOCK_NUM field can encode at most 20 bits, so reduce SPISG_BLOCK_MAX to 0xfffff to avoid programming a zero-length transfer. Perform the delay calculation in 64-bit arithmetic to avoid overflow when converting nanoseconds to SPI clock cycles. Stop the controller on transfer timeout by clearing the descriptor list register before returning an error. Fixes: cef9991e04ae ("spi: Add Amlogic SPISG driver") Signed-off-by: Xianwei Zhao Link: https://patch.msgid.link/20260731-a9-spisg-v3-2-a15da3f70029@amlogic.com Signed-off-by: Mark Brown --- drivers/spi/spi-amlogic-spisg.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c index 9049a87e9d0f..58dc6fedd8bc 100644 --- a/drivers/spi/spi-amlogic-spisg.c +++ b/drivers/spi/spi-amlogic-spisg.c @@ -95,7 +95,7 @@ #define SPISG_MAX_REG 0x40 -#define SPISG_BLOCK_MAX 0x100000 +#define SPISG_BLOCK_MAX 0xFFFFF #define SPISG_OP_MODE_WRITE_CMD 0 #define SPISG_OP_MODE_READ_STS 1 @@ -175,7 +175,7 @@ static int spi_delay_to_sclk(u32 slck_speed_hz, struct spi_delay *delay) if (ns < 0) return 0; - return DIV_ROUND_UP_ULL(slck_speed_hz * ns, NSEC_PER_SEC); + return DIV_ROUND_UP_ULL((u64)slck_speed_hz * ns, NSEC_PER_SEC); } static inline u32 aml_spisg_sem_down_read(struct spisg_device *spisg) @@ -565,10 +565,13 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, aml_spisg_pending(spisg, descs_paddr, false, true); if (wait_for_completion_timeout(&spisg->completion, spi_controller_is_target(spisg->controller) ? - MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(ms))) + MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(ms))) { ret = spisg->status ? -EIO : 0; - else + } else { + /* stop transfer */ + regmap_write(spisg->map, SPISG_REG_DESC_LIST_H, 0); ret = -ETIMEDOUT; + } dma_unmap_single(dev, descs_paddr, descs_len, DMA_TO_DEVICE); end: From 253dfb1839c6bc357aebe8aac4f1b4dcc20ee322 Mon Sep 17 00:00:00 2001 From: Sunny Luo Date: Fri, 31 Jul 2026 09:08:27 +0000 Subject: [PATCH 058/724] spi: amlogic: spisg: Fix the incorrect keep_ss of the last descriptor The driver currently unconditionally clears CFG_KEEP_SS on the last descriptor, causing the last transfer's cs_change setting to be ignored. Record the cs_change value of the last SPI transfer and use it to program CFG_KEEP_SS on the final descriptor. When a null descriptor is inserted to implement the cs-hold delay, keep CFG_KEEP_SS set on the preceding transfer descriptor and apply the recorded value to the final descriptor instead. This ensures the controller handles chip select correctly for the last transfer regardless of whether a cs-hold delay is required. Fixes: cef9991e04ae ("spi: Add Amlogic SPISG driver") Signed-off-by: Sunny Luo Signed-off-by: Xianwei Zhao Link: https://patch.msgid.link/20260731-a9-spisg-v3-3-a15da3f70029@amlogic.com Signed-off-by: Mark Brown --- drivers/spi/spi-amlogic-spisg.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c index 58dc6fedd8bc..6bca2379e4ee 100644 --- a/drivers/spi/spi-amlogic-spisg.c +++ b/drivers/spi/spi-amlogic-spisg.c @@ -489,6 +489,7 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, struct spisg_descriptor_extra *exdescs, *exdesc; dma_addr_t descs_paddr; int desc_num = 1, descs_len; + bool last_xfer_keep_ss = false; u32 cs_hold_in_sclk = 0; int ret = -EIO; @@ -529,9 +530,11 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->cs_setup)); /* calculate cs-hold delay with the last xfer speed */ - if (list_is_last(&xfer->transfer_list, &msg->transfers)) + if (list_is_last(&xfer->transfer_list, &msg->transfers)) { cs_hold_in_sclk = spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->cs_hold); + last_xfer_keep_ss = xfer->cs_change; + } desc++; exdesc++; @@ -539,13 +542,17 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, xfer->effective_speed_hz); } - if (cs_hold_in_sclk) + if (cs_hold_in_sclk) { /* additional null-descriptor to achieve the cs-hold delay */ aml_spisg_setup_null_desc(spisg, desc, cs_hold_in_sclk); - else desc--; + desc->cfg_bus |= FIELD_PREP(CFG_KEEP_SS, 1); + desc++; + } else { + desc--; + } - desc->cfg_bus |= FIELD_PREP(CFG_KEEP_SS, 0); + FIELD_MODIFY(CFG_KEEP_SS, &desc->cfg_bus, last_xfer_keep_ss); desc->cfg_start |= FIELD_PREP(CFG_EOC, 1); /* some tolerances */ From b552119acd63658bbfd86ee55ac802df4c9ab3f1 Mon Sep 17 00:00:00 2001 From: Xianwei Zhao Date: Fri, 31 Jul 2026 09:08:28 +0000 Subject: [PATCH 059/724] spi: amlogic: spisg: Add support for A9 controller features The Amlogic A9 SPISG controller extends the A4 controller with additional configuration options, including: - Extended CS setup timing - Hardware-controlled CS hold timing - MOSI idle output configuration - Configurable word delay Add SoC-specific capability data and configure these features when they are supported by the underlying hardware while keeping compatibility with existing A4 controllers. Signed-off-by: Xianwei Zhao Link: https://patch.msgid.link/20260731-a9-spisg-v3-4-a15da3f70029@amlogic.com Signed-off-by: Mark Brown --- drivers/spi/spi-amlogic-spisg.c | 75 ++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c index 6bca2379e4ee..5ace2c927da0 100644 --- a/drivers/spi/spi-amlogic-spisg.c +++ b/drivers/spi/spi-amlogic-spisg.c @@ -37,6 +37,12 @@ #define CFG_HW_POS BIT(6) /* start on vsync falling */ #define CFG_HW_NEG BIT(7) +#define CFG_WORD_GAP GENMASK(9, 8) +#define CFG_MO_IDLE_OUTPUT GENMASK(11, 10) +/* cs hold time in pclk */ +#define CFG_CS_HOLD GENMASK(26, 12) +/* high 4 bits of cs setup time in sclk */ +#define CFG_CS_SETUP_EXTEND GENMASK(30, 27) #define SPISG_REG_CFG_START 0x08 #define CFG_BLOCK_NUM GENMASK(19, 0) @@ -143,6 +149,13 @@ struct spisg_descriptor_extra { int rx_ccsg_len; }; +struct aml_spisg_data { + bool mo_idle_output_ctrl; + bool word_gap_ctrl; + bool cs_hold_ctrl; + bool cs_setup_extend_ctrl; +}; + struct spisg_device { struct spi_controller *controller; struct platform_device *pdev; @@ -152,6 +165,7 @@ struct spisg_device { struct clk *sclk; struct clk_div_table *tbl; struct completion completion; + const struct aml_spisg_data *data; u32 status; u32 speed_hz; u32 effective_speed_hz; @@ -424,7 +438,8 @@ static void aml_spisg_setup_null_desc(struct spisg_device *spisg, static void aml_spisg_pending(struct spisg_device *spisg, dma_addr_t desc_paddr, bool trig, - bool irq_en) + bool irq_en, + u32 delay) { u32 desc_l, desc_h, cfg_spi, irq_enable; @@ -437,6 +452,13 @@ static void aml_spisg_pending(struct spisg_device *spisg, #endif cfg_spi = spisg->cfg_spi; + + if (spisg->data && spisg->data->word_gap_ctrl) { + if (delay > 3) + delay = 3; + cfg_spi |= FIELD_PREP(CFG_WORD_GAP, delay); + } + if (trig) cfg_spi |= CFG_HW_POS; else @@ -483,6 +505,7 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, { struct spisg_device *spisg = spi_controller_get_devdata(ctlr); struct device *dev = &spisg->pdev->dev; + const struct aml_spisg_data *data = spisg->data; unsigned long long ms = 0; struct spi_transfer *xfer; struct spisg_descriptor *descs, *desc; @@ -491,6 +514,7 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, int desc_num = 1, descs_len; bool last_xfer_keep_ss = false; u32 cs_hold_in_sclk = 0; + u32 val, delay = 0; int ret = -EIO; if (!aml_spisg_sem_down_read(spisg)) { @@ -524,10 +548,20 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, goto end; } - /* calculate cs-setup delay with the first xfer speed */ - if (list_is_first(&xfer->transfer_list, &msg->transfers)) - desc->cfg_bus |= FIELD_PREP(CFG_CS_SETUP, - spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->cs_setup)); + /* calculate cs-setup delay with the first xfer speed and word dealy*/ + if (list_is_first(&xfer->transfer_list, &msg->transfers)) { + val = spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->cs_setup); + if (data && data->cs_setup_extend_ctrl) { + val = min_t(u32, 0xFF, val); + desc->cfg_bus |= FIELD_PREP(CFG_CS_SETUP, val & 0xF); + FIELD_MODIFY(CFG_CS_SETUP_EXTEND, &spisg->cfg_spi, val >> 4); + } else { + val = min_t(u32, 0xF, val); + desc->cfg_bus |= FIELD_PREP(CFG_CS_SETUP, val); + } + + delay = spi_delay_to_sclk(xfer->effective_speed_hz, &msg->spi->word_delay); + } /* calculate cs-hold delay with the last xfer speed */ if (list_is_last(&xfer->transfer_list, &msg->transfers)) { @@ -542,7 +576,13 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, xfer->effective_speed_hz); } - if (cs_hold_in_sclk) { + if (data && data->cs_hold_ctrl) { + cs_hold_in_sclk = cs_hold_in_sclk ? : 1; + val = cs_hold_in_sclk * (FIELD_GET(CFG_CLK_DIV, spisg->cfg_bus) + 1); + val = min_t(u32, 0x7FFF, val); + FIELD_MODIFY(CFG_CS_HOLD, &spisg->cfg_spi, val); + desc--; + } else if (cs_hold_in_sclk) { /* additional null-descriptor to achieve the cs-hold delay */ aml_spisg_setup_null_desc(spisg, desc, cs_hold_in_sclk); desc--; @@ -569,7 +609,7 @@ static int aml_spisg_transfer_one_message(struct spi_controller *ctlr, } reinit_completion(&spisg->completion); - aml_spisg_pending(spisg, descs_paddr, false, true); + aml_spisg_pending(spisg, descs_paddr, false, true, delay); if (wait_for_completion_timeout(&spisg->completion, spi_controller_is_target(spisg->controller) ? MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(ms))) { @@ -725,6 +765,7 @@ static int aml_spisg_probe(struct platform_device *pdev) struct spisg_device *spisg; struct device *dev = &pdev->dev; void __iomem *base; + u32 val = 0; int ret, irq; const struct regmap_config aml_regmap_config = { @@ -743,6 +784,7 @@ static int aml_spisg_probe(struct platform_device *pdev) spisg = spi_controller_get_devdata(ctlr); spisg->controller = ctlr; + spisg->data = (struct aml_spisg_data *)of_device_get_match_data(dev); spisg->pdev = pdev; platform_set_drvdata(pdev, spisg); @@ -773,6 +815,14 @@ static int aml_spisg_probe(struct platform_device *pdev) spisg->cfg_spi = FIELD_PREP(CFG_SFLASH_WP, 1) | FIELD_PREP(CFG_SFLASH_HD, 1); + + if (spisg->data && spisg->data->mo_idle_output_ctrl) { + if (!of_property_read_u32(dev->of_node, "amlogic,mo-idle-output", &val)) + spisg->cfg_spi |= FIELD_PREP(CFG_MO_IDLE_OUTPUT, val); + else + spisg->cfg_spi |= FIELD_PREP(CFG_MO_IDLE_OUTPUT, 0); + } + if (spi_controller_is_target(ctlr)) { spisg->cfg_spi |= FIELD_PREP(CFG_SLAVE_EN, 1); spisg->cfg_bus = FIELD_PREP(CFG_TX_TUNING, 0xf); @@ -859,6 +909,13 @@ static int spisg_resume_runtime(struct device *dev) return 0; } +static const struct aml_spisg_data a9_spisg_data = { + .mo_idle_output_ctrl = true, + .word_gap_ctrl = true, + .cs_hold_ctrl = true, + .cs_setup_extend_ctrl = true, +}; + static const struct dev_pm_ops amlogic_spisg_pm_ops = { .runtime_suspend = spisg_suspend_runtime, .runtime_resume = spisg_resume_runtime, @@ -868,6 +925,10 @@ static const struct of_device_id amlogic_spisg_of_match[] = { { .compatible = "amlogic,a4-spisg", }, + { + .compatible = "amlogic,a9-spisg", + .data = &a9_spisg_data, + }, { /* sentinel */ } }; From bb308b27016c9df54433474cdae6eacc32429b4a Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Fri, 4 Sep 2026 01:17:33 +0800 Subject: [PATCH 060/724] spi: ingenic: release tx DMA channel when rx request fails When the rx channel request fails, the driver carries on without DMA but the tx channel it already acquired stayed claimed until devm teardown. Release it on the error path so it goes straight back to the DMA engine for other users. Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260904-ingenic-v1-1-06218181a4f8@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-ingenic.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-ingenic.c b/drivers/spi/spi-ingenic.c index adcf85bccbcc..7ce4fe965cac 100644 --- a/drivers/spi/spi-ingenic.c +++ b/drivers/spi/spi-ingenic.c @@ -354,8 +354,11 @@ static int spi_ingenic_request_dma(struct spi_controller *ctlr, ctlr->dma_tx = chan; chan = dma_request_chan(dev, "rx"); - if (IS_ERR(chan)) + if (IS_ERR(chan)) { + dma_release_channel(ctlr->dma_tx); + ctlr->dma_tx = NULL; return PTR_ERR(chan); + } ctlr->dma_rx = chan; ctlr->can_dma = spi_ingenic_can_dma; From 366e4d4f0ca818fc4518b7eccbe243c6cd3161d2 Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Fri, 4 Sep 2026 14:00:35 +0800 Subject: [PATCH 061/724] 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 8992169e7ac6b216f881cb373bb2c6bff0c6162a Mon Sep 17 00:00:00 2001 From: Li Youhong Date: Tue, 1 Sep 2026 10:51:29 +0800 Subject: [PATCH 062/724] spi: ma35d1-qspi: assert reset on probe error paths nuvoton_qspi_probe() pulses the exclusive reset and leaves it deasserted for normal operation. Later probe failures returned without re-asserting the reset, leaving the controller out of reset after a failed probe. Assert the reset on those error paths. Fixes: 78b16af159ae ("spi: ma35d1-qspi: Add Nuvoton MA35D1 QSPI controller support") Signed-off-by: Li Youhong Link: https://patch.msgid.link/20260901025129.359960-1-dayou5941@163.com Signed-off-by: Mark Brown --- drivers/spi/spi-ma35d1-qspi.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c index 7f938d0c6f1f..cf731fa2f7e2 100644 --- a/drivers/spi/spi-ma35d1-qspi.c +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -633,12 +633,16 @@ static int nuvoton_qspi_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "failed to deassert reset\n"); ret = device_property_read_u32(dev, "num-cs", &num_cs); - if (ret && ret != -EINVAL) - return dev_err_probe(dev, ret, "failed to read num-cs\n"); + if (ret && ret != -EINVAL) { + ret = dev_err_probe(dev, ret, "failed to read num-cs\n"); + goto err_assert; + } - if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NUM_CS) - return dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n", + if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NUM_CS) { + ret = dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n", num_cs); + goto err_assert; + } ctlr->num_chipselect = num_cs; ctlr->max_transfer_size = nuvoton_qspi_max_transfer_size; @@ -655,14 +659,20 @@ static int nuvoton_qspi_probe(struct platform_device *pdev) ret = nuvoton_qspi_hw_init(qspi); if (ret) - return ret; + goto err_assert; ret = devm_spi_register_controller(dev, ctlr); - if (ret) - return dev_err_probe(dev, ret, - "failed to register spi controller\n"); + if (ret) { + ret = dev_err_probe(dev, ret, + "failed to register spi controller\n"); + goto err_assert; + } return 0; + +err_assert: + reset_control_assert(rst); + return ret; } static const struct of_device_id nuvoton_qspi_of_match[] = { From 4cee1ed13e2687f1a9fec7dd50e31eb179506d75 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Fri, 4 Sep 2026 23:51:29 +0800 Subject: [PATCH 063/724] spi: mtk-nor: fix runtime PM usage count leak in probe error path mtk_nor_probe() takes a runtime PM reference with pm_runtime_get_noresume() before registering the controller, but the error path never drops it. Balance the get with pm_runtime_put_noidle() in the error path. Fixes: 3bfd9103c7af ("spi: spi-mtk-nor: Add power management support") Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260904-mtk-nor-v1-1-cefdca098f83@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-mtk-nor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spi/spi-mtk-nor.c b/drivers/spi/spi-mtk-nor.c index 63f5139176c8..6b6e55d68048 100644 --- a/drivers/spi/spi-mtk-nor.c +++ b/drivers/spi/spi-mtk-nor.c @@ -929,6 +929,7 @@ static int mtk_nor_probe(struct platform_device *pdev) return 0; err_probe: + pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); From 8351ccada42181a0792ca9b26b244b8748b05061 Mon Sep 17 00:00:00 2001 From: Ricardo Rivera-Matos Date: Fri, 28 Aug 2026 10:46:26 -0500 Subject: [PATCH 064/724] 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 065/724] 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 86f4ab98e716fea99c604a427e676c44f781c185 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 18:15:10 +0200 Subject: [PATCH 066/724] spi: spi-mem: Enable DTR transfers using the standard API Most spi-mem operations today go through controllers implementing the spi-mem API. But it is also totally possible to use any standard SPI controller to operate these memories. If the controllers support DTR, there is no reason to prevent this feature from being used. Extend spi_mem_exec_op()'s fallback to the standard SPI API, by filling the transfer DTR information. Doing so also requires checking the dtr_caps flag, of course. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20260904-perso-ma35d1-master-v1-1-b6936e8c6fa1@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-mem.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 5f973ebfb8b6..94d122be1c93 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -172,7 +172,7 @@ bool spi_mem_default_supports_op(struct spi_mem *mem, op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr; if (op_is_dtr) { - if (!spi_mem_controller_is_capable(ctlr, dtr)) + if (!spi_mem_controller_is_capable(ctlr, dtr) && !ctlr->dtr_caps) return false; if (op->data.swap16 && !spi_mem_controller_is_capable(ctlr, swap16)) @@ -461,6 +461,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].len = op->cmd.nbytes; xfers[xferpos].tx_nbits = op->cmd.buswidth; xfers[xferpos].speed_hz = op->max_freq; + xfers[xferpos].dtr_mode = op->cmd.dtr; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen++; @@ -476,6 +477,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].len = op->addr.nbytes; xfers[xferpos].tx_nbits = op->addr.buswidth; xfers[xferpos].speed_hz = op->max_freq; + xfers[xferpos].dtr_mode = op->addr.dtr; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->addr.nbytes; @@ -488,6 +490,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].tx_nbits = op->dummy.buswidth; xfers[xferpos].dummy_data = 1; xfers[xferpos].speed_hz = op->max_freq; + xfers[xferpos].dtr_mode = op->dummy.dtr; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->dummy.nbytes; @@ -504,6 +507,7 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) xfers[xferpos].len = op->data.nbytes; xfers[xferpos].speed_hz = op->max_freq; + xfers[xferpos].dtr_mode = op->data.dtr; spi_message_add_tail(&xfers[xferpos], &msg); xferpos++; totalxferlen += op->data.nbytes; From 2448f25cc839739fe576d31d85c7d0df4844bb3a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 4 Sep 2026 18:15:11 +0200 Subject: [PATCH 067/724] spi: ma35d1-qspi: Allow DTR operations with the regular SPI API The feature was implemented but not actually enabled for regular SPI operations (as opposed to spi-mem operations). Make sure the missing capability is actually set. Reported-by: Mark Brown Closes: https://lore.kernel.org/all/09c3928f-4b58-4ac6-8e1a-84dfe3ff6b92@sirena.org.uk/ Fixes: 15e9362f6190 ("spi: ma35d1-qspi: Add DTR support") Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20260904-perso-ma35d1-master-v1-2-b6936e8c6fa1@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-ma35d1-qspi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c index 7f938d0c6f1f..ee3427640f3a 100644 --- a/drivers/spi/spi-ma35d1-qspi.c +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -645,6 +645,7 @@ static int nuvoton_qspi_probe(struct platform_device *pdev) ctlr->max_message_size = nuvoton_qspi_max_message_size; ctlr->mem_ops = &nuvoton_qspi_mem_ops; ctlr->mem_caps = &nuvoton_qspi_mem_caps; + ctlr->dtr_caps = true; ctlr->set_cs = nuvoton_qspi_set_cs; ctlr->transfer_one = nuvoton_qspi_transfer_one; ctlr->bits_per_word_mask = SPI_BPW_MASK(8); From 98477036fc35472834ccfee018fdc99d2d21b073 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:34:16 +0530 Subject: [PATCH 068/724] spi: 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 Link: https://patch.msgid.link/20260904110420.13707-1-hemanth.selam@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm63xx.c | 2 +- drivers/spi/spi-imx.c | 2 +- drivers/spi/spi-omap2-mcspi.c | 2 +- drivers/spi/spi-pic32.c | 2 +- drivers/spi/spi-s3c64xx.c | 2 +- drivers/spi/spi-stm32.c | 2 +- drivers/spi/spi-sun6i.c | 2 +- drivers/spi/spi-xilinx.c | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-bcm63xx.c b/drivers/spi/spi-bcm63xx.c index 1d8e257bad30..3f349c1db581 100644 --- a/drivers/spi/spi-bcm63xx.c +++ b/drivers/spi/spi-bcm63xx.c @@ -413,7 +413,7 @@ static irqreturn_t bcm63xx_spi_interrupt(int irq, void *dev_id) struct bcm63xx_spi *bs = spi_controller_get_devdata(host); u8 intr; - /* Read interupts and clear them immediately */ + /* Read interrupts and clear them immediately */ intr = bcm_spi_readb(bs, SPI_INT_STATUS); bcm_spi_writeb(bs, SPI_INTR_CLEAR_ALL, SPI_INT_STATUS); bcm_spi_writeb(bs, 0, SPI_INT_MASK); diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c index 79a6c1a60b0a..6f9686df0486 100644 --- a/drivers/spi/spi-imx.c +++ b/drivers/spi/spi-imx.c @@ -673,7 +673,7 @@ static int mx51_ecspi_prepare_message(struct spi_imx_data *spi_imx, * propagate into the hardware. It takes exactly one tick of the * SCLK clock, but we will wait two SCLK clock just to be sure. The * effect of the delay it takes for the hardware to apply changes - * is noticable if the SCLK clock run very slow. In such a case, if + * is noticeable if the SCLK clock run very slow. In such a case, if * the polarity of SCLK should be inverted, the GPIO ChipSelect might * be asserted before the SCLK polarity changes, which would disrupt * the SPI communication as the device on the other end would consider diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index 82c8b91e504e..ace6886a75c3 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -1244,7 +1244,7 @@ static int omap2_mcspi_transfer_one(struct spi_controller *ctlr, omap2_mcspi_set_fifo(spi, t, 0); out: - /* Restore defaults if they were overriden */ + /* Restore defaults if they were overridden */ if (par_override) { par_override = 0; status = omap2_mcspi_setup_transfer(spi, NULL); diff --git a/drivers/spi/spi-pic32.c b/drivers/spi/spi-pic32.c index 972128271e4b..8363bc873b03 100644 --- a/drivers/spi/spi-pic32.c +++ b/drivers/spi/spi-pic32.c @@ -53,7 +53,7 @@ struct pic32_spi_regs { #define TX_FIFO_ALL_EMPTY 0 /* completely empty */ #define TX_FIFO_EMPTY 1 /* empty */ #define TX_FIFO_HALF_EMPTY 2 /* empty by half or more */ -#define TX_FIFO_NOT_FULL 3 /* atleast one empty */ +#define TX_FIFO_NOT_FULL 3 /* at least one empty */ #define CTRL_MSTEN BIT(5) /* enable master mode */ #define CTRL_CKP BIT(6) /* active low */ diff --git a/drivers/spi/spi-s3c64xx.c b/drivers/spi/spi-s3c64xx.c index 8d4120cf9df1..3546414dfc70 100644 --- a/drivers/spi/spi-s3c64xx.c +++ b/drivers/spi/spi-s3c64xx.c @@ -661,7 +661,7 @@ static int s3c64xx_wait_for_pio(struct s3c64xx_spi_driver_data *sdd, * loops = length / max fifo size (calculated by using the * fifo mask). * For any size less than the fifo size the below code is - * executed atleast once. + * executed at least once. */ loops = xfer->len / sdd->fifo_depth; buf = xfer->rx_buf; diff --git a/drivers/spi/spi-stm32.c b/drivers/spi/spi-stm32.c index be88e62075af..5f5c43ae5096 100644 --- a/drivers/spi/spi-stm32.c +++ b/drivers/spi/spi-stm32.c @@ -515,7 +515,7 @@ static int stm32h7_spi_get_bpw_mask(struct stm32_spi *spi) /* * The most significant bit at DSIZE bit field is reserved when the - * maximum data size of periperal instances is limited to 16-bit + * maximum data size of peripheral instances is limited to 16-bit */ stm32_spi_set_bits(spi, STM32H7_SPI_CFG1, STM32H7_SPI_CFG1_DSIZE); diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c index 4631e9c7ca1d..6b7a05dd0de8 100644 --- a/drivers/spi/spi-sun6i.c +++ b/drivers/spi/spi-sun6i.c @@ -386,7 +386,7 @@ static int sun6i_spi_transfer_one(struct spi_controller *host, * SPI_CLK = MOD_CLK / (2 ^ cdr) * Or we can use CDR2, which is calculated with the formula: * SPI_CLK = MOD_CLK / (2 * (cdr + 1)) - * Wether we use the former or the latter is set through the + * Whether we use the former or the latter is set through the * DRS bit. * * First try CDR2, and if we can't reach the expected diff --git a/drivers/spi/spi-xilinx.c b/drivers/spi/spi-xilinx.c index dc1e968659e1..df4ea99a5427 100644 --- a/drivers/spi/spi-xilinx.c +++ b/drivers/spi/spi-xilinx.c @@ -460,7 +460,7 @@ static int xilinx_spi_probe(struct platform_device *pdev) host->num_chipselect = num_cs; /* - * Detect endianess on the IP via loop bit in CR. Detection + * Detect endianness on the IP via loop bit in CR. Detection * must be done before reset is sent because incorrect reset * value generates error interrupt. * Setup little endian helper functions first and try to use them 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 069/724] 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 070/724] 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 071/724] 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 072/724] 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 073/724] 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 074/724] 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 075/724] 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 23ea08ca607c2be0d4445dce7942a5049efb1451 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 6 Sep 2026 17:57:50 -0700 Subject: [PATCH 076/724] spi: orion: yield to scheduler in transfer wait loop orion_spi_wait_till_ready() busy-waits in a tight udelay(1) loop, up to 2000 iterations, and is called per byte from the polled, byte-at-a-time transfer path. On SoCs such as the Armada 388 (e.g. SolidRun Helios4), which also run SATA over the shared internal MBus fabric, this stalls the CPU for the whole transfer and delays servicing of SATA interrupts. Under sustained activity this can cause SATA timeouts and link resets (sometimes renegotiating down to SATA II, 3 Gbps). Add cond_resched() to the wait loop so the scheduler can run pending IRQs between polls. This is a no-op at runtime unless the kernel is built with CONFIG_PREEMPT enabled, where it lets other peripheral interrupts be serviced during SPI transfers. Built with LLVM=1 ARCH=powerpc; passes checkpatch --strict. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260907005750.230103-1-rosenp@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-orion.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/spi/spi-orion.c b/drivers/spi/spi-orion.c index 26a9f268b2d3..b8882229c054 100644 --- a/drivers/spi/spi-orion.c +++ b/drivers/spi/spi-orion.c @@ -370,7 +370,16 @@ static inline int orion_spi_wait_till_ready(struct orion_spi *orion_spi) if (readl(spi_reg(orion_spi, ORION_SPI_INT_CAUSE_REG))) return 1; + /* + * This is a polled, byte-at-a-time transfer loop. Each + * iteration busy-waits in a tight udelay() loop, which can + * starve other time-sensitive peripherals (e.g. SATA) of CPU + * time and interfere with them if they run on this SoC. + * Yield to the scheduler between polls so pending IRQs can + * be serviced. + */ udelay(1); + cond_resched(); } return -1; From 29863d75b9f0c9df012ae1fac18d72767ef20467 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 8 Sep 2026 07:36:43 +0100 Subject: [PATCH 077/724] 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 078/724] 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 079/724] 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 080/724] 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 081/724] 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 082/724] 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 083/724] 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 084/724] 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 085/724] 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 086/724] 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 087/724] 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 088/724] 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 089/724] 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 e95dac34dd5763c125dc597a159d72922b4f8fbb Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Mon, 7 Sep 2026 20:35:45 +0800 Subject: [PATCH 090/724] spi: ar934x: Convert to devm_spi_register_controller() Switch to devm_spi_register_controller() and drop the unneeded .remove callback and dev_set_drvdata(). Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260907-ar934x-v1-1-71327eb482bc@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-ar934x.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/drivers/spi/spi-ar934x.c b/drivers/spi/spi-ar934x.c index 2210186feab8..01e51a30591d 100644 --- a/drivers/spi/spi-ar934x.c +++ b/drivers/spi/spi-ar934x.c @@ -197,23 +197,13 @@ static int ar934x_spi_probe(struct platform_device *pdev) SPI_BPW_MASK(16) | SPI_BPW_MASK(8); ctlr->num_chipselect = 3; - dev_set_drvdata(&pdev->dev, ctlr); - sp = spi_controller_get_devdata(ctlr); sp->base = base; sp->clk = clk; sp->clk_freq = clk_get_rate(clk); sp->ctlr = ctlr; - return spi_register_controller(ctlr); -} - -static void ar934x_spi_remove(struct platform_device *pdev) -{ - struct spi_controller *ctlr; - - ctlr = dev_get_drvdata(&pdev->dev); - spi_unregister_controller(ctlr); + return devm_spi_register_controller(&pdev->dev, ctlr); } static struct platform_driver ar934x_spi_driver = { @@ -222,7 +212,6 @@ static struct platform_driver ar934x_spi_driver = { .of_match_table = ar934x_spi_match, }, .probe = ar934x_spi_probe, - .remove = ar934x_spi_remove, }; module_platform_driver(ar934x_spi_driver); From 233d36a96009682208cc4ca1139b9e5641a8bf09 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 8 Sep 2026 09:28:01 +0100 Subject: [PATCH 091/724] 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 092/724] 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 093/724] 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 094/724] 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 095/724] 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 096/724] 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 097/724] 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 098/724] 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 e329b94b1e47d1a73706bd09380bf0989fbcd4a8 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Thu, 13 Aug 2026 20:00:25 +0000 Subject: [PATCH 099/724] spi: tegra210-quad: Convert to hard IRQ with high-priority workqueue Threaded IRQ handlers can be delayed by the scheduler on heavily loaded systems, causing wait_for_completion_timeout() to expire before the handler runs and producing false transfer timeouts. On GB200 with TPM hwrng traffic running alongside a NCCL multicast workload this shows up as a WARN in tegra_qspi_transfer_one_message() even though the hardware has already signalled QSPI_RDY. irq_thread() runs SCHED_FIFO but set_cpus_allowed_ptr()s to the IRQ affinity mask (typically a single CPU). When that CPU is saturated by non-preemptible kernel work on the same interrupt line (softirqs, spinlock contention, network RX processing), the FIFO priority alone does not help - there is nothing at lower priority to preempt. The bottom half sits on the runqueue for milliseconds and occasionally seconds. Convert to a hard IRQ handler that schedules work on a WQ_HIGHPRI | WQ_UNBOUND workqueue: - The hard IRQ handler runs outside process-scheduler control - it can still be delayed by higher-priority IRQ handling or local IRQ-disabled / non-preemptible sections, but not by CFS or RT-userspace backpressure. tegra_qspi_isr() captures FIFO / trans status and masks the controller IRQ synchronously with the hardware event, so the subsequent timeout classification (added in the following patch) always sees the true state. - The workqueue worker runs SCHED_NORMAL with HIGHPRI_NICE_LEVEL (nice -20). A real-time SCHED_FIFO userspace task will preempt it where it would not have preempted the old irq_thread; that is a real trade-off. In exchange, WQ_UNBOUND lets the worker migrate off the interrupt-taking CPU that the threaded IRQ could not leave, which is the actual failure mode observed in the field. The following patch (small-PIO fastpath) further removes the worker from the latency-sensitive TPM path entirely. The work handler only touches QSPI MMIO when curr_xfer is non-NULL. curr_xfer is cleared only after the transfer thread has processed the completion, and while it is set the transfer thread is blocked in wait_for_completion_timeout() with the SPI core's runtime PM reference held, so the clocks are guaranteed on. The ISR returns IRQ_HANDLED unconditionally. Tegra QSPI has a dedicated, non-shared GIC SPI line on every SoC that uses this driver, so any spurious / late IRQ (for example after the timeout path has cleared curr_xfer) must still be acked and re-masked here; otherwise the level-triggered line could stay asserted and trip the kernel spurious-IRQ detector into disabling the line ("nobody cared, try to disable"). The lock-free curr_xfer NULL check lets the ISR bail without touching FIFO / status when there is no transfer to drive forward. handle_dma_based_xfer() snapshots curr_xfer under the spinlock at function entry and bails immediately when the timeout path has already cleared it. This avoids waiting up to QSPI_DMA_TIMEOUT on a DMA completion that belongs to a transfer the synchronous path has already torn down, and keeps the subsequent dma_unmap / FIFO-drain operations consistent with the transfer that actually started. Resources are allocated and torn down manually so that remove() can stop the controller, free the IRQ (preventing new work from being queued), then destroy the workqueue (which drains any already-queued work while the clocks are still on) before runtime PM is disabled. Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20260813200027.2711863-2-va@nvidia.com Signed-off-by: Mark Brown --- drivers/spi/spi-tegra210-quad.c | 165 ++++++++++++++++++++++---------- 1 file changed, 115 insertions(+), 50 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 8ede864c3d3c..7c09a1fe0d41 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -191,6 +191,8 @@ struct tegra_qspi { void __iomem *base; phys_addr_t phys; unsigned int irq; + struct work_struct irq_work; + struct workqueue_struct *wq; u32 cur_speed; unsigned int cur_pos; @@ -1232,9 +1234,9 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, if (ret == 0) { /* - * Check if hardware completed the transfer - * even though interrupt was lost or delayed. - * If so, process the completion and continue. + * Check if hardware completed the transfer even though + * workqueue was delayed. If so, process completion and + * continue. */ ret = tegra_qspi_handle_timeout(tqspi); if (ret < 0) { @@ -1351,8 +1353,8 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, if (ret == 0) { /* * Check if hardware completed the transfer even though - * interrupt was lost or delayed. If so, process the - * completion and continue. + * workqueue was delayed. If so, process completion and + * continue. */ ret = tegra_qspi_handle_timeout(tqspi); if (ret < 0) { @@ -1506,6 +1508,19 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) long wait_status; int num_errors = 0; + /* + * Snapshot curr_xfer under the lock before the (potentially long) + * DMA waits below. The timeout path can clear tqspi->curr_xfer + * concurrently; using the local copy keeps the subsequent dma_unmap + * and FIFO-drain steps consistent with the transfer that actually + * started, and lets us bail safely if cleanup already happened. + */ + spin_lock_irqsave(&tqspi->lock, flags); + t = tqspi->curr_xfer; + spin_unlock_irqrestore(&tqspi->lock, flags); + if (!t) + return IRQ_HANDLED; + if (tqspi->cur_direction & DATA_DIR_TX) { if (tqspi->tx_status) { if (tqspi->tx_dma_chan) @@ -1539,12 +1554,6 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) } spin_lock_irqsave(&tqspi->lock, flags); - t = tqspi->curr_xfer; - - if (!t) { - spin_unlock_irqrestore(&tqspi->lock, flags); - return IRQ_HANDLED; - } if (num_errors) { tegra_qspi_dma_unmap_xfer(tqspi, t); @@ -1581,46 +1590,38 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) return IRQ_HANDLED; } -static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) +/** + * tegra_qspi_work_handler - Workqueue handler for interrupt bottom-half + * @work: work_struct embedded in tegra_qspi + * + * Runs in process context and can sleep (needed for DMA completion waits). + * Runs on any CPU in the WQ_UNBOUND pool, so the bottom half can migrate off + * the interrupt-taking CPU that the previous threaded IRQ pinned to + * (irq_thread() calls set_cpus_allowed_ptr() with the IRQ affinity mask). + * + * The hard IRQ handler has already: + * - Verified this is our interrupt (QSPI_RDY was set) + * - Cached FIFO status in tqspi->status_reg + * - Parsed tx_status / rx_status from FIFO status + * - Masked further interrupts + */ +static void tegra_qspi_work_handler(struct work_struct *work) { - struct tegra_qspi *tqspi = context_data; + struct tegra_qspi *tqspi = container_of(work, struct tegra_qspi, irq_work); unsigned long flags; - u32 status; - /* - * Read transfer status to check if interrupt was triggered by transfer - * completion - */ - status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); - - /* - * Occasionally the IRQ thread takes a long time to wake up (usually - * when the CPU that it's running on is excessively busy) and we have - * already reached the timeout before and cleaned up the timed out - * transfer. Avoid any processing in that case and bail out early. - * - * If no transfer is in progress, check if this was a real interrupt - * that the timeout handler already processed, or a spurious one. - */ spin_lock_irqsave(&tqspi->lock, flags); + + /* + * The timeout path can clear curr_xfer between the ISR queuing + * this work and the worker actually running, so re-check under + * the lock and bail if there is nothing to do. + */ if (!tqspi->curr_xfer) { spin_unlock_irqrestore(&tqspi->lock, flags); - /* Spurious interrupt - transfer not ready */ - if (!(status & QSPI_RDY)) - return IRQ_NONE; - /* Real interrupt, already handled by timeout path */ - return IRQ_HANDLED; + return; } - tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); - - if (tqspi->cur_direction & DATA_DIR_TX) - tqspi->tx_status = tqspi->status_reg & (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); - - if (tqspi->cur_direction & DATA_DIR_RX) - tqspi->rx_status = tqspi->status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); - - tegra_qspi_mask_clear_irq(tqspi); spin_unlock_irqrestore(&tqspi->lock, flags); /* @@ -1630,9 +1631,55 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) * cannot be done while holding spinlock. */ if (!tqspi->is_curr_dma_xfer) - return handle_cpu_based_xfer(tqspi); + handle_cpu_based_xfer(tqspi); + else + handle_dma_based_xfer(tqspi); +} - return handle_dma_based_xfer(tqspi); +/** + * tegra_qspi_isr - Hard IRQ handler + * @irq: IRQ number + * @context_data: QSPI controller instance + * + * Runs in hard IRQ context with minimal latency. Cannot sleep. + * + * Tegra QSPI uses a dedicated, non-shared GIC SPI line on every SoC that + * uses this driver. The handler always returns IRQ_HANDLED and always + * acknowledges/re-masks the controller IRQ, so the level-triggered line + * cannot stay asserted and trip the kernel spurious-IRQ detector into + * disabling the line. On a stray IRQ where curr_xfer is NULL (e.g. the + * timeout path has already torn the transfer down) the FIFO/status + * processing and bottom-half scheduling are skipped because there is no + * transfer to drive forward. + * + * Return: IRQ_HANDLED. + */ +static irqreturn_t tegra_qspi_isr(int irq, void *context_data) +{ + struct tegra_qspi *tqspi = context_data; + + if (!READ_ONCE(tqspi->curr_xfer)) { + tegra_qspi_mask_clear_irq(tqspi); + return IRQ_HANDLED; + } + + spin_lock(&tqspi->lock); + tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); + tegra_qspi_mask_clear_irq(tqspi); + + if (tqspi->cur_direction & DATA_DIR_TX) + tqspi->tx_status = tqspi->status_reg & + (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); + + if (tqspi->cur_direction & DATA_DIR_RX) + tqspi->rx_status = tqspi->status_reg & + (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); + + spin_unlock(&tqspi->lock); + + queue_work(tqspi->wq, &tqspi->irq_work); + + return IRQ_HANDLED; } static struct tegra_qspi_soc_data tegra210_qspi_soc_data = { @@ -1800,12 +1847,21 @@ static int tegra_qspi_probe(struct platform_device *pdev) pm_runtime_put_autosuspend(&pdev->dev); - ret = request_threaded_irq(tqspi->irq, NULL, - tegra_qspi_isr_thread, IRQF_ONESHOT, - dev_name(&pdev->dev), tqspi); + tqspi->wq = alloc_workqueue("%s", WQ_HIGHPRI | WQ_UNBOUND, 0, + dev_name(&pdev->dev)); + if (!tqspi->wq) { + dev_err(&pdev->dev, "failed to allocate workqueue\n"); + ret = -ENOMEM; + goto exit_pm_disable; + } + + INIT_WORK(&tqspi->irq_work, tegra_qspi_work_handler); + + ret = request_irq(tqspi->irq, tegra_qspi_isr, 0, + dev_name(&pdev->dev), tqspi); if (ret < 0) { dev_err(&pdev->dev, "failed to request IRQ#%u: %d\n", tqspi->irq, ret); - goto exit_pm_disable; + goto exit_destroy_wq; } ret = spi_register_controller(host); @@ -1817,7 +1873,9 @@ static int tegra_qspi_probe(struct platform_device *pdev) return 0; exit_free_irq: - free_irq(qspi_irq, tqspi); + free_irq(tqspi->irq, tqspi); +exit_destroy_wq: + destroy_workqueue(tqspi->wq); exit_pm_disable: pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_force_suspend(&pdev->dev); @@ -1830,8 +1888,15 @@ static void tegra_qspi_remove(struct platform_device *pdev) struct spi_controller *host = platform_get_drvdata(pdev); struct tegra_qspi *tqspi = spi_controller_get_devdata(host); + /* + * Tear down in reverse order of probe() so that the controller stops + * accepting transfers before the IRQ is released, no new work can be + * queued after the IRQ is freed, and any work already queued is + * drained while the clocks are still running. + */ spi_unregister_controller(host); free_irq(tqspi->irq, tqspi); + destroy_workqueue(tqspi->wq); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_force_suspend(&pdev->dev); tegra_qspi_deinit_dma(tqspi); From 1ff82c13ea9475abc51eebfb83ee54bc75c08900 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Thu, 13 Aug 2026 20:00:26 +0000 Subject: [PATCH 100/724] spi: tegra210-quad: Cache TRANS_STATUS in ISR for timeout handler On heavily loaded systems the workqueue bottom half can be delayed long enough for wait_for_completion_timeout() to expire before the ISR's queued work actually runs. Reading QSPI_TRANS_STATUS directly from the controller in the timeout handler races with both the workqueue handler and the controller itself, and can mis-classify a transfer that genuinely timed out as having "completed". Cache the controller status captured by the hard IRQ before it is acked, and let the timeout handler consume that cache: - tegra_qspi_isr() reads QSPI_FIFO_STATUS and QSPI_TRANS_STATUS, derives tx_status / rx_status, publishes them via WRITE_ONCE(), and then publishes the trans_status cache via smp_store_release() *before* masking and acking the controller IRQ. Publish-before-clear is required so that a timeout handler that fell back to a live QSPI_TRANS_STATUS read (because it saw the cache still zero on another CPU) also sees the hardware RDY bit that has not been cleared yet. - tegra_qspi_handle_timeout() consumes trans_status with a paired smp_load_acquire() and a cache-live-cache retry pattern. If the initial cache load returns zero, the handler reads the live QSPI_TRANS_STATUS register; if that also returns zero it retries the cache once more. That closes the interleaving where an ISR publishes trans_status with release semantics and then W1Cs the hardware between the timeout handler's cache load and its live load, otherwise leaving the timeout handler with cache = 0 and HW = 0 (a false timeout on a transfer that has in fact just completed). - tegra_qspi_setup_transfer_one() and both tegra_qspi_start_{cpu,dma}_based_transfer() paths clear the cache with smp_store_release() under the spinlock before unmasking the IRQ for the new chunk, so a stale RDY bit from a previous chunk of a multi-chunk transfer cannot fool the handler. Serialise handle_timeout with the workqueue and the ISR unconditionally. Every expired wait_for_completion_timeout() enters the recovery state: publish recovery_in_progress under tqspi->lock, mask the controller IRQ, synchronize_irq() to drain any in-flight hard IRQ (including the small-PIO fastpath), and cancel_work_sync() to drain the workqueue. This holds regardless of whether the hardware finished, because a genuine hardware timeout still races the caller's dma_stop() + device_reset() + curr_xfer clear against a delayed ISR or worker that arrives immediately after the status sample. Classifying the timeout as -ETIMEDOUT only *after* serialisation gives the caller a stable state to clean up. Snapshot the live FIFO error status *before* entering recovery when the ISR cache is empty and the live QSPI_TRANS_STATUS shows RDY (the lost-IRQ path). tegra_qspi_mask_clear_irq() W1Cs QSPI_TRANS_STATUS and the QSPI_FIFO_STATUS error bits, so a lost-IRQ recovery that called it first would erase the very error state the manual handler downstream needs to see. Capturing the snapshot before the mask and publishing it into tqspi->{status_reg,tx_status,rx_status} after the drain keeps the manual final-chunk handler operating on fresh error data rather than stale fields from an earlier ISR run. cancel_work_sync() cancels a pending worker without executing it and waits for a currently running one to finish. The recovery_in_progress guard is checked inside tegra_qspi_isr() under the same tqspi->lock as its queue_work() and small-PIO fastpath dispatch decisions, so no new bottom-half work is enqueued once we publish the flag. synchronize_irq() closes the window where an ISR observed recovery_in_progress == false, released the lock, and is about to call queue_work(): we wait for that ISR to finish before draining the workqueue, so its queued work is caught by cancel_work_sync(). After the drain, re-check the cache once more (the drained worker may have published a completion status the entry snapshot did not observe). If try_wait_for_completion() reports the whole transfer completed, return success. Restrict the manual fallback that invokes handle_{cpu,dma}_based_xfer() from process context to the last chunk of a transfer. On an intermediate chunk of a multi-chunk DMA transfer the work handler may have processed the current chunk and armed the next chunk (unmasked the IRQ and kicked HW) before cancel_work_sync() returned; running another handler here would let the caller's seq_xfer clear curr_xfer and finalise the message while the DMA engine is still moving the next chunk into the client buffer. Return -ETIMEDOUT in that case and let the caller's existing dma_stop() + reset() path clean up. Before returning, re-mask the controller IRQ and synchronize_irq() one more time. The drained bottom half may have unmasked the IRQ when arming a subsequent chunk; without the re-mask a lingering RDY IRQ that arrives after this function returns could invoke the ISR and queue a worker after recovery_in_progress has been cleared, racing the caller's cleanup of curr_xfer. Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20260813200027.2711863-3-va@nvidia.com Signed-off-by: Mark Brown --- drivers/spi/spi-tegra210-quad.c | 296 ++++++++++++++++++++++++++++---- 1 file changed, 267 insertions(+), 29 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 7c09a1fe0d41..c242f56a09fd 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -193,6 +193,13 @@ struct tegra_qspi { unsigned int irq; struct work_struct irq_work; struct workqueue_struct *wq; + /* + * Set by tegra_qspi_handle_timeout() while it drains the bottom + * half so tegra_qspi_isr() suppresses new queue_work() calls + * that would otherwise race the recovery path or the caller's + * cleanup of curr_xfer. + */ + bool recovery_in_progress; u32 cur_speed; unsigned int cur_pos; @@ -214,6 +221,7 @@ struct tegra_qspi { u32 tx_status; u32 rx_status; u32 status_reg; + u32 trans_status; bool is_packed; bool use_dma; @@ -624,6 +632,17 @@ static int tegra_qspi_start_dma_based_transfer(struct tegra_qspi *tqspi, struct val = QSPI_DMA_BLK_SET(tqspi->curr_dma_words - 1); tegra_qspi_writel(tqspi, val, QSPI_DMA_BLK); + /* + * Reset the cached transfer status before unmasking the IRQ for + * this chunk. The cache must represent only the IRQ for THIS + * chunk; a stale RDY from the previous chunk of a multi-chunk + * transfer would otherwise mislead tegra_qspi_handle_timeout() + * into a false-positive recovery while the new chunk is still in + * flight. Pairs with smp_load_acquire() in + * tegra_qspi_handle_timeout(). The new chunk's IRQ cannot fire + * until QSPI_DMA_CTL is written below. + */ + smp_store_release(&tqspi->trans_status, 0); tegra_qspi_unmask_irq(tqspi); if (tqspi->is_packed) @@ -736,6 +755,16 @@ static int tegra_qspi_start_cpu_based_transfer(struct tegra_qspi *qspi, struct s val = QSPI_DMA_BLK_SET(cur_words - 1); tegra_qspi_writel(qspi, val, QSPI_DMA_BLK); + /* + * Reset the cached transfer status before unmasking the IRQ for + * this chunk so the cache represents only the IRQ for THIS chunk; + * a stale RDY from the previous chunk would otherwise mislead + * tegra_qspi_handle_timeout() into a false-positive recovery + * while the new chunk is still in flight. Pairs with + * smp_load_acquire() in tegra_qspi_handle_timeout(). The new + * chunk's IRQ cannot fire until QSPI_COMMAND1 is written below. + */ + smp_store_release(&qspi->trans_status, 0); tegra_qspi_unmask_irq(qspi); qspi->is_curr_dma_xfer = false; @@ -861,6 +890,13 @@ static u32 tegra_qspi_setup_transfer_one(struct spi_device *spi, struct spi_tran tqspi->cur_rx_pos = 0; tqspi->cur_tx_pos = 0; tqspi->curr_xfer = t; + /* + * Pairs with smp_load_acquire() in tegra_qspi_handle_timeout(). + * Clearing the cached trans_status before unmasking the IRQ for + * the new transfer prevents a stale RDY bit from the previous + * transfer fooling the timeout handler into a false recovery. + */ + smp_store_release(&tqspi->trans_status, 0); spin_unlock_irqrestore(&tqspi->lock, flags); if (is_first_of_msg) { @@ -1067,40 +1103,206 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi); * tegra_qspi_handle_timeout - Handle transfer timeout with hardware check * @tqspi: QSPI controller instance * - * When a timeout occurs but hardware has completed the transfer (interrupt - * was lost or delayed), manually trigger transfer completion processing. - * This avoids failing transfers that actually succeeded. + * When wait_for_completion_timeout() expires the hardware may still have + * finished the current chunk. Drain the pending bottom half and, if the + * whole transfer really did complete during the drain, consume the + * completion and report success. * - * Returns: 0 if transfer was completed, -ETIMEDOUT if real timeout + * When the bottom half advanced the transfer by only one chunk of a + * multi-chunk DMA/PIO transfer without signalling xfer_completion, a + * fallback that ran handle_{cpu,dma}_based_xfer() here would race with + * the DMA engine already moving the next chunk into the client buffer + * (spi_finalize_current_message() would then release the buffer while + * the controller is still writing memory). Fake completion is therefore + * only attempted when the current chunk is the last chunk of the + * transfer; multi-chunk continuation timeouts return -ETIMEDOUT and + * let the caller reset the controller. + * + * Returns: 0 if the transfer completed, -ETIMEDOUT otherwise. */ static int tegra_qspi_handle_timeout(struct tegra_qspi *tqspi) { + struct spi_transfer *t; + unsigned long flags; + bool is_last_chunk; + bool lost_irq_snapshot = false; irqreturn_t ret; - u32 status; - - /* Check if hardware actually completed the transfer */ - status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); - if (!(status & QSPI_RDY)) - return -ETIMEDOUT; + int retval; + u32 status, refreshed; + u32 lost_fifo_status = 0; + u32 lost_tx_status = 0; + u32 lost_rx_status = 0; /* - * Hardware completed but interrupt was lost/delayed. Manually - * process the completion by calling the appropriate handler. + * Snapshot both the ISR cache and (if the cache is empty) the + * live status registers BEFORE entering recovery. The recovery + * path calls tegra_qspi_mask_clear_irq() below, which performs + * W1Cs on QSPI_TRANS_STATUS and on the QSPI_FIFO_STATUS error + * bits: a lost-IRQ recovery must capture the current FIFO error + * state before the mask erases it. + * + * Cache-live-cache retry: if the initial cache load returns zero + * we fall back to a live QSPI_TRANS_STATUS read, and if that also + * returns zero we retry the cache once more. That closes the + * interleaving where an ISR on another CPU publishes trans_status + * with release semantics and then W1Cs the hardware between our + * cache load and our live load: the second cache load observes + * the now-visible release and we correctly classify the transfer + * as complete rather than reporting a false timeout. + * + * The trans_status cache is reset to zero in + * tegra_qspi_start_{cpu,dma}_based_transfer() before unmasking + * the IRQ for every chunk, so a stale RDY from the previous + * chunk of a multi-chunk transfer cannot survive into this + * check. */ + status = smp_load_acquire(&tqspi->trans_status); + if (!status) { + status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); + if (!status) { + /* Retry cache; pairs with release in ISR post-store. */ + status = smp_load_acquire(&tqspi->trans_status); + } else { + /* + * Live register shows RDY but the ISR cache is + * empty: either the ISR ran and cleared HW between + * our two loads (the cache retry above would have + * observed it, so we would not be here), or the IRQ + * was genuinely lost. Snapshot the live FIFO error + * status now so tegra_qspi_mask_clear_irq() below + * does not W1C it away before the manual handler + * downstream can see it. + */ + lost_fifo_status = tegra_qspi_readl(tqspi, + QSPI_FIFO_STATUS); + lost_tx_status = lost_fifo_status & + (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); + lost_rx_status = lost_fifo_status & + (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); + lost_irq_snapshot = true; + } + } + + /* + * Enter recovery unconditionally. Every expired + * wait_for_completion_timeout() must serialise against a delayed + * ISR or worker before the caller runs dma_stop() + + * device_reset() + curr_xfer clear: publishing + * recovery_in_progress under tqspi->lock, masking the controller + * IRQ, calling synchronize_irq() to drain any in-flight ISR + * (including the small-PIO hard-IRQ fastpath), and finally + * cancel_work_sync() to drain the workqueue gives us that + * serialisation regardless of whether the hardware finished. A + * genuine hardware timeout still ends up as -ETIMEDOUT further + * down, but only after ISR and workqueue activity are quiesced. + * + * cancel_work_sync() cancels a pending worker without executing + * it and waits for a currently running one to finish; the + * recovery_in_progress guard checked inside tegra_qspi_isr() + * under tqspi->lock is atomic with its queue_work() and small-PIO + * fastpath dispatch decisions, so no new bottom-half work is + * enqueued once we publish the flag. + * + * tegra_qspi_mask_clear_irq() is idempotent: its read-modify-write + * of QSPI_INTR_MASK and W1C of QSPI_TRANS_STATUS / FIFO error + * status all tolerate a double-write, so it is safe whether or + * not the ISR has already run for this transfer. + */ + spin_lock_irqsave(&tqspi->lock, flags); + WRITE_ONCE(tqspi->recovery_in_progress, true); + spin_unlock_irqrestore(&tqspi->lock, flags); + + tegra_qspi_mask_clear_irq(tqspi); + synchronize_irq(tqspi->irq); + cancel_work_sync(&tqspi->irq_work); + + if (try_wait_for_completion(&tqspi->xfer_completion)) { + retval = 0; + goto out; + } + + /* + * Re-check the cache after the drain: the worker we just drained + * may have published a completion status the entry snapshot did + * not observe (for example the ISR fired on another CPU after we + * loaded the cache but before we masked). + */ + refreshed = smp_load_acquire(&tqspi->trans_status); + if (refreshed) + status = refreshed; + + if (!(status & QSPI_RDY)) { + retval = -ETIMEDOUT; + goto out; + } + + /* + * If the ISR never ran (lost IRQ path) publish the FIFO error + * snapshot we captured before mask_clear_irq() so the manual + * handler downstream has fresh error state rather than stale + * fields from a previous chunk's ISR. + */ + if (lost_irq_snapshot) { + WRITE_ONCE(tqspi->status_reg, lost_fifo_status); + WRITE_ONCE(tqspi->tx_status, lost_tx_status); + WRITE_ONCE(tqspi->rx_status, lost_rx_status); + } + + /* + * The bottom half did not signal full completion. Either the work + * ran and advanced the transfer by one chunk (possibly arming the + * next chunk of a multi-chunk transfer), or it was cancelled + * before it could run, or the current chunk really did not + * complete. Only fake completion when the current chunk is the + * last chunk of the transfer; otherwise the DMA engine may still + * be moving the next chunk into memory, and returning 0 here would + * let seq_xfer clear curr_xfer and finalise the message while the + * hardware is still writing. + * + * The last-chunk arithmetic mirrors tegra_qspi_start_cpu_based_ + * transfer(), which uses cur_pos + curr_dma_words * bytes_per_word + * >= t->len to set is_last_pio_chunk before arming the IRQ. + */ + spin_lock_irqsave(&tqspi->lock, flags); + t = tqspi->curr_xfer; + if (!t) { + /* CPU-path handler already cleared curr_xfer */ + spin_unlock_irqrestore(&tqspi->lock, flags); + retval = 0; + goto out; + } + is_last_chunk = (tqspi->cur_pos + + tqspi->curr_dma_words * tqspi->bytes_per_word) >= t->len; + spin_unlock_irqrestore(&tqspi->lock, flags); + + if (!is_last_chunk) { + retval = -ETIMEDOUT; + goto out; + } + dev_warn_ratelimited(tqspi->dev, "QSPI interrupt timeout, but transfer complete\n"); - /* Clear the transfer status */ - status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); - tegra_qspi_writel(tqspi, status, QSPI_TRANS_STATUS); - - /* Manually trigger completion handler */ - if (!tqspi->is_curr_dma_xfer) + if (!READ_ONCE(tqspi->is_curr_dma_xfer)) ret = handle_cpu_based_xfer(tqspi); else ret = handle_dma_based_xfer(tqspi); - return (ret == IRQ_HANDLED) ? 0 : -EIO; + retval = (ret == IRQ_HANDLED) ? 0 : -EIO; + +out: + /* + * The drained bottom half may have unmasked the controller IRQ + * to arm the next chunk of a multi-chunk transfer. Re-mask and + * synchronize before clearing recovery_in_progress so that no + * lingering ISR can queue fresh work behind the caller's back + * (the caller's dma_stop() + device_reset() + curr_xfer clear + * runs immediately after we return on the error path). + */ + tegra_qspi_mask_clear_irq(tqspi); + synchronize_irq(tqspi->irq); + WRITE_ONCE(tqspi->recovery_in_progress, false); + return retval; } static u32 tegra_qspi_cmd_config(bool is_ddr, u8 bus_width, u8 len) @@ -1613,9 +1815,12 @@ static void tegra_qspi_work_handler(struct work_struct *work) spin_lock_irqsave(&tqspi->lock, flags); /* - * The timeout path can clear curr_xfer between the ISR queuing - * this work and the worker actually running, so re-check under - * the lock and bail if there is nothing to do. + * tegra_qspi_handle_timeout() sets recovery_in_progress under + * tqspi->lock and then calls cancel_work_sync(), so any running + * worker is drained and tegra_qspi_isr() cannot enqueue a new + * one while recovery runs. The curr_xfer NULL check catches the + * case where the timeout path already tore the transfer down + * before this work got a chance to run. */ if (!tqspi->curr_xfer) { spin_unlock_irqrestore(&tqspi->lock, flags); @@ -1657,6 +1862,7 @@ static void tegra_qspi_work_handler(struct work_struct *work) static irqreturn_t tegra_qspi_isr(int irq, void *context_data) { struct tegra_qspi *tqspi = context_data; + u32 status_reg, trans_status; if (!READ_ONCE(tqspi->curr_xfer)) { tegra_qspi_mask_clear_irq(tqspi); @@ -1664,21 +1870,53 @@ static irqreturn_t tegra_qspi_isr(int irq, void *context_data) } spin_lock(&tqspi->lock); - tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); - tegra_qspi_mask_clear_irq(tqspi); + status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); + trans_status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); if (tqspi->cur_direction & DATA_DIR_TX) - tqspi->tx_status = tqspi->status_reg & - (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); + WRITE_ONCE(tqspi->tx_status, + status_reg & (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF)); if (tqspi->cur_direction & DATA_DIR_RX) - tqspi->rx_status = tqspi->status_reg & - (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); + WRITE_ONCE(tqspi->rx_status, + status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF)); - spin_unlock(&tqspi->lock); + WRITE_ONCE(tqspi->status_reg, status_reg); + /* + * Publish trans_status with release semantics before we clear + * the hardware status in tegra_qspi_mask_clear_irq() below. That + * ordering matters for the lock-free cache read in + * tegra_qspi_handle_timeout(): if the timeout path sees the + * released trans_status it also observes the matching status_reg + * / tx_status / rx_status; if it does not yet see the released + * value it falls back to a live QSPI_TRANS_STATUS read, and that + * live read still returns QSPI_RDY because we have not cleared + * the register yet. Reversing this order would open a window + * where the cache is still zero but the hardware bit has already + * been cleared, making the fallback report a false timeout. + */ + smp_store_release(&tqspi->trans_status, trans_status); + + tegra_qspi_mask_clear_irq(tqspi); + + /* + * If tegra_qspi_handle_timeout() is draining the bottom half, + * skip queueing new work. The flag is set under tqspi->lock and + * queue_work() below happens while we still hold the lock, so + * the guard is atomic with the queue decision. Any ISR that had + * already passed this check is drained by the synchronize_irq() + * call that tegra_qspi_handle_timeout() issues after publishing + * the flag. + */ + if (READ_ONCE(tqspi->recovery_in_progress)) { + spin_unlock(&tqspi->lock); + return IRQ_HANDLED; + } queue_work(tqspi->wq, &tqspi->irq_work); + spin_unlock(&tqspi->lock); + return IRQ_HANDLED; } From 252ee24aff97cd0a14c192c200753487ed88ee24 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Thu, 13 Aug 2026 20:00:27 +0000 Subject: [PATCH 101/724] spi: tegra210-quad: Process small PIO transfers in hard IRQ context On heavily loaded systems, workqueue scheduling delays can exceed transfer timeouts even for high-priority queues, causing false timeouts for latency-sensitive devices like TPM despite hardware completing in microseconds. Process small PIO transfers (those that complete the whole spi_transfer in a single chunk) directly in hard IRQ context instead of deferring to the workqueue. This reduces completion latency from 1000ms+ to microseconds and matches the pattern used by other SPI drivers. To avoid touching the spi_transfer object from hard IRQ context (which would race with the synchronous teardown path that clears curr_xfer on timeout), tegra_qspi_start_cpu_based_transfer() caches the "this PIO chunk completes the whole transfer" decision into a scalar tqspi->is_last_pio_chunk *before* unmasking the IRQ. The hard-IRQ fastpath consumes that scalar with READ_ONCE() and never dereferences curr_xfer or any spi_transfer fields. Multi-chunk PIO transfers are intentionally kept on the workqueue (only the final chunk sets the flag) so the fastpath can never recurse into tegra_qspi_start_cpu_based_transfer() from hard IRQ context, and DMA transfers always go through the workqueue because their completion path sleeps on the DMA engine. The fastpath also gates on the per-IRQ tx_status / rx_status locals being zero, because handle_cpu_based_xfer()'s error path calls tegra_qspi_reset() -> device_reset(), which can sleep and must not run from hard IRQ context. is_curr_dma_xfer and is_last_pio_chunk are written from process context (the transfer-start functions) and read lock-free from the hard IRQ handler and the workqueue handler, so the writes use WRITE_ONCE() and the reads use READ_ONCE() to prevent compiler tearing and silence KCSAN data-race warnings. Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20260813200027.2711863-4-va@nvidia.com Signed-off-by: Mark Brown --- drivers/spi/spi-tegra210-quad.c | 88 +++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index c242f56a09fd..d8cca3aa276b 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -214,6 +214,18 @@ struct tegra_qspi { unsigned int dma_buf_size; unsigned int max_buf_size; bool is_curr_dma_xfer; + /* + * Cached "this PIO chunk completes the whole transfer" decision, + * computed by tegra_qspi_start_cpu_based_transfer() before it + * unmasks the IRQ. Used by the hard IRQ small-PIO fastpath in + * place of dereferencing curr_xfer->len, so the ISR cannot touch + * the spi_transfer object even on a late IRQ that races with the + * synchronous teardown path. Multi-chunk PIO transfers always go + * through the workqueue (this flag is only set on the final + * chunk), so the fastpath cannot recurse into + * tegra_qspi_start_cpu_based_transfer() from hard IRQ context. + */ + bool is_last_pio_chunk; struct completion rx_dma_complete; struct completion tx_dma_complete; @@ -734,7 +746,13 @@ static int tegra_qspi_start_dma_based_transfer(struct tegra_qspi *tqspi, struct tegra_qspi_writel(tqspi, tqspi->command1_reg, QSPI_COMMAND1); - tqspi->is_curr_dma_xfer = true; + /* + * WRITE_ONCE() pairs with READ_ONCE() in tegra_qspi_isr() and + * tegra_qspi_work_handler(); the flag is read lock-free across + * the hard-IRQ / process-context boundary so the annotation + * prevents compiler tearing and silences KCSAN. + */ + WRITE_ONCE(tqspi->is_curr_dma_xfer, true); tqspi->dma_control_reg = val; val |= QSPI_DMA_EN; tegra_qspi_writel(tqspi, val, QSPI_DMA_CTL); @@ -755,6 +773,20 @@ static int tegra_qspi_start_cpu_based_transfer(struct tegra_qspi *qspi, struct s val = QSPI_DMA_BLK_SET(cur_words - 1); tegra_qspi_writel(qspi, val, QSPI_DMA_BLK); + /* + * Snapshot whether this PIO chunk completes the whole transfer + * before unmasking the IRQ, so the hard IRQ small-PIO fastpath + * can decide whether to drain inline without dereferencing the + * spi_transfer object. cur_pos / curr_dma_words / bytes_per_word + * are stable here: they are written by + * tegra_qspi_calculate_curr_xfer_param() earlier in this code + * path. The IRQ cannot fire until the QSPI_COMMAND1 write below + * kicks the transfer off, so this store happens-before any ISR + * that observes the unmask. + */ + WRITE_ONCE(qspi->is_last_pio_chunk, + qspi->cur_pos + qspi->curr_dma_words * qspi->bytes_per_word >= t->len); + /* * Reset the cached transfer status before unmasking the IRQ for * this chunk so the cache represents only the IRQ for THIS chunk; @@ -767,7 +799,7 @@ static int tegra_qspi_start_cpu_based_transfer(struct tegra_qspi *qspi, struct s smp_store_release(&qspi->trans_status, 0); tegra_qspi_unmask_irq(qspi); - qspi->is_curr_dma_xfer = false; + WRITE_ONCE(qspi->is_curr_dma_xfer, false); val = qspi->command1_reg; val |= QSPI_PIO; tegra_qspi_writel(qspi, val, QSPI_COMMAND1); @@ -1835,7 +1867,7 @@ static void tegra_qspi_work_handler(struct work_struct *work) * DMA handler also needs to sleep in wait_for_completion_*(), which * cannot be done while holding spinlock. */ - if (!tqspi->is_curr_dma_xfer) + if (!READ_ONCE(tqspi->is_curr_dma_xfer)) handle_cpu_based_xfer(tqspi); else handle_dma_based_xfer(tqspi); @@ -1863,6 +1895,7 @@ static irqreturn_t tegra_qspi_isr(int irq, void *context_data) { struct tegra_qspi *tqspi = context_data; u32 status_reg, trans_status; + u32 tx_status = 0, rx_status = 0; if (!READ_ONCE(tqspi->curr_xfer)) { tegra_qspi_mask_clear_irq(tqspi); @@ -1873,13 +1906,15 @@ static irqreturn_t tegra_qspi_isr(int irq, void *context_data) status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); trans_status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); - if (tqspi->cur_direction & DATA_DIR_TX) - WRITE_ONCE(tqspi->tx_status, - status_reg & (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF)); + if (tqspi->cur_direction & DATA_DIR_TX) { + tx_status = status_reg & (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF); + WRITE_ONCE(tqspi->tx_status, tx_status); + } - if (tqspi->cur_direction & DATA_DIR_RX) - WRITE_ONCE(tqspi->rx_status, - status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF)); + if (tqspi->cur_direction & DATA_DIR_RX) { + rx_status = status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); + WRITE_ONCE(tqspi->rx_status, rx_status); + } WRITE_ONCE(tqspi->status_reg, status_reg); /* @@ -1913,6 +1948,41 @@ static irqreturn_t tegra_qspi_isr(int irq, void *context_data) return IRQ_HANDLED; } + /* + * Small-PIO fastpath: drain the FIFO inline only when this chunk + * completes the entire outstanding transfer and no error bit was + * latched, to avoid workqueue scheduling latency for TPM-style + * short reads. + * + * The "last chunk" decision is computed and cached as a scalar by + * tegra_qspi_start_cpu_based_transfer() before it unmasks the IRQ, + * so the hard-IRQ fastpath never dereferences the spi_transfer + * pointer here. That keeps the ISR safe against any teardown race + * where the synchronous path could clear curr_xfer concurrently. + * + * The fastpath dispatch decision is made while still holding + * tqspi->lock, so the recovery_in_progress guard above covers it + * atomically with queue_work() below: an ISR that reaches the + * fastpath cannot race a tegra_qspi_handle_timeout() that + * subsequently observes recovery_in_progress == true, because + * that path calls synchronize_irq() before proceeding. We drop + * the lock before calling handle_cpu_based_xfer() so it can take + * tqspi->lock internally without deadlocking. + * + * Multi-chunk PIO continuation stays on the workqueue so that + * tegra_qspi_start_cpu_based_transfer() can re-arm the IRQ from + * process context. DMA transfers also stay on the workqueue + * because their completion path sleeps on the DMA engine. + * tegra_qspi_handle_error() -> device_reset() can sleep, so the + * fastpath only runs when both status words are clean. + */ + if (!READ_ONCE(tqspi->is_curr_dma_xfer) && + READ_ONCE(tqspi->is_last_pio_chunk) && + !tx_status && !rx_status) { + spin_unlock(&tqspi->lock); + return handle_cpu_based_xfer(tqspi); + } + queue_work(tqspi->wq, &tqspi->irq_work); spin_unlock(&tqspi->lock); From 3e56757584d631fb954332f55698bbb55ee93dc9 Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Sat, 15 Aug 2026 14:40:00 +0300 Subject: [PATCH 102/724] 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 103/724] 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 104/724] 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 c595dcbce20f55c98cd9b0003c26db3787e0ddee Mon Sep 17 00:00:00 2001 From: Li Youhong Date: Mon, 31 Aug 2026 17:45:53 +0800 Subject: [PATCH 105/724] spi: amlogic-spisg: check clk_prepare_enable() return value The driver ignored clk_prepare_enable() failures for sclk during probe and for core/sclk during runtime resume. Propagate the errors and, on resume, disable core if enabling sclk fails, so probe/resume do not continue with clocks disabled or report success falsely. Fixes: cef9991e04ae ("spi: Add Amlogic SPISG driver") Signed-off-by: Li Youhong Link: https://patch.msgid.link/20260831094553.2247003-1-dayou5941@163.com Signed-off-by: Mark Brown --- drivers/spi/spi-amlogic-spisg.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c index 5ace2c927da0..1b00f34e9e93 100644 --- a/drivers/spi/spi-amlogic-spisg.c +++ b/drivers/spi/spi-amlogic-spisg.c @@ -754,7 +754,9 @@ static int aml_spisg_clk_init(struct spisg_device *spisg, void __iomem *base) return PTR_ERR(spisg->sclk); } - clk_prepare_enable(spisg->sclk); + ret = clk_prepare_enable(spisg->sclk); + if (ret) + return ret; return 0; } @@ -901,9 +903,18 @@ static int spisg_suspend_runtime(struct device *dev) static int spisg_resume_runtime(struct device *dev) { struct spisg_device *spisg = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(spisg->core); + if (ret) + return ret; + + ret = clk_prepare_enable(spisg->sclk); + if (ret) { + clk_disable_unprepare(spisg->core); + return ret; + } - clk_prepare_enable(spisg->core); - clk_prepare_enable(spisg->sclk); pinctrl_pm_select_default_state(&spisg->pdev->dev); return 0; From 9c343445b349422bddebb4cb9668c246bc6f08de Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 22:18:28 +0800 Subject: [PATCH 106/724] 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 34e32e9ade58e32daf933974c9b697d066fc5aaa Mon Sep 17 00:00:00 2001 From: Jiawen Liu <1298662399@qq.com> Date: Tue, 18 Aug 2026 17:05:50 +0400 Subject: [PATCH 107/724] spi: rockchip-sfc: disable runtime PM in remove rockchip_sfc_probe enables runtime PM and leaves the device active with a usage count held by pm_runtime_get_noresume(). The remove callback only disables the clocks and does not disable runtime PM, leaving the device in an inconsistent state and leaking the runtime PM reference. Add the missing runtime PM teardown in rockchip_sfc_remove to balance the probe's enable and get_noresume calls. Signed-off-by: jiawen <1298662399@qq.com> Link: https://patch.msgid.link/tencent_DD16C0EEDFEF4ACAFAB5A23EC8235D462707@qq.com Signed-off-by: Mark Brown --- drivers/spi/spi-rockchip-sfc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index 662a994da60b..43e91b49bb1d 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -743,6 +743,11 @@ static void rockchip_sfc_remove(struct platform_device *pdev) struct spi_controller *host = sfc->host; spi_unregister_controller(host); + pm_runtime_get_sync(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); dma_unmap_single(&pdev->dev, sfc->dma_buffer, sfc->max_iosize, DMA_BIDIRECTIONAL); free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); From a27da8f9fceb12ec4cf438f111f9a8c93d9c4eab Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 06:38:06 +0000 Subject: [PATCH 108/724] 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 109/724] 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 110/724] 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 111/724] 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 112/724] 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 113/724] 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 114/724] 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 115/724] 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 116/724] 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 117/724] 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 118/724] 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 119/724] 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 120/724] 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 121/724] 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 44d39535cd4f7343284336c7288018db2a167bde Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Tue, 8 Sep 2026 22:23:55 +0200 Subject: [PATCH 122/724] spi: spi-qpic-snand: remove interim 'dev_data' variable from qcom_spi_probe() The dev_data variable in the qcom_spi_probe() function is only used to temporarily store a pointer of the device specific data before that value gets assigned to 'snandc->props'. Remove the interim variable and use 'snandc->props' directly instead in order to simplify the code. No functional changes. Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260908-qpic-snand-drop-dev_data-var-v1-1-147d3fab6c48@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 61b1f2eb19ce..9eafaea74f6b 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -1585,7 +1585,6 @@ static int qcom_spi_probe(struct platform_device *pdev) struct qpic_spi_nand *qspi; struct qpic_ecc *ecc; struct resource *res; - const void *dev_data; int ret; ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL); @@ -1613,14 +1612,12 @@ static int qcom_spi_probe(struct platform_device *pdev) snandc->qspi->ctlr = ctlr; snandc->qspi->ecc = ecc; - dev_data = of_device_get_match_data(dev); - if (!dev_data) { + snandc->props = of_device_get_match_data(dev); + if (!snandc->props) { dev_err(&pdev->dev, "failed to get device data\n"); return -ENODEV; } - snandc->props = dev_data; - snandc->core_clk = devm_clk_get_enabled(dev, "core"); if (IS_ERR(snandc->core_clk)) return PTR_ERR(snandc->core_clk); From 04c3ee0f5d158f6e2027c7ef617044cb649ab4ab Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Wed, 9 Sep 2026 17:18:06 +0800 Subject: [PATCH 123/724] 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 6ca4474f5a362c502182d737589942bbd282330d Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 05:32:19 +0200 Subject: [PATCH 124/724] spi: docs: Use the multi-lane mode constants in examples The multi-lane documentation names SPI_MULTI_BUS_MODE_* constants, including in the transfer initializers. The interface introduced with the document uses SPI_MULTI_LANE_MODE_*, so the examples refer to undefined macros. Use the interface's constants in the descriptions and examples. Fixes: 05c3bd745bb0 ("spi: Documentation: add page on multi-lane support") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Link: https://patch.msgid.link/20260910033219.15996-1-kmehltretter@gmail.com Signed-off-by: Mark Brown --- Documentation/spi/multiple-data-lanes.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/spi/multiple-data-lanes.rst b/Documentation/spi/multiple-data-lanes.rst index 69cb532d052f..802aece35d56 100644 --- a/Documentation/spi/multiple-data-lanes.rst +++ b/Documentation/spi/multiple-data-lanes.rst @@ -127,7 +127,7 @@ field to indicate which mode they want to use for a given transfer. The possible values for this field have the following semantics: -- :c:macro:`SPI_MULTI_BUS_MODE_SINGLE`: Only use the first lane. Other lanes are +- :c:macro:`SPI_MULTI_LANE_MODE_SINGLE`: Only use the first lane. Other lanes are ignored. This means that it is operating just like a conventional SPI peripheral. This is the default, so it does not need to be explicitly set. @@ -149,7 +149,7 @@ The possible values for this field have the following semantics: ---------- ---------------- ---------- SDO 0 0-0-0-1-0-0-0-1 SDI 0 -- :c:macro:`SPI_MULTI_BUS_MODE_MIRROR`: Send a single data word over all of the +- :c:macro:`SPI_MULTI_LANE_MODE_MIRROR`: Send a single data word over all of the lanes at the same time. This only makes sense for writes and not for reads. @@ -160,7 +160,7 @@ The possible values for this field have the following semantics: struct spi_transfer xfer = { .tx_buf = tx_buf, .len = 1, - .multi_lane_mode = SPI_MULTI_BUS_MODE_MIRROR, + .multi_lane_mode = SPI_MULTI_LANE_MODE_MIRROR, }; spi_sync_transfer(spi, &xfer, 1); @@ -172,7 +172,7 @@ The possible values for this field have the following semantics: SDO 0 0-0-0-1-0-0-0-1 SDI 0 SDO 1 0-0-0-1-0-0-0-1 SDI 1 -- :c:macro:`SPI_MULTI_BUS_MODE_STRIPE`: Send or receive two different data words +- :c:macro:`SPI_MULTI_LANE_MODE_STRIPE`: Send or receive two different data words at the same time, one on each lane. This means that the buffer needs to be sized to hold data for all lanes. Data is interleaved in the buffer, with the first word corresponding to lane 0, the second to lane 1, and so on. @@ -185,7 +185,7 @@ The possible values for this field have the following semantics: struct spi_transfer xfer = { .rx_buf = rx_buf, .len = 2, - .multi_lane_mode = SPI_MULTI_BUS_MODE_STRIPE, + .multi_lane_mode = SPI_MULTI_LANE_MODE_STRIPE, }; spi_sync_transfer(spi, &xfer, 1); From 527d09d74d903eb995c25f4377d371130b4c1b58 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 31 Aug 2026 05:06:49 +0000 Subject: [PATCH 125/724] 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 126/724] 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 127/724] 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 128/724] 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 129/724] 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 130/724] 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 131/724] 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 132/724] 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 133/724] 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 134/724] 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 135/724] 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 136/724] 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 137/724] 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 138/724] 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 139/724] 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 140/724] 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 141/724] 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 142/724] 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 143/724] 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 144/724] 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 145/724] 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 146/724] 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 147/724] 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 148/724] 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 149/724] 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 150/724] 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 151/724] 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 152/724] 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 153/724] 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 154/724] 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 155/724] 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 156/724] 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 157/724] 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 158/724] 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 159/724] 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 160/724] 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 161/724] 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 162/724] 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 163/724] 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 164/724] 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 165/724] 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 166/724] 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 167/724] 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 168/724] 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 169/724] 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 170/724] 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 171/724] 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 172/724] 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 173/724] 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 174/724] 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 175/724] 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 176/724] 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 177/724] 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 178/724] 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 179/724] 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 180/724] 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 181/724] 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 3742867ce96539100ed5f4c754f04273d8aa00d7 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 12 Sep 2026 01:02:11 +0200 Subject: [PATCH 182/724] spi: Fix the return value in spi_new_ancillary_device() kernel-doc spi_new_ancillary_device() returns the new struct spi_device, or an ERR_PTR() on failure, but its kernel-doc says "0 on success; negative errno on failure", which was never true. Describe the pointer. Fixes: 0c79378c0199 ("spi: add ancillary device support") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260911230211.11755-1-kmehltretter@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 5b5b3bc5f0d8..172141650b64 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -2893,7 +2893,7 @@ static void of_register_spi_devices(struct spi_controller *ctlr) { } * * This may only be called from main SPI device's probe routine. * - * Return: 0 on success; negative errno on failure + * Return: the new device on success; an ERR_PTR() on failure */ struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 chip_select) From 4b258adc049d5c52e9e316eec2102ca821516cd1 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Tue, 8 Sep 2026 21:53:16 +0200 Subject: [PATCH 183/724] spi: spi-qpic-snand: remove unnecessary cast to '__le32 *' Remove an unnecessary cast to '__le32 *' in the qcom_spi_io_op() function. The 'reg_read_buf' member of the 'qcom_nand_controller' structure is defined as '__le32 *' already, so the cast is not needed. Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260908-qpic-snand-unnecessary-cast-v1-1-26df1cdd4bb6@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 9eafaea74f6b..cfcf51f4b249 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -1485,7 +1485,7 @@ static int qcom_spi_io_op(struct qcom_nand_controller *snandc, const struct spi_ if (copy_ftr) { qcom_nandc_dev_to_mem(snandc, true); - val = le32_to_cpu(*(__le32 *)snandc->reg_read_buf); + val = le32_to_cpu(*snandc->reg_read_buf); val >>= 8; memcpy(op->data.buf.in, &val, snandc->buf_count); From 81d2ec6f924e08efde646b60473997de0f568fd3 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Sat, 12 Sep 2026 09:24:18 +0100 Subject: [PATCH 184/724] 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 b9e5343ba72a07a12d364e8ba6874e2a8fb617c6 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:15:26 +0900 Subject: [PATCH 185/724] 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 186/724] 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 187/724] 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 188/724] 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 189/724] 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 190/724] 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 191/724] 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 192/724] 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 193/724] 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 14318426e2c00ffda93606df64f74c345021eaf8 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:17:01 +0530 Subject: [PATCH 194/724] 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 195/724] 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 196/724] 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 197/724] 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 198/724] 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 199/724] 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 200/724] 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 201/724] 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 202/724] 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 203/724] 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 204/724] 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 bd1041d3e1c03e17c2e594a271d2fc90da3a227a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 15 Sep 2026 08:33:18 +0200 Subject: [PATCH 205/724] 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 206/724] 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 207/724] 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 208/724] 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 209/724] 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 210/724] 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 211/724] 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 212/724] 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 ed7d8ed9a915633f90b0cf528b096abc80e250c9 Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Tue, 15 Sep 2026 09:49:40 +0800 Subject: [PATCH 213/724] 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 214/724] 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 215/724] 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 216/724] 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 217/724] 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 218/724] 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 219/724] 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 220/724] 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 221/724] 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 222/724] 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 223/724] 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 224/724] 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 225/724] 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 226/724] 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 227/724] 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 228/724] 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 229/724] 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 230/724] 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 231/724] 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 232/724] 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 233/724] 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 234/724] 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 235/724] 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 236/724] 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 237/724] 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 238/724] 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 239/724] 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 240/724] 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 241/724] 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 242/724] 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 243/724] 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 244/724] 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 245/724] 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 246/724] 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 26f42ca8becd01877e762916865a168c1b6c4ff4 Mon Sep 17 00:00:00 2001 From: Francesco Valla Date: Tue, 15 Sep 2026 22:24:36 +0200 Subject: [PATCH 247/724] spi: virtio: fix max frequency setting The maximum transfer frequency is read from the virtio config space but never propagated to the SPI framework logic. Fix this behavior and drop the useless setting copy from the driver's private data. Fixes: f98cabe3f6cf ("SPI: Add virtio SPI driver") Signed-off-by: Francesco Valla Link: https://patch.msgid.link/20260915-virtio-spi-fix2-v1-1-7a474cb1b13b@valla.it Signed-off-by: Mark Brown --- drivers/spi/spi-virtio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/spi/spi-virtio.c b/drivers/spi/spi-virtio.c index 3e181bd8bc94..96cdaf1b3697 100644 --- a/drivers/spi/spi-virtio.c +++ b/drivers/spi/spi-virtio.c @@ -33,8 +33,6 @@ struct virtio_spi_priv { struct virtqueue *vq; /* Copy of config space mode_func_supported */ u32 mode_func_supported; - /* Copy of config space max_freq_hz */ - u32 max_freq_hz; }; static void virtio_spi_msg_done(struct virtqueue *vq) @@ -304,7 +302,7 @@ static void virtio_spi_read_config(struct virtio_device *vdev) virtio_cread32(vdev, offsetof(struct virtio_spi_config, bits_per_word_mask)); - priv->max_freq_hz = + ctrl->max_speed_hz = virtio_cread32(vdev, offsetof(struct virtio_spi_config, max_freq_hz)); } From 62b223cf4334c646bbb0b842ca0ff13c5cd0e875 Mon Sep 17 00:00:00 2001 From: Francesco Valla Date: Tue, 15 Sep 2026 22:24:37 +0200 Subject: [PATCH 248/724] spi: virtio: drop unused field from private data The mode_func_supported field is read from the config space into the driver's private data, but then never used outside of the function it is read in. Drop the variable from the private data and parse it from the stack instead. Signed-off-by: Francesco Valla Link: https://patch.msgid.link/20260915-virtio-spi-fix2-v1-2-7a474cb1b13b@valla.it Signed-off-by: Mark Brown --- drivers/spi/spi-virtio.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-virtio.c b/drivers/spi/spi-virtio.c index 96cdaf1b3697..cdee3a78979b 100644 --- a/drivers/spi/spi-virtio.c +++ b/drivers/spi/spi-virtio.c @@ -31,8 +31,6 @@ struct virtio_spi_priv { struct virtio_device *vdev; /* Pointer to the virtqueue */ struct virtqueue *vq; - /* Copy of config space mode_func_supported */ - u32 mode_func_supported; }; static void virtio_spi_msg_done(struct virtqueue *vq) @@ -256,7 +254,7 @@ static int virtio_spi_transfer_one(struct spi_controller *ctrl, static void virtio_spi_read_config(struct virtio_device *vdev) { struct spi_controller *ctrl = dev_get_drvdata(&vdev->dev); - struct virtio_spi_priv *priv = vdev->priv; + u32 mode_func_supported; u8 cs_max_number; u8 tx_nbits_supported; u8 rx_nbits_supported; @@ -266,18 +264,18 @@ static void virtio_spi_read_config(struct virtio_device *vdev) ctrl->num_chipselect = cs_max_number; /* Set the mode bits which are understood by this driver */ - priv->mode_func_supported = + mode_func_supported = virtio_cread32(vdev, offsetof(struct virtio_spi_config, mode_func_supported)); - ctrl->mode_bits = priv->mode_func_supported & + ctrl->mode_bits = mode_func_supported & (VIRTIO_SPI_CS_HIGH | VIRTIO_SPI_MODE_LSB_FIRST); - if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPHA_1) + if (mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPHA_1) ctrl->mode_bits |= VIRTIO_SPI_CPHA; - if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPOL_1) + if (mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPOL_1) ctrl->mode_bits |= VIRTIO_SPI_CPOL; - if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LSB_FIRST) + if (mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LSB_FIRST) ctrl->mode_bits |= SPI_LSB_FIRST; - if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LOOPBACK) + if (mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LOOPBACK) ctrl->mode_bits |= SPI_LOOP; tx_nbits_supported = virtio_cread8(vdev, offsetof(struct virtio_spi_config, From f94c9b68bb5fdbef9f841da493d5305ff634ebb5 Mon Sep 17 00:00:00 2001 From: Johan Alvarado Date: Fri, 11 Sep 2026 13:44:15 -0500 Subject: [PATCH 249/724] spi: spi-qpic-snand: publish the ECC context to snandc->qspi qcom_spi_ooblayout_ecc() and qcom_spi_ooblayout_free() read the ECC configuration through snandc->qspi->ecc. qcom_spi_probe() points it at a zeroed scratch struct and only qcom_spi_ecc_prepare_io_req_pipelined(), which runs on page I/O, ever updates it. qcom_spi_ecc_init_ctx_pipelined() installs the ooblayout but does not publish the context it just allocated, and qcom_spi_ecc_cleanup_ctx_pipelined() frees that context without clearing the pointer. spinand_init() calls mtd_ooblayout_count_freebytes() right after the ECC context is created and before any page I/O, so the ooblayout always runs against a pointer that does not describe the current context: - On a first probe it reads the zeroed struct from qcom_spi_probe(), so steps, bytes and bbm_size are 0. The count then returns 0 rather than an error, so the probe continues with mtd->oobavail set to 0. - On a probe retry it reads the ecc_cfg the previous attempt freed. A retry is easy to hit. On IPQ5018 with the qcom,smem-part parser the partition parse returns -EPROBE_DEFER until SMEM has probed, so the first spi-nand probe defers. It defers inside mtd_device_parse_register(), after mtd_otp_nvmem_add() has already read the factory OTP - that read goes through prepare_io_req and leaves snandc->qspi->ecc pointing at the context that spinand_cleanup() then frees. The second probe allocates a new context, never publishes it, and computes the OOB layout from the freed one. Once the slab has been reused, qecc->steps holds garbage and oobregion->length = qecc->steps * 4; goes negative. qcom_spi_ooblayout_free() only reports -ERANGE for section 1 and later, so mtd_ooblayout_count_bytes() sums the regions and returns that negative length as the byte count. The -512 below is steps * 4 with steps == -128. It is a byte count that happens to collide with -ERESTARTSYS, not an error the driver returned. spinand_init() takes it as an error, and because it is not -EPROBE_DEFER the driver core never retries and the NAND never appears: spi-nand spi0.0: ESMT SPI NAND was found. spi-nand spi0.0: probe with driver spi-nand failed with error -512 UBI error: cannot open mtd rootfs, error -2 Waiting for root device /dev/ubiblock0_1... On a Mercusys MR80X (IPQ5018, ESMT F50D1G41LB) about half of the boots failed to mount the rootfs, the outcome depending on whether the freed memory had been overwritten yet. Publish the context when it is created and clear the pointer when it is destroyed. Clearing leaves snandc->qspi->ecc NULL after cleanup, which is safe: the mtd is unregistered before cleanup_ctx runs, so no ooblayout callback can follow. Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface") Cc: stable@vger.kernel.org Tested-by: Gabor Juhos Signed-off-by: Johan Alvarado Link: https://patch.msgid.link/20260911184416.109790-2-contact@c127.dev Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index cfcf51f4b249..e96a2bdbb69d 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -411,6 +411,8 @@ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand) dev_dbg(snandc->dev, "ECC strength: %u bits per %u bytes\n", ecc_cfg->strength, ecc_cfg->step_size); + snandc->qspi->ecc = ecc_cfg; + return 0; err_free_ecc_cfg: @@ -427,6 +429,7 @@ static void qcom_spi_ecc_cleanup_ctx_pipelined(struct nand_device *nand) kfree(snandc->qspi->oob_buf); snandc->qspi->oob_buf = NULL; + snandc->qspi->ecc = NULL; kfree(ecc_cfg); } From 8d691164547832f43289a7d5f3450738ead98c2d Mon Sep 17 00:00:00 2001 From: Johan Alvarado Date: Fri, 11 Sep 2026 13:44:16 -0500 Subject: [PATCH 250/724] spi: spi-qpic-snand: drop the redundant ECC context handling qcom_spi_ecc_init_ctx_pipelined() now publishes the ECC context to snandc->qspi->ecc, so the assignment in qcom_spi_ecc_prepare_io_req_pipelined() repeats what the pointer already holds, and the zeroed struct qpic_ecc that qcom_spi_probe() allocates is never read. The pointer is non-NULL only between context creation and destruction, and every reader runs inside that window. The ooblayout callbacks are installed by init_ctx. The page read, write and program helpers run only when prepare_io_req has set page_rw or oob_rw. qcom_spi_block_erase() runs only while the mtd is registered, which happens after init_ctx and ends before cleanup_ctx. The controller drives a single chip select, so the per-controller pointer and the per-chip context cannot disagree. Remove both. No functional change. Suggested-by: Gabor Juhos Signed-off-by: Johan Alvarado Tested-by: Gabor Juhos Link: https://patch.msgid.link/20260911184416.109790-3-contact@c127.dev Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index e96a2bdbb69d..c3cdc43daa21 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -437,9 +437,7 @@ static int qcom_spi_ecc_prepare_io_req_pipelined(struct nand_device *nand, struct nand_page_io_req *req) { struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand); - struct qpic_ecc *ecc_cfg = nand_to_ecc_ctx(nand); - snandc->qspi->ecc = ecc_cfg; snandc->qspi->raw_rw = false; snandc->qspi->oob_rw = false; snandc->qspi->page_rw = false; @@ -1586,14 +1584,9 @@ static int qcom_spi_probe(struct platform_device *pdev) struct spi_controller *ctlr; struct qcom_nand_controller *snandc; struct qpic_spi_nand *qspi; - struct qpic_ecc *ecc; struct resource *res; int ret; - ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL); - if (!ecc) - return -ENOMEM; - qspi = devm_kzalloc(dev, sizeof(*qspi), GFP_KERNEL); if (!qspi) return -ENOMEM; @@ -1613,7 +1606,6 @@ static int qcom_spi_probe(struct platform_device *pdev) snandc->dev = dev; snandc->qspi = qspi; snandc->qspi->ctlr = ctlr; - snandc->qspi->ecc = ecc; snandc->props = of_device_get_match_data(dev); if (!snandc->props) { From d5212d2d50f36a132568534ca637b56ae50f3f02 Mon Sep 17 00:00:00 2001 From: Khanh Le Date: Thu, 17 Sep 2026 03:02:42 +0000 Subject: [PATCH 251/724] 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/724] 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/724] 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/724] 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/724] 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/724] 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/724] 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/724] 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/724] 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/724] 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/724] 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/724] 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/724] 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/724] 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 648e3c436032b675b339662ea9c0edd9221add76 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Wed, 2 Sep 2026 22:48:09 +0800 Subject: [PATCH 265/724] spi: mxic: fix ps_clk leak in runtime resume mxic_spi_runtime_resume() enables ps_clk before calling mxic_spi_clk_enable(). If the latter fails, ps_clk was not disabled. Unwind ps_clk with clk_disable_unprepare() before returning the error. Fixes: b942d80b0a39 ("spi: Add MXIC controller driver") Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260902-mxic-v1-1-76806325826d@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-mxic.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-mxic.c b/drivers/spi/spi-mxic.c index e3c85e5191e3..481edd4b35cb 100644 --- a/drivers/spi/spi-mxic.c +++ b/drivers/spi/spi-mxic.c @@ -744,7 +744,13 @@ static int mxic_spi_runtime_resume(struct device *dev) return ret; } - return mxic_spi_clk_enable(mxic); + ret = mxic_spi_clk_enable(mxic); + if (ret) { + clk_disable_unprepare(mxic->ps_clk); + return ret; + } + + return 0; } static const struct dev_pm_ops mxic_spi_dev_pm_ops = { From beb56d1d3de522885b30bb97fee83825041d65c2 Mon Sep 17 00:00:00 2001 From: Karl Asseily Date: Fri, 18 Sep 2026 21:23:14 +0300 Subject: [PATCH 266/724] 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 267/724] 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 268/724] 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 269/724] 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 270/724] 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 271/724] 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 272/724] 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 273/724] 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 274/724] 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 001cdc434aa89d1e6c7ea3e5e118167e02dffe20 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Fri, 18 Sep 2026 21:53:08 +0200 Subject: [PATCH 275/724] spi: spi-qpic-snand: add qcom_spi_config_page_read() helper Move the register write sequence needed for configuring a page read into a separate function to reduce code duplication. No functional changes. Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260918-qpic-snand-config_page_read-v1-1-a686c4657857@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 45 ++++++++++++------------------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 9e626ffbe63a..aaa40753d274 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -501,6 +501,17 @@ static void qcom_spi_set_read_loc(struct qcom_nand_controller *snandc, int cw, i read_size, is_last_read_loc); } +static void qcom_spi_config_page_read(struct qcom_nand_controller *snandc) +{ + qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); + qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); + qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, + NAND_ERASED_CW_DETECT_CFG, 1, 0); + qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, + NAND_ERASED_CW_DETECT_CFG, 1, + NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); +} + static void qcom_spi_config_cw_read(struct qcom_nand_controller *snandc, bool use_ecc, int cw) { @@ -564,13 +575,7 @@ static void qcom_spi_config_single_cw_page_read(struct qcom_nand_controller *sna __le32 *reg = &snandc->regs->read_location0; int num_cw = snandc->qspi->num_cw; - qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, - NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); + qcom_spi_config_page_read(snandc); if (cw == (num_cw - 1)) { reg = &snandc->regs->read_location_last0; @@ -763,14 +768,7 @@ static int qcom_spi_read_cw_raw(struct qcom_nand_controller *snandc, u8 *data_bu snandc->regs->exec = cpu_to_le32(1); qcom_spi_set_read_loc(snandc, raw_cw, 0, 0, ecc_cfg->cw_size, 1); - - qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, - NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); + qcom_spi_config_page_read(snandc); data_size1 = mtd->writesize - ecc_cfg->cw_size * (num_cw - 1); oob_size1 = ecc_cfg->bbm_size; @@ -875,14 +873,7 @@ static int qcom_spi_read_page_ecc(struct qcom_nand_controller *snandc, snandc->regs->exec = cpu_to_le32(1); qcom_clear_bam_transaction(snandc); - - qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, - NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); + qcom_spi_config_page_read(snandc); for (i = 0; i < num_cw; i++) { int data_size, oob_size; @@ -963,13 +954,7 @@ static int qcom_spi_read_page_oob(struct qcom_nand_controller *snandc, snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg); snandc->regs->exec = cpu_to_le32(1); - qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); - qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, - NAND_ERASED_CW_DETECT_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, - NAND_ERASED_CW_DETECT_CFG, 1, - NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); + qcom_spi_config_page_read(snandc); for (i = 0; i < num_cw; i++) { int data_size, oob_size; From 3c82e199d411af3a0fff00ca3c54baaf04abbbb3 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 20 Sep 2026 21:47:13 -0700 Subject: [PATCH 276/724] spi: spi-master: use literal blocks for commands Use literal blocks for console I/O to separate it from the surrounding text. Otherwise the lines run together, without any clear break before or after the commands. Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260921044713.93122-1-rdunlap@infradead.org Signed-off-by: Mark Brown --- Documentation/ABI/testing/sysfs-class-spi-master | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-spi-master b/Documentation/ABI/testing/sysfs-class-spi-master index 0a524fcd96d2..4e1e8a7ca83c 100644 --- a/Documentation/ABI/testing/sysfs-class-spi-master +++ b/Documentation/ABI/testing/sysfs-class-spi-master @@ -17,7 +17,8 @@ Description: (WO) Instantiate a new SPI device on bus B, where B with delete_device; platform and DT devices are not affected. - Example: + Example:: + # echo spidev 0 > /sys/class/spi_master/spi0/new_device # echo spidev 0 10000000 > /sys/class/spi_master/spi0/new_device # echo spidev 0 10000000 3 > /sys/class/spi_master/spi0/new_device @@ -30,5 +31,6 @@ Description: (WO) Remove a SPI device previously created via new_device. Takes a single parameter: the chip select number of the device to remove. - Example: + Example:: + # echo 0 > /sys/class/spi_master/spi0/delete_device From 4454102d6565afedfcde64b222595f067a5b7e93 Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Thu, 17 Sep 2026 11:33:59 +0100 Subject: [PATCH 277/724] 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 278/724] 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 279/724] 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 280/724] 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 281/724] 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 282/724] 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 283/724] 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 284/724] 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 285/724] 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 2c68b2403acd1cbf0b19c71dab9858cd2ca6bbb5 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Mon, 21 Sep 2026 12:16:44 +0200 Subject: [PATCH 286/724] spi: dt-bindings: Clarify what rx-sample-delay-ns describes The property is documented as an offset from the controller's default sampling time. That says what the value does, but not what it is meant to describe, and it leaves room for two readings: a delay that belongs to the board, or a stand-in for the peripheral's own clock-to-output-valid time, which has no other expression in a device tree today. The first reading is the original one. The property was introduced for Rockchip in commit 76b17e6e4923 ("spi/rockchip: Add device tree property to configure Rx Sample Delay") to deal with "boards with high-capacitance SPI lines", where "the controller samples the Rx data line too early". The current wording arrived later, in commit 5ce78f4456a9 ("dt-bindings: snps, dw-apb-ssi: Add sparx5 support, plus rx-sample-delay-ns property"), where it described a DesignWare register and was qualified as such. That qualification was dropped when the property was moved to the generic schema in commit b658be56e867 ("spi: dt-bindings: Move 'rx-sample-delay-ns' to spi-peripheral-props.yaml"), leaving a register description standing in for a definition. The distinction matters because the two compose. A board delay is specific to one design, while a datasheet timing parameter is the same wherever the chip is soldered, so a mechanism that derives the chip side from the chip would add to a value that already accounts for it. Spell out that the property describes the board. Nothing changes in what the value means or in how existing device trees are interpreted. Assisted-by: Claude:claude-opus-5 Signed-off-by: Frieder Schrempf Link: https://patch.msgid.link/20260921-spi-sample-delay-cleanup-v2-1-b460d572fd32@kontron.de Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/spi-peripheral-props.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml index 880a9f624566..afcf9c41c058 100644 --- a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml +++ b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml @@ -91,6 +91,14 @@ properties: The delay from the default sample time before the actual sample of the rxd input signal occurs. + This describes the board rather than the peripheral, namely the extra + delay this particular design needs, for example because of the flight time + of the clock and data signals between controller and peripheral. + + Timing parameters of the peripheral itself, such as its + clock-to-output-valid time, are the same on every board using that chip + and should be described with the chip. + spi-tx-bus-width: description: Bus width to the SPI bus used for write transfers. From 631a5633638136add14406d5bc18ca113492cd77 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Mon, 21 Sep 2026 12:16:45 +0200 Subject: [PATCH 287/724] spi: Parse the rx-sample-delay-ns peripheral property in the core "rx-sample-delay-ns" is documented in spi-peripheral-props.yaml as a generic SPI peripheral property, but the core never looks at it. Only spi-dw reads it from the peripheral node as the binding describes, and any other driver wanting to act on it would have to duplicate that. Add spi_device.rx_sample_delay_ns and parse the property once, in __spi_add_device(), which covers every way a device can be instantiated and runs before ->setup(). Use device_property_read_u32() rather than adding this to of_spi_parse_dt(). spi-dw already uses the fwnode accessor, so the property works today on ACPI and software node platforms such as the ones served by spi-dw-pci, and parsing it only in the device tree path would quietly drop it there. No functional change, since nothing reads the new field yet. Assisted-by: Claude:claude-opus-5 Signed-off-by: Frieder Schrempf Link: https://patch.msgid.link/20260921-spi-sample-delay-cleanup-v2-2-b460d572fd32@kontron.de Signed-off-by: Mark Brown --- drivers/spi/spi.c | 9 +++++++++ include/linux/spi/spi.h | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 5b5b3bc5f0d8..9e3c24ed8f9f 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -929,6 +929,15 @@ static int __spi_add_device(struct spi_device *spi, struct spi_device *parent) } } + /* + * Peripheral properties the core handles on behalf of controller + * drivers are parsed here, rather than in the firmware specific + * instantiation paths, so that device tree, ACPI and software nodes + * are covered alike, and early enough for ->setup() to act on them. + */ + device_property_read_u32(&spi->dev, "rx-sample-delay-ns", + &spi->rx_sample_delay_ns); + /* * Drivers may modify this initial i/o setup, but will * normally rely on the device being setup. Devices diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 88d17fce02dc..fb4baa0d3398 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -169,6 +169,12 @@ extern void spi_transfer_cs_change_delay_exec(struct spi_message *msg, * @cs_inactive: delay to be introduced by the controller after CS is * deasserted. If @cs_change_delay is used from @spi_transfer, then the * two delays will be added up. + * @rx_sample_delay_ns: Delay in nanoseconds by which the controller should + * postpone sampling the incoming data, relative to the sampling point it + * uses by default. Describes the board rather than the device, namely the + * flight time of the clock and data signals between controller and + * device, and comes from the "rx-sample-delay-ns" property. Zero when the + * property is absent. * @chip_select: Array of physical chipselect, spi->chipselect[i] gives * the corresponding physical CS for logical CS i. * @num_chipselect: Number of physical chipselects used. @@ -235,6 +241,9 @@ struct spi_device { struct spi_delay cs_hold; struct spi_delay cs_inactive; + /* Additional delay before the incoming data is sampled, in ns */ + u32 rx_sample_delay_ns; + u8 chip_select[SPI_DEVICE_CS_CNT_MAX]; u8 num_chipselect; From 38d4b42e4a2af5066b602867042c10c699bd08ad Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Mon, 21 Sep 2026 12:16:46 +0200 Subject: [PATCH 288/724] spi: dw: Use the rx-sample-delay-ns value parsed by the core The core now parses the "rx-sample-delay-ns" peripheral property into spi_device.rx_sample_delay_ns, so drop the private copy of that parsing. The controller-wide default is deliberately left alone. It is read from the *controller* node into dws->def_rx_sample_dly_ns, which is a different node and none of the core's business when it parses properties of a peripheral. One corner case changes: the peripheral value is now treated as unset when it is zero, whereas before an absent property could be told apart from an explicit "rx-sample-delay-ns = <0>", the latter overriding a non-zero controller default with no delay at all. There are no in-tree users affected by this and it is very unlikely for out-of-tree users to set rx-sample-delay-ns on the controller level and then revert it to 0 at the chip level. We should abandon this configuration that was probably never meant to work like this and doesn't make sense. Assisted-by: Claude:claude-opus-5 Signed-off-by: Frieder Schrempf Link: https://patch.msgid.link/20260921-spi-sample-delay-cleanup-v2-3-b460d572fd32@kontron.de Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 206d3f9dd83d..b86b607cc817 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -1104,12 +1104,13 @@ static int dw_spi_setup(struct spi_device *spi) if (!chip) return -ENOMEM; spi_set_ctldata(spi, chip); - /* Get specific / default rx-sample-delay */ - if (device_property_read_u32(&spi->dev, - "rx-sample-delay-ns", - &rx_sample_dly_ns) != 0) - /* Use default controller value */ - rx_sample_dly_ns = dws->def_rx_sample_dly_ns; + /* + * Use the per-device value the core parsed from the peripheral + * node, and fall back to the controller-wide default when the + * device does not ask for a delay of its own. + */ + rx_sample_dly_ns = spi->rx_sample_delay_ns ?: + dws->def_rx_sample_dly_ns; chip->rx_sample_dly = DIV_ROUND_CLOSEST(rx_sample_dly_ns, NSEC_PER_SEC / dws->max_freq); From f42fbd560cd834c7d10118743683def979c68739 Mon Sep 17 00:00:00 2001 From: Baojun Xu Date: Mon, 21 Sep 2026 19:19:30 +0800 Subject: [PATCH 289/724] 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 290/724] 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 291/724] 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 1f82e6956faacc6e0c1b402838ba188e19af5a3c Mon Sep 17 00:00:00 2001 From: Jonas Jelonek Date: Mon, 21 Sep 2026 21:31:45 +0000 Subject: [PATCH 292/724] spi: realtek-rtl: add support for second CS The initial driver assumed that the controller's second chip select was vestigial because none of the devices examined at the time used it. This does not hold for all hardware: there are devices with a peripheral connected to CS1. Since the controller only advertises one chip select and keeps CS1 permanently deasserted, those peripherals cannot be used. Advertise both native chip selects and select the appropriate CSBx bit in ->set_cs(). Build the initial SFCSR control value from zero. The datasheet specifies CHIP_SEL for MMIO mode only, so keep it clear for this PIO driver and set only the two active-low CSB bits to leave both lines deasserted. Rename the callback argument from "active" to "level" while touching this code. ->set_cs() receives the physical line level, not a logical assertion state: after applying SPI_CS_HIGH, the core passes false to drive the line low and true to drive it high. The CSBx bits directly encode that level, and the native outputs are active-low, so clear the selected bit for a low/asserted line and set it for a high/deasserted line. Signed-off-by: Jonas Jelonek Link: https://patch.msgid.link/20260921213145.671282-1-jonas@jonasjelonek.de Signed-off-by: Mark Brown --- drivers/spi/spi-realtek-rtl.c | 36 +++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/drivers/spi/spi-realtek-rtl.c b/drivers/spi/spi-realtek-rtl.c index e1c40ff2e49d..7b250b447abf 100644 --- a/drivers/spi/spi-realtek-rtl.c +++ b/drivers/spi/spi-realtek-rtl.c @@ -18,7 +18,6 @@ struct rtspi { #define RTL_SPI_SFCSR_CSB0 BIT(31) #define RTL_SPI_SFCSR_CSB1 BIT(30) #define RTL_SPI_SFCSR_RDY BIT(27) -#define RTL_SPI_SFCSR_CS BIT(24) #define RTL_SPI_SFCSR_LEN_MASK ~(0x03 << 28) #define RTL_SPI_SFCSR_LEN1 (0x00 << 28) #define RTL_SPI_SFCSR_LEN4 (0x03 << 28) @@ -29,17 +28,31 @@ struct rtspi { #define REG(x) (rtspi->base + x) -static void rt_set_cs(struct spi_device *spi, bool active) +static void rt_set_cs(struct spi_device *spi, bool level) { struct rtspi *rtspi = spi_controller_get_devdata(spi->controller); - u32 value; + unsigned int cs = spi_get_chipselect(spi, 0); + u32 cs_mask, value; + + switch (cs) { + case 0: + cs_mask = RTL_SPI_SFCSR_CSB0; + break; + case 1: + cs_mask = RTL_SPI_SFCSR_CSB1; + break; + default: + return; + } - /* CS0 bit is active low */ value = __raw_readl(REG(RTL_SPI_SFCSR)); - if (active) - value |= RTL_SPI_SFCSR_CSB0; + + /* CSBx is active low */ + if (level) + value |= cs_mask; else - value &= ~RTL_SPI_SFCSR_CSB0; + value &= ~cs_mask; + __raw_writel(value, REG(RTL_SPI_SFCSR)); } @@ -138,11 +151,9 @@ static void init_hw(struct rtspi *rtspi) value |= RTL_SPI_SFCR_RBO | RTL_SPI_SFCR_WBO; __raw_writel(value, REG(RTL_SPI_SFCR)); - value = __raw_readl(REG(RTL_SPI_SFCSR)); - /* Permanently disable CS1, since it's never used */ - value |= RTL_SPI_SFCSR_CSB1; - /* Select CS0 for use */ - value &= RTL_SPI_SFCSR_CS; + /* CHIP_SEL is only used in MMIO mode; CSB0/CSB1 are active low. */ + value = 0; + value |= RTL_SPI_SFCSR_CSB0 | RTL_SPI_SFCSR_CSB1; __raw_writel(value, REG(RTL_SPI_SFCSR)); } @@ -171,6 +182,7 @@ static int realtek_rtl_spi_probe(struct platform_device *pdev) ctrl->flags = SPI_CONTROLLER_HALF_DUPLEX; ctrl->set_cs = rt_set_cs; ctrl->transfer_one = transfer_one; + ctrl->num_chipselect = 2; err = devm_spi_register_controller(&pdev->dev, ctrl); if (err) { From 7325b35a12d7bdab94b9712c07f226c9354c65b4 Mon Sep 17 00:00:00 2001 From: Oder Chiou Date: Thu, 17 Sep 2026 18:42:44 +0800 Subject: [PATCH 293/724] 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 51e04054e2994ccbf79a2596853e89d539956659 Mon Sep 17 00:00:00 2001 From: Thanh Quan Date: Tue, 22 Sep 2026 10:27:00 +0200 Subject: [PATCH 294/724] spi: dt-bindings: renesas,sh-msiof: Add support for R-Car X5H Document support for Clock-Synchronized Serial Interfaces with FIFO (MSIOF) in the Renesas R-Car X5H (R8A78000) SoC, which is the first member of the R-Car Gen5 family. On R-Car X5H, MSIOF modules are wired to three DMACs, hence increase the maximum number of DMA channels to six. Signed-off-by: Thanh Quan Signed-off-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/b8f4648eb1151b75d41e561c6b2adbd9bc4c0782.1790065451.git.geert+renesas@glider.be Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/renesas,sh-msiof.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml b/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml index e0c7047ae8ad..41e423a42654 100644 --- a/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml +++ b/Documentation/devicetree/bindings/spi/renesas,sh-msiof.yaml @@ -54,6 +54,11 @@ properties: - renesas,msiof-r8a779h0 # R-Car V4M - const: renesas,rcar-gen4-msiof # generic R-Car Gen4 # compatible device + - items: + - enum: + - renesas,msiof-r8a78000 # R-Car X5H + - const: renesas,rcar-gen5-msiof # generic R-Car Gen5 + # compatible device - items: - const: renesas,sh-msiof # deprecated @@ -100,11 +105,11 @@ properties: dmas: minItems: 2 - maxItems: 4 + maxItems: 6 dma-names: minItems: 2 - maxItems: 4 + maxItems: 6 items: enum: [ tx, rx ] From 8157bb8232a46e3596d396735bfbc3572ea823f5 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 22 Sep 2026 10:27:01 +0200 Subject: [PATCH 295/724] spi: sh-msiof: Add support for R-Car Gen5 The Clock-Synchronized Serial Interfaces with FIFO (MSIOF) on R-Car Gen5 have FIFOs with 256 entries, just like on R-Car Gen4. Signed-off-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/a254e27481716a07a49b10db31523fcaa3114afb.1790065451.git.geert+renesas@glider.be Signed-off-by: Mark Brown --- drivers/spi/spi-sh-msiof.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c index 1aeab7ec0bc8..f493df5df9e4 100644 --- a/drivers/spi/spi-sh-msiof.c +++ b/drivers/spi/spi-sh-msiof.c @@ -1013,6 +1013,7 @@ static const struct of_device_id sh_msiof_match[] __maybe_unused = { { .compatible = "renesas,msiof-r8a779a0", .data = &rcar_gen3_data }, { .compatible = "renesas,msiof-r8a779f0", .data = &rcar_gen3_data }, { .compatible = "renesas,rcar-gen4-msiof", .data = &rcar_gen4_data }, + { .compatible = "renesas,rcar-gen5-msiof", .data = &rcar_gen4_data }, { .compatible = "renesas,sh-msiof", .data = &sh_data }, /* Deprecated */ { /* sentinel */ } }; From 6d3400863baea26232cb1a85948887fa2b7df700 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:10 +0800 Subject: [PATCH 296/724] 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 297/724] 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 298/724] 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 299/724] 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 300/724] 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 301/724] 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 302/724] 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 303/724] 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 304/724] 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 305/724] 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 306/724] 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 307/724] 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 308/724] 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 309/724] 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 310/724] 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 311/724] 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 312/724] 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 313/724] 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 314/724] 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 315/724] 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 316/724] 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 317/724] 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 318/724] 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 319/724] 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 320/724] 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 321/724] 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 322/724] 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 323/724] 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 324/724] 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 325/724] 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 326/724] 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 327/724] 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 328/724] 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 329/724] 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 330/724] 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 331/724] 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 cb5a15943d7d9c78cc5bef86ee080010c1029c70 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Wed, 16 Sep 2026 23:30:55 +0800 Subject: [PATCH 332/724] spi: use dmaengine_get_dma_device() for DMA mapping Replace chan->device->dev with dmaengine_get_dma_device(chan) in the SPI core (spi.c) and SPI mem (spi-mem.c) DMA mapping helpers so that DMA mapping goes through the correct device when the DMA controller has per-channel IOMMU domains (chan_dma_dev=true). Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Peng Fan Reviewed-by: Frank Li Link: https://patch.msgid.link/20260916-spi-dma-v1-1-2b30657d373d@nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-mem.c | 8 ++++---- drivers/spi/spi.c | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 94d122be1c93..b94680b60af9 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -46,9 +46,9 @@ int spi_controller_dma_map_mem_op_data(struct spi_controller *ctlr, return -EINVAL; if (op->data.dir == SPI_MEM_DATA_OUT && ctlr->dma_tx) - dmadev = ctlr->dma_tx->device->dev; + dmadev = dmaengine_get_dma_device(ctlr->dma_tx); else if (op->data.dir == SPI_MEM_DATA_IN && ctlr->dma_rx) - dmadev = ctlr->dma_rx->device->dev; + dmadev = dmaengine_get_dma_device(ctlr->dma_rx); else dmadev = ctlr->dev.parent; @@ -92,9 +92,9 @@ void spi_controller_dma_unmap_mem_op_data(struct spi_controller *ctlr, return; if (op->data.dir == SPI_MEM_DATA_OUT && ctlr->dma_tx) - dmadev = ctlr->dma_tx->device->dev; + dmadev = dmaengine_get_dma_device(ctlr->dma_tx); else if (op->data.dir == SPI_MEM_DATA_IN && ctlr->dma_rx) - dmadev = ctlr->dma_rx->device->dev; + dmadev = dmaengine_get_dma_device(ctlr->dma_rx); else dmadev = ctlr->dev.parent; diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 6bd6478f766e..015d93680e5d 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1465,14 +1465,14 @@ int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) return 0; if (ctlr->dma_tx) - tx_dev = ctlr->dma_tx->device->dev; + tx_dev = dmaengine_get_dma_device(ctlr->dma_tx); else if (ctlr->dma_map_dev) tx_dev = ctlr->dma_map_dev; else tx_dev = ctlr->dev.parent; if (ctlr->dma_rx) - rx_dev = ctlr->dma_rx->device->dev; + rx_dev = dmaengine_get_dma_device(ctlr->dma_rx); else if (ctlr->dma_map_dev) rx_dev = ctlr->dma_map_dev; else From 92871abc1302b852b88d86114115f8fd6b50b9b9 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 16 Sep 2026 17:40:42 +0800 Subject: [PATCH 333/724] 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 2f06fa7a4150debe9c89c13a1d6ee13ffd881271 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 17 Aug 2026 23:14:03 +0800 Subject: [PATCH 334/724] spi: dw: use DW_SPI_ISR directly The DW_SPI_ISR register reports the masked interrupts, no need to mask again. Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260817151404.28324-2-jszhang@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index b86b607cc817..ed6e688cfafb 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -275,7 +275,7 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id) { struct spi_controller *ctlr = dev_id; struct dw_spi *dws = spi_controller_get_devdata(ctlr); - u16 irq_status = dw_readl(dws, DW_SPI_ISR) & DW_SPI_INT_MASK; + u16 irq_status = dw_readl(dws, DW_SPI_ISR); if (!irq_status) return IRQ_NONE; From eb169cc54b87fc2c633c2343c6060c5955efb69d Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Thu, 24 Sep 2026 10:05:10 +0800 Subject: [PATCH 335/724] 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 336/724] 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 337/724] 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 338/724] 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 339/724] 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 340/724] 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 341/724] 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 342/724] 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 343/724] 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 344/724] 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 345/724] 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 346/724] 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 347/724] 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 348/724] 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 88ca7a587f737ff717565063518e0a555e110f07 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:43:51 +0200 Subject: [PATCH 349/724] spi: spidev_test: include tools/include Add tools/include to include paths to allow using macro helpers. Use ARRAY_SIZE() from tools/include/linux/kernel.h instead of maintaining a copy. Remove the obsolete comment with cross-compile instructions from the header since spidev_test is integrated with the tools buildsystem since commit f325b73dc4db ("spi: tools: move to tools buildsystem"). Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-1-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/Makefile | 2 +- tools/spi/spidev_test.c | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tools/spi/Makefile b/tools/spi/Makefile index 7fccd245a535..8cf21ff8cd03 100644 --- a/tools/spi/Makefile +++ b/tools/spi/Makefile @@ -12,7 +12,7 @@ endif # (this improves performance and avoids hard-to-debug behaviour); MAKEFLAGS += -r -CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include +CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include -I$(srctree)/tools/include ALL_TARGETS := spidev_test spidev_fdx ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index f2135d619a0b..bd5ee7a6ac82 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -4,8 +4,6 @@ * * Copyright (c) 2007 MontaVista Software, Inc. * Copyright (c) 2007 Anton Vorontsov - * - * Cross-compile with cross-gcc -I/path/to/cross-kernel/include */ #include @@ -22,8 +20,7 @@ #include #include #include - -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#include static void pabort(const char *s) { From a9e74a4c991126e263e2984fca327267ade5e224 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:43:52 +0200 Subject: [PATCH 350/724] spi: spidev_test: clarify usage for --size By default, spidev_test transmits the predetermined message default_tx. Given the --size parameter, the given number of random bytes are transmitted instead. Make all the input options (random bytes, string, file) mutually exclusive. Clarify the help text for --size. While at it, make the -p help text consistently lowercase. Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-2-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index bd5ee7a6ac82..0bd6ae419cbf 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -44,6 +44,7 @@ static int verbose; static int transfer_size; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ +static int input_choices; static uint8_t default_tx[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, @@ -191,8 +192,8 @@ static void print_usage(const char *prog) "data:\n" " -i --input input data from a file (e.g. \"test.bin\")\n" " -o --output output data to a file (e.g. \"results.bin\")\n" - " -p Send data (e.g. \"1234\\xde\\xad\")\n" - " -S --size transfer size\n" + " -p send data (e.g. \"1234\\xde\\xad\")\n" + " -S --size transfer the given number of random bytes\n" " -I --iter iterations\n" "additional parameters:\n" " -b --bpw bits per word\n" @@ -262,6 +263,7 @@ static void parse_opts(int argc, char *argv[]) break; case 'i': input_file = optarg; + input_choices++; break; case 'o': output_file = optarg; @@ -304,6 +306,7 @@ static void parse_opts(int argc, char *argv[]) break; case 'p': input_tx = optarg; + input_choices++; break; case '2': mode |= SPI_TX_DUAL; @@ -316,6 +319,7 @@ static void parse_opts(int argc, char *argv[]) break; case 'S': transfer_size = atoi(optarg); + input_choices++; break; case 'I': iterations = atoi(optarg); @@ -446,8 +450,9 @@ int main(int argc, char *argv[]) parse_opts(argc, argv); - if (input_tx && input_file) - pabort("only one of -p and --input may be selected"); + if (input_choices > 1) + pabort("at most one of -S (--size), -p, -i (--input) may be selected, " + "and each may be specified only once"); fd = open(device, O_RDWR); if (fd < 0) From df79e49bd7d77121a0396a094676568de79b3f6c Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:43:53 +0200 Subject: [PATCH 351/724] spi: spidev_test: make size argument mandatory for --size When the argument to --size is omitted, abort with a message instead of transmitting default_tx[]. Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-3-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 0bd6ae419cbf..36292d868d69 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -41,9 +41,10 @@ static uint32_t speed = 500000; static uint16_t delay; static uint16_t word_delay; static int verbose; -static int transfer_size; +static int transfer_size = -1; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ +static int random_input; static int input_choices; static uint8_t default_tx[] = { @@ -319,6 +320,7 @@ static void parse_opts(int argc, char *argv[]) break; case 'S': transfer_size = atoi(optarg); + random_input = 1; input_choices++; break; case 'I': @@ -454,6 +456,9 @@ int main(int argc, char *argv[]) pabort("at most one of -S (--size), -p, -i (--input) may be selected, " "and each may be specified only once"); + if (random_input && transfer_size < 0) + pabort("a size argument is mandatory for -S (--size)"); + fd = open(device, O_RDWR); if (fd < 0) pabort("can't open device"); @@ -509,7 +514,7 @@ int main(int argc, char *argv[]) transfer_escaped_string(fd, input_tx); else if (input_file) transfer_file(fd, input_file); - else if (transfer_size) { + else if (transfer_size >= 0) { struct timespec last_stat; clock_gettime(CLOCK_MONOTONIC, &last_stat); From bd511c42d290807a04a325d584d65b98fd6721d7 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:43:54 +0200 Subject: [PATCH 352/724] spi: spidev_test: allow zero-length transfers SPI drivers may in fact handle zero-length transfers. The behavior so far was that such transfers would be executed but then result in the "can't send spi message" error message because the spi_sync() call returning 0 would be treated as an error while really it simply indicates that zero bytes were successfully transferred in a message. Don't treat a return value of zero as indication of an error. Update hex_dump() to work with empty buffers now printing e.g. TX | __ || Now including a trailing newline. Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-4-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 36292d868d69..908ed038889b 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -68,6 +68,10 @@ static void hex_dump(const void *src, size_t length, size_t line_size, unsigned char c; printf("%s | ", prefix); + + if (length == 0) + printf("__ ||\n"); + while (length-- > 0) { printf("%02X ", *address++); if (!(++i % line_size) || (length == 0 && i % line_size)) { @@ -149,7 +153,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) } ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); - if (ret < 1) + if (ret < 0) pabort("can't send spi message"); if (verbose) From 683d7fac9b0ff7aa8f0ff2ccfa0f4bbba99ee273 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:43:55 +0200 Subject: [PATCH 353/724] spi: spidev_test: abort when -I is selected without -S -I (--iter) for transferring multiple iterations of the selected input currently only has an effect when transferring random data using -S (--size). Error out if -I is used without -S to avoid confusion. Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-5-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 908ed038889b..97d09bf4dafb 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -463,6 +463,9 @@ int main(int argc, char *argv[]) if (random_input && transfer_size < 0) pabort("a size argument is mandatory for -S (--size)"); + if (iterations && transfer_size < 0) + pabort("-I (--iter) is only implemented for -S (--size)"); + fd = open(device, O_RDWR); if (fd < 0) pabort("can't open device"); From 2d736e05dbcd1294ed621fe5d4b6d06ef0c259be Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:43:56 +0200 Subject: [PATCH 354/724] spi: spidev_test: always compare loopback data In loopback mode, spidev_test verifies that the received data matches the sent data. However this test was only performed when random data was transferred using the -S switch. Always perform the test by moving it from transfer_buf() to transfer(). Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-6-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 97d09bf4dafb..e46eaef4d2ab 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -173,6 +173,15 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) if (verbose) hex_dump(rx, len, 32, "RX"); + + if (mode & SPI_LOOP) { + if (memcmp(tx, rx, len)) { + fprintf(stderr, "transfer error !\n"); + hex_dump(tx, len, 32, "TX"); + hex_dump(rx, len, 32, "RX"); + exit(1); + } + } } static void print_usage(const char *prog) @@ -435,15 +444,6 @@ static void transfer_buf(int fd, int len) _write_count += len; _read_count += len; - if (mode & SPI_LOOP) { - if (memcmp(tx, rx, len)) { - fprintf(stderr, "transfer error !\n"); - hex_dump(tx, len, 32, "TX"); - hex_dump(rx, len, 32, "RX"); - exit(1); - } - } - free(rx); free(tx); } From a47226866b8ae4c3a63802b668cdb99f4a76b151 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Thu, 24 Sep 2026 15:43:57 +0200 Subject: [PATCH 355/724] spi: spidev_test: add compare mode Check whether the received data correspond to the transferred data but without enabling loopback mode in the controller. This is for testing with TX physically bridged to RX which is useful if a loopback mode is unavailable or insufficient. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-7-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index e46eaef4d2ab..7b343803903e 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -44,6 +44,7 @@ static int verbose; static int transfer_size = -1; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ +static int compare; static int random_input; static int input_choices; @@ -174,7 +175,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) if (verbose) hex_dump(rx, len, 32, "RX"); - if (mode & SPI_LOOP) { + if (compare || mode & SPI_LOOP) { if (memcmp(tx, rx, len)) { fprintf(stderr, "transfer error !\n"); hex_dump(tx, len, 32, "TX"); @@ -186,13 +187,14 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) static void print_usage(const char *prog) { - printf("Usage: %s [-2348CDFHILMNORSZbdilopsvw]\n", prog); + printf("Usage: %s [-2348CDFHILMNORSZbcdilopsvw]\n", prog); puts("general device settings:\n" " -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" " -d --delay delay (usec)\n" " -w --word-delay word delay (usec)\n" " -l --loop loopback\n" + " -c --compare compare RX'ed and TX'ed data\n" "spi mode:\n" " -H --cpha clock phase\n" " -O --cpol clock polarity\n" @@ -230,6 +232,7 @@ static void parse_opts(int argc, char *argv[]) { "delay", 1, 0, 'd' }, { "word-delay", 1, 0, 'w' }, { "loop", 0, 0, 'l' }, + { "compare", 0, 0, 'c' }, { "cpha", 0, 0, 'H' }, { "cpol", 0, 0, 'O' }, { "rx-cpha-flip", 0, 0, 'F' }, @@ -253,7 +256,7 @@ static void parse_opts(int argc, char *argv[]) }; int c; - c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lHOLC3ZFMNR248p:vS:I:", + c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lcHOLC3ZFMNR248p:vS:I:", lopts, NULL); if (c == -1) @@ -285,6 +288,9 @@ static void parse_opts(int argc, char *argv[]) case 'l': mode |= SPI_LOOP; break; + case 'c': + compare = 1; + break; case 'H': mode |= SPI_CPHA; break; @@ -466,6 +472,9 @@ int main(int argc, char *argv[]) if (iterations && transfer_size < 0) pabort("-I (--iter) is only implemented for -S (--size)"); + if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) + pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)"); + fd = open(device, O_RDWR); if (fd < 0) pabort("can't open device"); From d2b908c75a8103efbcefe9c6efa290c58c8fe849 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Thu, 24 Sep 2026 15:43:58 +0200 Subject: [PATCH 356/724] spi: spidev_test: allow disabling rx or tx buffers Allow not providing rx or tx buffers. This is useful to check if drivers that don't use SPI_CONTROLLER_MUST_RX (or -TX respectively) handle their operations correctly without a buffer. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-8-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 84 ++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 7b343803903e..b02a6384d27b 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -45,6 +45,7 @@ static int transfer_size = -1; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ static int compare; +static int do_tx = 1, do_rx = 1; static int random_input; static int input_choices; @@ -157,7 +158,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) if (ret < 0) pabort("can't send spi message"); - if (verbose) + if (verbose && tx) hex_dump(tx, len, 32, "TX"); if (output_file) { @@ -172,10 +173,10 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) close(out_fd); } - if (verbose) + if (verbose && rx) hex_dump(rx, len, 32, "RX"); - if (compare || mode & SPI_LOOP) { + if (tx && rx && (compare || mode & SPI_LOOP)) { if (memcmp(tx, rx, len)) { fprintf(stderr, "transfer error !\n"); hex_dump(tx, len, 32, "TX"); @@ -187,7 +188,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) static void print_usage(const char *prog) { - printf("Usage: %s [-2348CDFHILMNORSZbcdilopsvw]\n", prog); + printf("Usage: %s [-2348CDFHILMNORSZbcdiloprstvw]\n", prog); puts("general device settings:\n" " -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" @@ -195,6 +196,8 @@ static void print_usage(const char *prog) " -w --word-delay word delay (usec)\n" " -l --loop loopback\n" " -c --compare compare RX'ed and TX'ed data\n" + " -t --no-tx don't send data\n" + " -r --no-rx don't receive data\n" "spi mode:\n" " -H --cpha clock phase\n" " -O --cpol clock polarity\n" @@ -233,6 +236,8 @@ static void parse_opts(int argc, char *argv[]) { "word-delay", 1, 0, 'w' }, { "loop", 0, 0, 'l' }, { "compare", 0, 0, 'c' }, + { "no-tx", 0, 0, 't' }, + { "no-rx", 0, 0, 'r' }, { "cpha", 0, 0, 'H' }, { "cpol", 0, 0, 'O' }, { "rx-cpha-flip", 0, 0, 'F' }, @@ -256,7 +261,7 @@ static void parse_opts(int argc, char *argv[]) }; int c; - c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lcHOLC3ZFMNR248p:vS:I:", + c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:vS:I:", lopts, NULL); if (c == -1) @@ -291,6 +296,12 @@ static void parse_opts(int argc, char *argv[]) case 'c': compare = 1; break; + case 't': + do_tx = 0; + break; + case 'r': + do_rx = 0; + break; case 'H': mode |= SPI_CPHA; break; @@ -363,15 +374,17 @@ static void transfer_escaped_string(int fd, char *str) { size_t size = strlen(str); uint8_t *tx; - uint8_t *rx; + uint8_t *rx = NULL; tx = malloc(size); if (!tx) pabort("can't allocate tx buffer"); - rx = malloc(size); - if (!rx) - pabort("can't allocate rx buffer"); + if (do_rx) { + rx = malloc(size); + if (!rx) + pabort("can't allocate rx buffer"); + } size = unescape((char *)tx, str, size); transfer(fd, tx, rx, size); @@ -385,7 +398,7 @@ static void transfer_file(int fd, char *filename) struct stat sb; int tx_fd; uint8_t *tx; - uint8_t *rx; + uint8_t *rx = NULL; if (stat(filename, &sb) == -1) pabort("can't stat input file"); @@ -398,9 +411,12 @@ static void transfer_file(int fd, char *filename) if (!tx) pabort("can't allocate tx buffer"); - rx = malloc(sb.st_size); - if (!rx) - pabort("can't allocate rx buffer"); + + if (do_rx) { + rx = malloc(sb.st_size); + if (!rx) + pabort("can't allocate rx buffer"); + } bytes = read(tx_fd, tx, sb.st_size); if (bytes != sb.st_size) @@ -431,24 +447,29 @@ static void show_transfer_rate(void) static void transfer_buf(int fd, int len) { - uint8_t *tx; - uint8_t *rx; + uint8_t *tx = NULL; + uint8_t *rx = NULL; int i; - tx = malloc(len); - if (!tx) - pabort("can't allocate tx buffer"); - for (i = 0; i < len; i++) - tx[i] = random(); + if (do_tx) { + tx = malloc(len); + if (!tx) + pabort("can't allocate tx buffer"); + for (i = 0; i < len; i++) + tx[i] = random(); + } - rx = malloc(len); - if (!rx) - pabort("can't allocate rx buffer"); + if (do_rx) { + rx = malloc(len); + if (!rx) + pabort("can't allocate rx buffer"); + } transfer(fd, tx, rx, len); - - _write_count += len; - _read_count += len; + if (do_tx) + _write_count += len; + if (do_rx) + _read_count += len; free(rx); free(tx); @@ -472,6 +493,15 @@ int main(int argc, char *argv[]) if (iterations && transfer_size < 0) pabort("-I (--iter) is only implemented for -S (--size)"); + if (compare && (!do_tx || !do_rx)) + pabort("-c (--compare) conflicts with -t (--no-tx) or -r (--no-rx)"); + + if (!do_rx && output_file) + pabort("-r (--no-rx) conflicts with -o (--output)"); + + if (!do_tx && transfer_size < 0) + pabort("-t (--no-tx) is only implemented for -S (--size)"); + if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)"); @@ -549,7 +579,7 @@ int main(int argc, char *argv[]) printf("total: tx %.1fKB, rx %.1fKB\n", _write_count/1024.0, _read_count/1024.0); } else - transfer(fd, default_tx, default_rx, sizeof(default_tx)); + transfer(fd, default_tx, do_rx ? default_rx : NULL, sizeof(default_tx)); close(fd); From 951a60af6c0baa36d6bed82cf3f7e8b754426ea7 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Thu, 24 Sep 2026 15:43:59 +0200 Subject: [PATCH 357/724] spi: spidev_test: don't send 0x0 or 0xff Particularly when using compare mode, if the controller fails to transfer any data, asserting on a read of 0x00 or 0xff may lead to a false negative test, indicating a byte was successfully transferred when the values simply originate from the pull-up or pull-down of RX. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-9-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index b02a6384d27b..7ca427c27f07 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -45,6 +45,7 @@ static int transfer_size = -1; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ static int compare; +static int nonzero; static int do_tx = 1, do_rx = 1; static int random_input; static int input_choices; @@ -188,7 +189,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) static void print_usage(const char *prog) { - printf("Usage: %s [-2348CDFHILMNORSZbcdiloprstvw]\n", prog); + printf("Usage: %s [-2348CDFHILMNORSZbcdiloprstvwz]\n", prog); puts("general device settings:\n" " -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" @@ -212,6 +213,7 @@ static void print_usage(const char *prog) " -i --input input data from a file (e.g. \"test.bin\")\n" " -o --output output data to a file (e.g. \"results.bin\")\n" " -p send data (e.g. \"1234\\xde\\xad\")\n" + " -z --nonzero don't send 0x00 or 0xff bytes\n" " -S --size transfer the given number of random bytes\n" " -I --iter iterations\n" "additional parameters:\n" @@ -249,6 +251,7 @@ static void parse_opts(int argc, char *argv[]) { "input", 1, 0, 'i' }, { "output", 1, 0, 'o' }, { "size", 1, 0, 'S' }, + { "nonzero", 0, 0, 'z' }, { "iter", 1, 0, 'I' }, { "bpw", 1, 0, 'b' }, { "lsb", 0, 0, 'L' }, @@ -261,7 +264,7 @@ static void parse_opts(int argc, char *argv[]) }; int c; - c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:vS:I:", + c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:vS:zI:", lopts, NULL); if (c == -1) @@ -353,6 +356,9 @@ static void parse_opts(int argc, char *argv[]) random_input = 1; input_choices++; break; + case 'z': + nonzero = 1; + break; case 'I': iterations = atoi(optarg); break; @@ -455,8 +461,11 @@ static void transfer_buf(int fd, int len) tx = malloc(len); if (!tx) pabort("can't allocate tx buffer"); - for (i = 0; i < len; i++) - tx[i] = random(); + for (i = 0; i < len; i++) { + do + tx[i] = random(); + while (nonzero && (tx[i] == 0x0 || tx[i] == 0xff)); + } } if (do_rx) { @@ -493,6 +502,9 @@ int main(int argc, char *argv[]) if (iterations && transfer_size < 0) pabort("-I (--iter) is only implemented for -S (--size)"); + if (nonzero && transfer_size < 0) + pabort("-z (--nonzero) is only implemented for -S (--size)"); + if (compare && (!do_tx || !do_rx)) pabort("-c (--compare) conflicts with -t (--no-tx) or -r (--no-rx)"); From c807c71d21eb77862fc497899720d28d5a149830 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:44:00 +0200 Subject: [PATCH 358/724] spi: spidev_test: send predictable data Introduce a flag to test on a predictable byte sequence instead of random bytes. This is useful when comparing multiple runs with the same option set e.g. with an oscilloscope. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-10-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 7ca427c27f07..a58680b6288a 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -48,6 +48,7 @@ static int compare; static int nonzero; static int do_tx = 1, do_rx = 1; static int random_input; +static int predictable; static int input_choices; static uint8_t default_tx[] = { @@ -189,7 +190,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) static void print_usage(const char *prog) { - printf("Usage: %s [-2348CDFHILMNORSZbcdiloprstvwz]\n", prog); + printf("Usage: %s [-2348CDFHILMNOPRSZbcdiloprstvwz]\n", prog); puts("general device settings:\n" " -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" @@ -214,6 +215,7 @@ static void print_usage(const char *prog) " -o --output output data to a file (e.g. \"results.bin\")\n" " -p send data (e.g. \"1234\\xde\\xad\")\n" " -z --nonzero don't send 0x00 or 0xff bytes\n" + " -P --predictable transfer the given number of sequential bytes\n" " -S --size transfer the given number of random bytes\n" " -I --iter iterations\n" "additional parameters:\n" @@ -259,12 +261,13 @@ static void parse_opts(int argc, char *argv[]) { "no-cs", 0, 0, 'N' }, { "ready", 0, 0, 'R' }, { "mosi-idle-low", 0, 0, 'M' }, + { "predictable", 1, 0, 'P' }, { "verbose", 0, 0, 'v' }, { NULL, 0, 0, 0 }, }; int c; - c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:vS:zI:", + c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:P:vS:zI:", lopts, NULL); if (c == -1) @@ -342,6 +345,11 @@ static void parse_opts(int argc, char *argv[]) input_tx = optarg; input_choices++; break; + case 'P': + transfer_size = atoi(optarg); + predictable = 1; + input_choices++; + break; case '2': mode |= SPI_TX_DUAL; break; @@ -462,9 +470,17 @@ static void transfer_buf(int fd, int len) if (!tx) pabort("can't allocate tx buffer"); for (i = 0; i < len; i++) { - do - tx[i] = random(); - while (nonzero && (tx[i] == 0x0 || tx[i] == 0xff)); + if (predictable) { + int v = i - iterations; + + if (nonzero) + v = ((v % 254) + 254) % 254 + 1; + tx[i] = (char)v; + } else { + do { + tx[i] = random(); + } while (nonzero && (tx[i] == 0x0 || tx[i] == 0xff)); + } } } @@ -493,17 +509,17 @@ int main(int argc, char *argv[]) parse_opts(argc, argv); if (input_choices > 1) - pabort("at most one of -S (--size), -p, -i (--input) may be selected, " + pabort("at most one of -S (--size), -p, -i (--input), -P (--predictable) may be selected, " "and each may be specified only once"); - if (random_input && transfer_size < 0) - pabort("a size argument is mandatory for -S (--size)"); + if ((random_input || predictable) && transfer_size < 0) + pabort("a size argument is mandatory for -S (--size) and -P (--predictable)"); if (iterations && transfer_size < 0) - pabort("-I (--iter) is only implemented for -S (--size)"); + pabort("-I (--iter) is only implemented for -S (--size) and -P (--predictable)"); if (nonzero && transfer_size < 0) - pabort("-z (--nonzero) is only implemented for -S (--size)"); + pabort("-z (--nonzero) is only implemented for -S (--size) and -P (--predictable)"); if (compare && (!do_tx || !do_rx)) pabort("-c (--compare) conflicts with -t (--no-tx) or -r (--no-rx)"); @@ -512,7 +528,7 @@ int main(int argc, char *argv[]) pabort("-r (--no-rx) conflicts with -o (--output)"); if (!do_tx && transfer_size < 0) - pabort("-t (--no-tx) is only implemented for -S (--size)"); + pabort("-t (--no-tx) is only implemented for -S (--size) and -P (--predictable)"); if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)"); From 31ce698070aa1e177f8e608d7fb92045fb01275c Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Thu, 24 Sep 2026 15:44:01 +0200 Subject: [PATCH 359/724] spi: spidev_test: add option to split message into multiple transfers SPI_IOC_MESSAGE() submits a single SPI message containing one or more transfers. Introduce an option to allow splitting the message into multiple transfers. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-11-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 111 +++++++++++++++++++++++++++++----------- 1 file changed, 81 insertions(+), 30 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index a58680b6288a..ac24d051835f 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -42,6 +42,7 @@ static uint16_t delay; static uint16_t word_delay; static int verbose; static int transfer_size = -1; +static int transfers = 1; static int iterations; static int interval = 5; /* interval in seconds for showing transfer rate */ static int compare; @@ -123,40 +124,79 @@ static int unescape(char *_dst, char *_src, size_t len) return ret; } -static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) +static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx, size_t len) { int ret; int out_fd; - struct spi_ioc_transfer tr = { - .tx_buf = (unsigned long)tx, - .rx_buf = (unsigned long)rx, - .len = len, - .delay_usecs = delay, - .word_delay_usecs = word_delay, - .speed_hz = speed, - .bits_per_word = bits, - }; + size_t bytes_per_word = DIV_ROUND_UP(bits, 8); + int effective_transfers; + struct spi_ioc_transfer *tr; + size_t len_per_transfer; + const uint8_t *tx_buf = tx; + const uint8_t *rx_buf = rx; + size_t rem; - if (mode & SPI_TX_OCTAL) - tr.tx_nbits = 8; - else if (mode & SPI_TX_QUAD) - tr.tx_nbits = 4; - else if (mode & SPI_TX_DUAL) - tr.tx_nbits = 2; - if (mode & SPI_RX_OCTAL) - tr.rx_nbits = 8; - else if (mode & SPI_RX_QUAD) - tr.rx_nbits = 4; - else if (mode & SPI_RX_DUAL) - tr.rx_nbits = 2; - if (!(mode & SPI_LOOP)) { - if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) - tr.rx_buf = 0; - else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL)) - tr.tx_buf = 0; + if (len) { + effective_transfers = min_t(size_t, transfers, DIV_ROUND_UP(len, bytes_per_word)); + len_per_transfer = roundup(DIV_ROUND_UP(len, effective_transfers), bytes_per_word); + } else { + /* zero-length transfers may be handled by the driver, deliver as ordered */ + effective_transfers = transfers; + len_per_transfer = 0; } - ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); + if (SPI_MSGSIZE(effective_transfers) == 0) + pabort("too many transfers, SPI_IOC_MESSAGE size limit exceeded"); + + tr = calloc(effective_transfers, sizeof(*tr)); + if (!tr) + pabort("can't allocate transfer array"); + + rem = len; + + for (int i = 0; i < effective_transfers; i++) { + size_t n = min(rem, len_per_transfer); + + tr[i] = (struct spi_ioc_transfer) { + .tx_buf = (unsigned long)tx_buf, + .rx_buf = (unsigned long)rx_buf, + .len = n, + .delay_usecs = delay, + .word_delay_usecs = word_delay, + .speed_hz = speed, + .bits_per_word = bits, + }; + + if (tx_buf) + tx_buf += n; + + if (rx_buf) + rx_buf += n; + + rem -= n; + + if (mode & SPI_TX_OCTAL) + tr[i].tx_nbits = 8; + else if (mode & SPI_TX_QUAD) + tr[i].tx_nbits = 4; + else if (mode & SPI_TX_DUAL) + tr[i].tx_nbits = 2; + if (mode & SPI_RX_OCTAL) + tr[i].rx_nbits = 8; + else if (mode & SPI_RX_QUAD) + tr[i].rx_nbits = 4; + else if (mode & SPI_RX_DUAL) + tr[i].rx_nbits = 2; + if (!(mode & SPI_LOOP)) { + if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) + tr[i].rx_buf = 0; + else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL)) + tr[i].tx_buf = 0; + } + } + + ret = ioctl(fd, SPI_IOC_MESSAGE(effective_transfers), tr); + free(tr); if (ret < 0) pabort("can't send spi message"); @@ -190,7 +230,7 @@ static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) static void print_usage(const char *prog) { - printf("Usage: %s [-2348CDFHILMNOPRSZbcdiloprstvwz]\n", prog); + printf("Usage: %s [-2348CDFHILMNOPRSTZbcdiloprstvwz]\n", prog); puts("general device settings:\n" " -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" @@ -225,6 +265,7 @@ static void print_usage(const char *prog) " -N --no-cs no chip select\n" " -R --ready slave pulls low to pause\n" " -M --mosi-idle-low leave mosi line low when idle\n" + " -T --transfers number of transfers\n" "misc:\n" " -v --verbose Verbose (show tx buffer)\n"); exit(1); @@ -262,12 +303,13 @@ static void parse_opts(int argc, char *argv[]) { "ready", 0, 0, 'R' }, { "mosi-idle-low", 0, 0, 'M' }, { "predictable", 1, 0, 'P' }, + { "transfers", 1, 0, 'T' }, { "verbose", 0, 0, 'v' }, { NULL, 0, 0, 0 }, }; int c; - c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:P:vS:zI:", + c = getopt_long(argc, argv, "D:s:d:w:b:i:o:lctrHOLC3ZFMNR248p:P:T:vS:zI:", lopts, NULL); if (c == -1) @@ -332,6 +374,9 @@ static void parse_opts(int argc, char *argv[]) case 'M': mode |= SPI_MOSI_IDLE_LOW; break; + case 'T': + transfers = atoi(optarg); + break; case 'N': mode |= SPI_NO_CS; break; @@ -533,6 +578,12 @@ int main(int argc, char *argv[]) if (compare && mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL)) pabort("-c (--compare) conflicts with -2 (--dual), -4 (--quad) or -8 (--octal)"); + if (transfers < 1) + pabort("-T (--transfers) must be 1 or above"); + + if (bits < 1) + pabort("-b (--bpw) must be 1 or above"); + fd = open(device, O_RDWR); if (fd < 0) pabort("can't open device"); From a73c71e1f25a4f7d6266a6c1cc5e3c903b52cc6a Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Thu, 24 Sep 2026 15:44:02 +0200 Subject: [PATCH 360/724] spi: spidev_test: print TX on error If sending an spi message fails, dump its first up to 256 bytes to the console. Signed-off-by: Marc Kleine-Budde Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-12-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index ac24d051835f..61951ed1c908 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -197,8 +197,18 @@ static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx, ret = ioctl(fd, SPI_IOC_MESSAGE(effective_transfers), tr); free(tr); - if (ret < 0) + if (ret < 0) { + const size_t dump_len = min_t(size_t, len, 256); + int saved_errno = errno; + + if (tx) { + hex_dump(tx, dump_len, 32, "TX"); + if (len > dump_len) + printf("... (%zu more bytes)\n", len - dump_len); + } + errno = saved_errno; pabort("can't send spi message"); + } if (verbose && tx) hex_dump(tx, len, 32, "TX"); From c79bbeadabc07e9a4c68ca16a3744a2264d6fbae Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Thu, 24 Sep 2026 15:44:03 +0200 Subject: [PATCH 361/724] spi: spidev_test: rewrite unescape() to stay in bounds unescape() could read the source buffer out of bounds for inputs like "\\x" and could write the destination buffer out of bounds due to lack of checking against the len parameter. For better readability and consistency, rewrite it as a simplified version of unescape_string() in string_helpers.h. This includes rearranging src and dst parameters. Signed-off-by: Jonas Rebmann Link: https://patch.msgid.link/20260924-spi-sun4i-spidev_test-v7-13-126e31a07fdc@pengutronix.de Signed-off-by: Mark Brown --- tools/spi/spidev_test.c | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/tools/spi/spidev_test.c b/tools/spi/spidev_test.c index 61951ed1c908..5bd3159f61a7 100644 --- a/tools/spi/spidev_test.c +++ b/tools/spi/spidev_test.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -100,28 +101,31 @@ static void hex_dump(const void *src, size_t length, size_t line_size, * Unescape - process hexadecimal escape character * converts shell input "\x23" -> 0x23 */ -static int unescape(char *_dst, char *_src, size_t len) +static int unescape(char *src, char *dst, size_t size) { - int ret = 0; - int match; - char *src = _src; - char *dst = _dst; + char *out = dst; unsigned int ch; - while (*src) { - if (*src == '\\' && *(src+1) == 'x') { - match = sscanf(src + 2, "%2x", &ch); - if (!match) - pabort("malformed input string"); + while (*src && size--) { + if (src[0] == '\\' && src[1] != '\0') { + src++; - src += 4; - *dst++ = (unsigned char)ch; - } else { - *dst++ = *src++; + if (src[0] == 'x' && + isxdigit((unsigned char)src[1]) && + isxdigit((unsigned char)src[2]) && + sscanf(&src[1], "%2x", &ch)) { + *out++ = (unsigned char)ch; + src += 3; + continue; + } + + *out++ = '\\'; + if (!size--) + break; } - ret++; + *out++ = *src++; } - return ret; + return out - dst; } static void transfer(int fd, uint8_t const * const tx, uint8_t const * const rx, size_t len) @@ -455,7 +459,7 @@ static void transfer_escaped_string(int fd, char *str) pabort("can't allocate rx buffer"); } - size = unescape((char *)tx, str, size); + size = unescape(str, (char *)tx, size); transfer(fd, tx, rx, size); free(rx); free(tx); From b4136c0d69ead737197af741e20edd1c3574f6b4 Mon Sep 17 00:00:00 2001 From: Slavin Liu Date: Sun, 13 Sep 2026 20:51:47 +0800 Subject: [PATCH 362/724] 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 363/724] 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 364/724] 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 365/724] 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 366/724] 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 367/724] 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 368/724] 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 369/724] 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 370/724] 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 371/724] 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 372/724] 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 373/724] 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 374/724] 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 375/724] 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 376/724] 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 377/724] 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 378/724] 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 379/724] 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 380/724] 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 381/724] 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 382/724] 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 383/724] 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 2a252105e03411e9a5fed78ad4a99e3b6dc98afd Mon Sep 17 00:00:00 2001 From: Ziyang Huang Date: Mon, 17 Aug 2026 23:15:02 +0800 Subject: [PATCH 384/724] spi: spi-qpic-snand: reject when no enough OOB space Print a clear error when the flash doesn't have enough space, instand of return -EBADMSG silently in read or write functions. ECC4 requires 64B OOB space. ECC8 requires 80B OOB space. Signed-off-by: Ziyang Huang Link: https://patch.msgid.link/SEYPR01MB5882463D860A9670617D5834C9A72@SEYPR01MB5882.apcprd01.prod.exchangelabs.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index aaa40753d274..9d626cee3d17 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -343,6 +343,15 @@ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand) ecc_cfg->cw_size = ecc_cfg->cw_data + ecc_cfg->bytes; bad_block_byte = mtd->writesize - ecc_cfg->cw_size * (cwperpage - 1) + 1; + if ((ecc_cfg->cw_size * cwperpage) > (mtd->writesize + mtd->oobsize)) { + dev_err(snandc->dev, + "Current ECC settings require %d bytes, but the flash only has %d+%d bytes.\n", + (ecc_cfg->cw_size * cwperpage), mtd->writesize, + mtd->oobsize); + ret = -EINVAL; + goto err_free_ecc_cfg; + } + mtd_set_ooblayout(mtd, &qcom_spi_ooblayout); /* From 5626653a4bdf32812d66b970860677cd9d7a2190 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 17 Sep 2026 19:11:36 +0700 Subject: [PATCH 385/724] 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 926f9c44a76fe9e722180d0804c607c6cf1ebbb4 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sat, 12 Sep 2026 15:00:56 -0400 Subject: [PATCH 386/724] spi: rockchip-sfc: skip DMA cleanup in PIO mode With rockchip,sfc-no-dma, probe skips allocating and mapping the transfer buffer. However, controller registration failure and driver removal still call dma_unmap_single() for that nonexistent mapping. Guard the DMA mapping and buffer cleanup with use_dma. Keep the existing cleanup for allocation and mapping failures on the DMA path. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: ee795e82e101 ("spi: rockchip-sfc: Fix DMA-API usage") Assisted-by: OpenAI:GPT-5.6 Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260912190056.42192-1-mhun512@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-rockchip-sfc.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-rockchip-sfc.c b/drivers/spi/spi-rockchip-sfc.c index 43e91b49bb1d..a85a260ba95f 100644 --- a/drivers/spi/spi-rockchip-sfc.c +++ b/drivers/spi/spi-rockchip-sfc.c @@ -719,10 +719,12 @@ static int rockchip_sfc_probe(struct platform_device *pdev) return 0; err_register: - dma_unmap_single(dev, sfc->dma_buffer, sfc->max_iosize, - DMA_BIDIRECTIONAL); + if (sfc->use_dma) + dma_unmap_single(dev, sfc->dma_buffer, sfc->max_iosize, + DMA_BIDIRECTIONAL); err_dma_map: - free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); + if (sfc->use_dma) + free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); err_dma: pm_runtime_get_sync(dev); pm_runtime_put_noidle(dev); @@ -748,9 +750,11 @@ static void rockchip_sfc_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); - dma_unmap_single(&pdev->dev, sfc->dma_buffer, sfc->max_iosize, - DMA_BIDIRECTIONAL); - free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); + if (sfc->use_dma) { + dma_unmap_single(&pdev->dev, sfc->dma_buffer, sfc->max_iosize, + DMA_BIDIRECTIONAL); + free_pages((unsigned long)sfc->buffer, get_order(sfc->max_iosize)); + } clk_disable_unprepare(sfc->clk); clk_disable_unprepare(sfc->hclk); From 286ece639c3753ff929b219452391134e611823a Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 16:34:12 +0800 Subject: [PATCH 387/724] 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 388/724] 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 389/724] 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 390/724] 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 391/724] 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 392/724] 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 393/724] 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 394/724] 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 395/724] 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 396/724] 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 397/724] 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 398/724] 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 399/724] 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 400/724] 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 401/724] 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 402/724] 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 403/724] 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 3e71ea6dca1d585692b645aaf3b8cc58f6e5eb35 Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Mon, 28 Sep 2026 04:45:13 +0000 Subject: [PATCH 404/724] 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 405/724] 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 406/724] 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 7f7400bd60c3ccaf823bb7a245da67d27e2face5 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Sun, 27 Sep 2026 16:11:22 +0200 Subject: [PATCH 407/724] spi: spi-qpic-snand: drop ecceng_to_qspi() macro The ecceng_to_qspi() macro is only used within the nand_to_qcom_snand() function. Drop the macro and use the respective container_of() macro call directly in the function instead to simplify the code. No functional changes. Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260927-qpic-snand-drop-ecceng_to_qspi-v1-1-993e9ec1c2d4@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 9d626cee3d17..40ec6a3769bc 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -63,7 +63,6 @@ #define ACC_FEATURE 0xe #define BAD_BLOCK_MARKER_SIZE 0x2 #define OOB_BUF_SIZE 128 -#define ecceng_to_qspi(eng) container_of(eng, struct qpic_spi_nand, ecc_eng) struct snandc_read_status { __le32 snandc_flash; @@ -168,8 +167,9 @@ static void qcom_spi_set_read_loc_last(struct qcom_nand_controller *snandc, static struct qcom_nand_controller *nand_to_qcom_snand(struct nand_device *nand) { struct nand_ecc_engine *eng = nand->ecc.engine; - struct qpic_spi_nand *qspi = ecceng_to_qspi(eng); + struct qpic_spi_nand *qspi; + qspi = container_of(eng, struct qpic_spi_nand, ecc_eng); return qspi->snandc; } From 37e117c23563d6428a97395c23aa9af196cdbda4 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:41:15 +0000 Subject: [PATCH 408/724] 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 409/724] 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 410/724] 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 411/724] 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 412/724] 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 413/724] 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 414/724] 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 415/724] 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 416/724] 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 082723fee626532f176727b6438b666a96d5246c Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 16:48:53 +0000 Subject: [PATCH 417/724] 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 7103a8c7b2ce196aded3bdfd8c4cf5afef4a90ff Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Sun, 27 Sep 2026 09:36:23 +0800 Subject: [PATCH 418/724] 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 2ec5d38037b849340e6312ada3d54b522b648c9b Mon Sep 17 00:00:00 2001 From: Xianwei Zhao Date: Thu, 24 Sep 2026 05:51:08 +0000 Subject: [PATCH 419/724] spi: dt-bindings: amlogic,meson-gx-spicc: add T7 compatible Add the Amlogic T7 SPICC compatible to the device tree binding. The T7 controller is compatible with the existing G12A SPICC implementation, so require amlogic,meson-g12a-spicc as a fallback compatible for T7 devices. Signed-off-by: Xianwei Zhao Link: https://patch.msgid.link/20260924-t7-node-new-v2-1-319d3c19b4b2@amlogic.com Signed-off-by: Mark Brown --- .../bindings/spi/amlogic,meson-gx-spicc.yaml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml b/Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml index 4e28e6e9d8e0..f0ba15402a4c 100644 --- a/Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml +++ b/Documentation/devicetree/bindings/spi/amlogic,meson-gx-spicc.yaml @@ -16,10 +16,15 @@ description: | properties: compatible: - enum: - - amlogic,meson-gx-spicc # SPICC controller on Amlogic GX and compatible SoCs - - amlogic,meson-axg-spicc # SPICC controller on Amlogic AXG and compatible SoCs - - amlogic,meson-g12a-spicc # SPICC controller on Amlogic G12A and compatible SoCs + oneOf: + - items: + - enum: + - amlogic,t7-spicc + - const: amlogic,meson-g12a-spicc + - enum: + - amlogic,meson-gx-spicc # SPICC controller on Amlogic GX and compatible SoCs + - amlogic,meson-axg-spicc # SPICC controller on Amlogic AXG and compatible SoCs + - amlogic,meson-g12a-spicc # SPICC controller on Amlogic G12A and compatible SoCs interrupts: maxItems: 1 From 7b85114d47f693b709b2e8e22e44cdf58c04b111 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 21 Sep 2026 19:59:43 +0000 Subject: [PATCH 420/724] spi: rspi: Fix DMA mode when the DMA controller is built as a module On the Renesas RZ/G2L platform, the RSPI driver falls back to PIO mode instead of using DMA when the DMA controller driver is built as a module (CONFIG_RZ_DMAC=m): renesas_spi 1004b000.spi: dma_request_slave_channel_compat failed renesas_spi 1004b000.spi: DMA not available, using PIO The driver requests DMA channels through dma_request_slave_channel_compat() which returns either a valid pointer or NULL and cannot handle -EPROBE_DEFER. If the DMA controller has not probed yet, the driver falls back to PIO directly. Fix this by replacing the dma_request_slave_channel_compat() call with a regular dma_request_chan() call. dma_request_slave_channel_compat() is always passed a NULL fn_param in all code paths, so it actually behaves like a plain dma_request_chan() call. Fixes: e825b8dd2b36 ("spi: rspi: Add DT support to DMA setup") Signed-off-by: Ovidiu Panait Link: https://patch.msgid.link/20260921195943.107247-1-ovidiu.panait.rb@renesas.com Signed-off-by: Mark Brown --- drivers/spi/spi-rspi.c | 49 ++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/drivers/spi/spi-rspi.c b/drivers/spi/spi-rspi.c index 38df676774ee..c61edd1c1353 100644 --- a/drivers/spi/spi-rspi.c +++ b/drivers/spi/spi-rspi.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -1091,24 +1090,15 @@ static irqreturn_t rspi_irq_tx(int irq, void *_sr) static struct dma_chan *rspi_request_dma_chan(struct device *dev, enum dma_transfer_direction dir, - unsigned int id, dma_addr_t port_addr) { - dma_cap_mask_t mask; struct dma_chan *chan; struct dma_slave_config cfg; int ret; - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - - chan = dma_request_slave_channel_compat(mask, shdma_chan_filter, - (void *)(unsigned long)id, dev, - dir == DMA_MEM_TO_DEV ? "tx" : "rx"); - if (!chan) { - dev_warn(dev, "dma_request_slave_channel_compat failed\n"); - return NULL; - } + chan = dma_request_chan(dev, dir == DMA_MEM_TO_DEV ? "tx" : "rx"); + if (IS_ERR(chan)) + return chan; memset(&cfg, 0, sizeof(cfg)); cfg.dst_addr = port_addr + RSPI_SPDR; @@ -1121,7 +1111,7 @@ static struct dma_chan *rspi_request_dma_chan(struct device *dev, if (ret) { dev_warn(dev, "dmaengine_slave_config failed %d\n", ret); dma_release_channel(chan); - return NULL; + return ERR_PTR(ret); } return chan; @@ -1130,29 +1120,23 @@ static struct dma_chan *rspi_request_dma_chan(struct device *dev, static int rspi_request_dma(struct device *dev, struct spi_controller *ctlr, const struct resource *res) { - unsigned int dma_tx_id, dma_rx_id; + struct dma_chan *chan; - if (dev->of_node) { - /* In the OF case we will get the slave IDs from the DT */ - dma_tx_id = 0; - dma_rx_id = 0; - } else { - /* The driver assumes no error. */ + if (!dev->of_node) return 0; - } - ctlr->dma_tx = rspi_request_dma_chan(dev, DMA_MEM_TO_DEV, dma_tx_id, - res->start); - if (!ctlr->dma_tx) - return -ENODEV; + chan = rspi_request_dma_chan(dev, DMA_MEM_TO_DEV, res->start); + if (IS_ERR(chan)) + return PTR_ERR(chan); + ctlr->dma_tx = chan; - ctlr->dma_rx = rspi_request_dma_chan(dev, DMA_DEV_TO_MEM, dma_rx_id, - res->start); - if (!ctlr->dma_rx) { + chan = rspi_request_dma_chan(dev, DMA_DEV_TO_MEM, res->start); + if (IS_ERR(chan)) { dma_release_channel(ctlr->dma_tx); ctlr->dma_tx = NULL; - return -ENODEV; + return PTR_ERR(chan); } + ctlr->dma_rx = chan; ctlr->can_dma = rspi_can_dma; dev_info(dev, "DMA available"); @@ -1355,6 +1339,11 @@ static int rspi_probe(struct platform_device *pdev) } ret = rspi_request_dma(&pdev->dev, ctlr, res); + if (ret == -EPROBE_DEFER) { + dev_err_probe(&pdev->dev, ret, + "failed to request DMA channels\n"); + goto error2; + } if (ret < 0) dev_warn(&pdev->dev, "DMA not available, using PIO\n"); From e8c263e0ef957fd8286e299b2be9266b902cec16 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:38:57 +0700 Subject: [PATCH 421/724] 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 422/724] 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 423/724] 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 424/724] 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 425/724] 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 426/724] 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 427/724] 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 428/724] 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 429/724] 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 430/724] 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 431/724] 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 432/724] 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 433/724] 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 434/724] 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 435/724] 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 8db44cca03df066186d6220bfad9fa482d7cdc13 Mon Sep 17 00:00:00 2001 From: Lucas Costa <2000.costalucas@gmail.com> Date: Fri, 25 Sep 2026 16:30:08 -0300 Subject: [PATCH 436/724] spi: bcm2835: fix device_node reference leak in bcm2835_spi_setup() of_find_compatible_node() returns a device_node with its reference count incremented, but bcm2835_spi_setup() only uses the return value to test for a matching node in the loop and never releases it. Declare the node with a scope-based __free(device_node) cleanup so the reference is dropped automatically when leaving the loop body, matching the existing __free(kfree) usage in the same function. Fixes: e19c1272c80a ("spi: bcm2835: Restore native CS probing when pinctrl-bcm2835 is absent") Cc: stable@vger.kernel.org Signed-off-by: Lucas Costa <2000.costalucas@gmail.com> Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260925193008.127314-1-2000.costalucas@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm2835.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c index 8f8715809c7c..23606967f7d1 100644 --- a/drivers/spi/spi-bcm2835.c +++ b/drivers/spi/spi-bcm2835.c @@ -1297,7 +1297,9 @@ static int bcm2835_spi_setup(struct spi_device *spi) } for (i = 0; i < ARRAY_SIZE(pinctrl_compats); i++) { - if (of_find_compatible_node(NULL, NULL, pinctrl_compats[i])) + struct device_node *np __free(device_node) = + of_find_compatible_node(NULL, NULL, pinctrl_compats[i]); + if (np) break; } From f3c0a2b557ea87cbd802b278783c32484a063ded Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Tue, 22 Sep 2026 19:50:57 +0200 Subject: [PATCH 437/724] spi: spi-qpic-snand: fix read location register usage in qcom_spi_config_cw_read() While configuring a codeword read, the com_spi_config_cw_read() function unconditionally schedules writing of the regular read location registers. However these registers should be written only if the actual codeword is not the last one. Change the function to schedule writing of the registers corresponding to the acual codeword only. While at it, also drop the single-use 'reg' and 'num_cw' local variables and use the respective values directly in order to make the code easier to understand. Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface") Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260922-qpic-snand-fix-cw_read-readloc-v1-1-67f6819fb3d7@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 40ec6a3769bc..edfac4866a96 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -524,15 +524,14 @@ static void qcom_spi_config_page_read(struct qcom_nand_controller *snandc) static void qcom_spi_config_cw_read(struct qcom_nand_controller *snandc, bool use_ecc, int cw) { - __le32 *reg = &snandc->regs->read_location0; - int num_cw = snandc->qspi->num_cw; - - qcom_write_reg_dma(snandc, reg, NAND_READ_LOCATION_0, 4, NAND_BAM_NEXT_SGL); - if (cw == (num_cw - 1)) { - reg = &snandc->regs->read_location_last0; - qcom_write_reg_dma(snandc, reg, NAND_READ_LOCATION_LAST_CW_0, 4, + if (cw == (snandc->qspi->num_cw - 1)) + qcom_write_reg_dma(snandc, &snandc->regs->read_location_last0, + NAND_READ_LOCATION_LAST_CW_0, 4, + NAND_BAM_NEXT_SGL); + else + qcom_write_reg_dma(snandc, &snandc->regs->read_location0, + NAND_READ_LOCATION_0, 4, NAND_BAM_NEXT_SGL); - } qcom_write_reg_dma(snandc, &snandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL); qcom_write_reg_dma(snandc, &snandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); From 7845324094fcf88060170b44661519e57b179b61 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Mon, 28 Sep 2026 14:11:45 +0800 Subject: [PATCH 438/724] 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 439/724] 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 440/724] 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 441/724] 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 442/724] 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 cecb86a37ac0bdd69edc2b70c160b436ec6bea16 Mon Sep 17 00:00:00 2001 From: Udaya Kiran Challa Date: Tue, 25 Aug 2026 21:52:57 +0530 Subject: [PATCH 443/724] spi: dt-bindings: microchip,pic32mzda-spi: Convert to DT schema Convert Microchip PIC32 SPI controller devicetree binding from legacy text format to DT schema. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Udaya Kiran Challa Link: https://patch.msgid.link/20260825162257.77042-1-challauday369@gmail.com Signed-off-by: Mark Brown --- .../bindings/spi/microchip,pic32mzda-spi.yaml | 82 +++++++++++++++++++ .../bindings/spi/microchip,spi-pic32.txt | 34 -------- 2 files changed, 82 insertions(+), 34 deletions(-) create mode 100644 Documentation/devicetree/bindings/spi/microchip,pic32mzda-spi.yaml delete mode 100644 Documentation/devicetree/bindings/spi/microchip,spi-pic32.txt diff --git a/Documentation/devicetree/bindings/spi/microchip,pic32mzda-spi.yaml b/Documentation/devicetree/bindings/spi/microchip,pic32mzda-spi.yaml new file mode 100644 index 000000000000..4669b521fa1f --- /dev/null +++ b/Documentation/devicetree/bindings/spi/microchip,pic32mzda-spi.yaml @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/microchip,pic32mzda-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip PIC32MZDA SPI Controller + +maintainers: + - Thomas Bogendoerfer + +allOf: + - $ref: spi-controller.yaml# + +properties: + compatible: + const: microchip,pic32mzda-spi + + reg: + maxItems: 1 + + interrupts: + items: + - description: Fault interrupt + - description: Receive interrupt + - description: Transmit interrupt + + interrupt-names: + items: + - const: fault + - const: rx + - const: tx + + clocks: + maxItems: 1 + + clock-names: + items: + - const: mck0 + + cs-gpios: + maxItems: 1 + + dmas: + items: + - description: RX DMA channel + - description: TX DMA channel + + dma-names: + items: + - const: spi-rx + - const: spi-tx + +required: + - compatible + - reg + - interrupts + - interrupt-names + - clocks + - clock-names + - cs-gpios + +unevaluatedProperties: false + +examples: + - | + #include + #include + + spi@1f821000 { + compatible = "microchip,pic32mzda-spi"; + reg = <0x1f821000 0x200>; + interrupts = <109 IRQ_TYPE_LEVEL_HIGH>, + <110 IRQ_TYPE_LEVEL_HIGH>, + <111 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "fault", "rx", "tx"; + clocks = <&PBCLK2>; + clock-names = "mck0"; + cs-gpios = <&gpio3 4 GPIO_ACTIVE_LOW>; + dmas = <&dma 134>, <&dma 135>; + dma-names = "spi-rx", "spi-tx"; + }; diff --git a/Documentation/devicetree/bindings/spi/microchip,spi-pic32.txt b/Documentation/devicetree/bindings/spi/microchip,spi-pic32.txt deleted file mode 100644 index 79de379f4dc0..000000000000 --- a/Documentation/devicetree/bindings/spi/microchip,spi-pic32.txt +++ /dev/null @@ -1,34 +0,0 @@ -Microchip PIC32 SPI Master controller - -Required properties: -- compatible: Should be "microchip,pic32mzda-spi". -- reg: Address and length of register space for the device. -- interrupts: Should contain all three spi interrupts in sequence - of , , . -- interrupt-names: Should be "fault", "rx", "tx" in order. -- clocks: Phandle of the clock generating SPI clock on the bus. -- clock-names: Should be "mck0". -- cs-gpios: Specifies the gpio pins to be used for chipselects. - See: Documentation/devicetree/bindings/spi/spi-bus.txt - -Optional properties: -- dmas: Two or more DMA channel specifiers following the convention outlined - in Documentation/devicetree/bindings/dma/dma.txt -- dma-names: Names for the dma channels. There must be at least one channel - named "spi-tx" for transmit and named "spi-rx" for receive. - -Example: - -spi1: spi@1f821000 { - compatible = "microchip,pic32mzda-spi"; - reg = <0x1f821000 0x200>; - interrupts = <109 IRQ_TYPE_LEVEL_HIGH>, - <110 IRQ_TYPE_LEVEL_HIGH>, - <111 IRQ_TYPE_LEVEL_HIGH>; - interrupt-names = "fault", "rx", "tx"; - clocks = <&PBCLK2>; - clock-names = "mck0"; - cs-gpios = <&gpio3 4 GPIO_ACTIVE_LOW>; - dmas = <&dma 134>, <&dma 135>; - dma-names = "spi-rx", "spi-tx"; -}; From 1f07c6fecb38c2839bc71bb4f8b6637684fdd33e Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Tue, 29 Sep 2026 13:04:00 +0200 Subject: [PATCH 444/724] spi: spi-qpic-snand: remove 'ecc_buf_cfg' member of struct 'qpic_ecc' The 'ecc_buf_cfg' member of struct 'qpic_ecc' is used to store a configuration value for the QPIC_EBI2_ECC_BUF_CFG register. However that register is obsolete on the QPIC v2 controller the driver is written for and programming of the register has been removed in commit 930a7312c946 ("spi: spi-qpic-snand: avoid writing QPIC_EBI2_ECC_BUF_CFG register"). Since thaen, the value stored in 'ecc_buf_cfg' has no real use, so drop the member from the structure. Also remove the remaining code which uses that. Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260929-qpic-snand-drop-ecc_buf_cfg-v1-1-c9de93142815@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index edfac4866a96..00072c44db7a 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -97,7 +97,6 @@ struct qpic_ecc { u32 cfg1; u32 cfg0_raw; u32 cfg1_raw; - u32 ecc_buf_cfg; u32 ecc_bch_cfg; bool bch_enabled; }; @@ -407,8 +406,6 @@ static int qcom_spi_ecc_init_ctx_pipelined(struct nand_device *nand) FIELD_PREP(ECC_MODE_MASK, ecc_cfg->ecc_mode) | FIELD_PREP(ECC_PARITY_SIZE_BYTES_BCH_MASK, ecc_cfg->ecc_bytes_hw); - ecc_cfg->ecc_buf_cfg = FIELD_PREP(NUM_STEPS_MASK, 0x203); - conf->step_size = ecc_cfg->step_size; conf->strength = ecc_cfg->strength; @@ -1198,13 +1195,12 @@ static int qcom_spi_program_ecc(struct qcom_nand_controller *snandc, u8 *data_buf = NULL, *oob_buf = NULL; int i, ret; int num_cw = snandc->qspi->num_cw; - u32 cfg0, cfg1, ecc_bch_cfg, ecc_buf_cfg; + u32 cfg0, cfg1, ecc_bch_cfg; cfg0 = (ecc_cfg->cfg0 & ~CW_PER_PAGE_MASK) | FIELD_PREP(CW_PER_PAGE_MASK, num_cw - 1); cfg1 = ecc_cfg->cfg1; ecc_bch_cfg = ecc_cfg->ecc_bch_cfg; - ecc_buf_cfg = ecc_cfg->ecc_buf_cfg; if (snandc->qspi->data_buf) data_buf = snandc->qspi->data_buf; @@ -1222,7 +1218,6 @@ static int qcom_spi_program_ecc(struct qcom_nand_controller *snandc, snandc->regs->cfg0 = cpu_to_le32(cfg0); snandc->regs->cfg1 = cpu_to_le32(cfg1); snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg); - snandc->regs->ecc_buf_cfg = cpu_to_le32(ecc_buf_cfg); snandc->regs->exec = cpu_to_le32(1); qcom_spi_config_page_write(snandc); @@ -1275,13 +1270,12 @@ static int qcom_spi_program_oob(struct qcom_nand_controller *snandc, u8 *oob_buf = NULL; int ret, col, data_size, oob_size; int num_cw = snandc->qspi->num_cw; - u32 cfg0, cfg1, ecc_bch_cfg, ecc_buf_cfg; + u32 cfg0, cfg1, ecc_bch_cfg; cfg0 = (ecc_cfg->cfg0 & ~CW_PER_PAGE_MASK) | FIELD_PREP(CW_PER_PAGE_MASK, 0); cfg1 = ecc_cfg->cfg1; ecc_bch_cfg = ecc_cfg->ecc_bch_cfg; - ecc_buf_cfg = ecc_cfg->ecc_buf_cfg; col = ecc_cfg->cw_size * (num_cw - 1); @@ -1297,7 +1291,6 @@ static int qcom_spi_program_oob(struct qcom_nand_controller *snandc, snandc->regs->cfg0 = cpu_to_le32(cfg0); snandc->regs->cfg1 = cpu_to_le32(cfg1); snandc->regs->ecc_bch_cfg = cpu_to_le32(ecc_bch_cfg); - snandc->regs->ecc_buf_cfg = cpu_to_le32(ecc_buf_cfg); snandc->regs->exec = cpu_to_le32(1); /* calculate the data and oob size for the last codeword/step */ From f38638f5181ab0545ca36f2ac3b0d2cf3404b1b7 Mon Sep 17 00:00:00 2001 From: Alexander Koch Date: Tue, 29 Sep 2026 11:08:20 +0200 Subject: [PATCH 445/724] 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 446/724] 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 447/724] 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 448/724] 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 449/724] 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 450/724] 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 451/724] 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 452/724] 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 453/724] 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 0bebef817d4816a95a5d13a00210908f96596552 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Tue, 15 Sep 2026 12:47:35 +0800 Subject: [PATCH 454/724] 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 455/724] 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 69338914b1d98d2c0dcd6e9617c56907a1b3e2db Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 29 Sep 2026 15:06:20 -0400 Subject: [PATCH 456/724] spi: ingenic: remove dmaengine_desc_free() dmaengine_desc_free() is designed for DMA_CTRL_REUSE. If DMA_CTRL_REUSE is not set, dmaengine_desc_free() will return -EPERM. if (!dmaengine_desc_test_reuse(desc)) return -EPERM; So calling dmaengine_desc_free() does nothing. dmaengine_terminate_(a)sync() will free all already allocated descriptors. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260929190621.3516424-1-Frank.Li@oss.nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-ingenic.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/spi/spi-ingenic.c b/drivers/spi/spi-ingenic.c index 7ce4fe965cac..a0a047510611 100644 --- a/drivers/spi/spi-ingenic.c +++ b/drivers/spi/spi-ingenic.c @@ -164,10 +164,8 @@ spi_ingenic_prepare_dma(struct spi_controller *ctlr, struct dma_chan *chan, cookie = dmaengine_submit(desc); ret = dma_submit_error(cookie); - if (ret) { - dmaengine_desc_free(desc); + if (ret) return ERR_PTR(ret); - } return desc; } @@ -186,7 +184,6 @@ static int spi_ingenic_dma_tx(struct spi_controller *ctlr, &xfer->tx_sg, DMA_MEM_TO_DEV, bits); if (IS_ERR(tx_desc)) { dmaengine_terminate_async(ctlr->dma_rx); - dmaengine_desc_free(rx_desc); return PTR_ERR(tx_desc); } From cf0f217a52d04af0be2e695475b8b4b392ae508c Mon Sep 17 00:00:00 2001 From: Jyan Chou Date: Wed, 30 Sep 2026 14:59:44 +0800 Subject: [PATCH 457/724] spi: dt-bindings: Add Realtek RTD1625 SPI NOR support Add DT binding schema for Realtek RTD1625 SPI NOR Flash Controller Signed-off-by: Jyan Chou Co-developed-by: Yu-Chun Lin Signed-off-by: Yu-Chun Lin Acked-by: Conor Dooley Link: https://patch.msgid.link/20260930065945.88008-2-eleanor.lin@realtek.com Signed-off-by: Mark Brown --- .../bindings/spi/realtek,rtd1625-nor.yaml | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/realtek,rtd1625-nor.yaml diff --git a/Documentation/devicetree/bindings/spi/realtek,rtd1625-nor.yaml b/Documentation/devicetree/bindings/spi/realtek,rtd1625-nor.yaml new file mode 100644 index 000000000000..014ddfe0f421 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/realtek,rtd1625-nor.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/realtek,rtd1625-nor.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek RTD1625 SPI NOR Flash Controller + +maintainers: + - Jyan Chou + - Yu-Chun Lin + +properties: + compatible: + enum: + - realtek,rtd1625-nor + + reg: + maxItems: 3 + + reg-names: + items: + - const: ctrl + - const: dma + - const: dirmap + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +required: + - compatible + - reg + - reg-names + - clocks + - resets + - "#address-cells" + - "#size-cells" + +allOf: + - $ref: /schemas/spi/spi-controller.yaml# + +unevaluatedProperties: false + +examples: + - | + spi@14b000 { + compatible = "realtek,rtd1625-nor"; + reg = <0x14b000 0x50>, <0x14bf00 0x30>, <0x88100000 0x2000000>; + reg-names = "ctrl", "dma", "dirmap"; + clocks = <&cc 18>; + resets = <&cc 26>; + pinctrl-0 = <&spi_pins>; + pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <0>; + }; From aca8cbecdd2e9fcc54540228be78197913bbea7d Mon Sep 17 00:00:00 2001 From: Jyan Chou Date: Wed, 30 Sep 2026 14:59:45 +0800 Subject: [PATCH 458/724] spi: spi-mem: Add Realtek SPI NOR flash controller driver Add a spi-mem driver for the SPI NOR Flash Controller (SFC) found on Realtek DHC SoC. SFC supports Dual I/O. Implement the exec_op API for regular control commands and the dirmap API for hardware-accelerated read/write operations. The controller is described by three register ranges. The first one (ctrl) holds the control registers. The second one (dma) holds the DMA engine registers, used to transfer the payload between the flash and main memory. The third range is the memory aperture (dirmap) that maps the flash contents. Signed-off-by: Jyan Chou Co-developed-by: Yu-Chun Lin Signed-off-by: Yu-Chun Lin Link: https://patch.msgid.link/20260930065945.88008-3-eleanor.lin@realtek.com Signed-off-by: Mark Brown --- MAINTAINERS | 6 + drivers/spi/Kconfig | 10 + drivers/spi/Makefile | 1 + drivers/spi/spi-rtk-nor.c | 624 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 641 insertions(+) create mode 100644 drivers/spi/spi-rtk-nor.c diff --git a/MAINTAINERS b/MAINTAINERS index 360977678f70..a9cec9cfdf1e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23023,6 +23023,12 @@ S: Maintained F: Documentation/devicetree/bindings/spi/realtek,rtl9301-snand.yaml F: drivers/spi/spi-realtek-rtl-snand.c +REALTEK SPI-NOR +M: Jyan Chou +M: Yu-Chun Lin +S: Supported +F: drivers/spi/spi-rtk-nor.c + REALTEK SYSTIMER DRIVER M: Hao-Wen Ting S: Maintained diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index a0f37e2df716..99ed19a99d75 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -967,6 +967,16 @@ config SPI_RSPI help SPI driver for Renesas RSPI and QSPI blocks. +config SPI_RTK_NOR + tristate "Realtek SPI-NOR flash controller" + depends on ARCH_REALTEK || COMPILE_TEST + depends on SPI_MEM + help + This enables support for the SPI NOR Flash Controller found on + Realtek DHC SoCs. The controller supports single and dual I/O + transfers and offloads the payload to an integrated DMA engine. + Say Y or M here if you are building a kernel for a Realtek DHC SoC + config SPI_RZV2H_RSPI tristate "Renesas RZ/V2H RSPI controller" depends on ARCH_RENESAS || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index a18814fbde93..37c6eec9b541 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -133,6 +133,7 @@ obj-$(CONFIG_MACH_REALTEK_RTL) += spi-realtek-rtl.o obj-$(CONFIG_SPI_REALTEK_SNAND) += spi-realtek-rtl-snand.o obj-$(CONFIG_SPI_RPCIF) += spi-rpc-if.o obj-$(CONFIG_SPI_RSPI) += spi-rspi.o +obj-$(CONFIG_SPI_RTK_NOR) += spi-rtk-nor.o obj-$(CONFIG_SPI_RZV2H_RSPI) += spi-rzv2h-rspi.o obj-$(CONFIG_SPI_RZV2M_CSI) += spi-rzv2m-csi.o obj-$(CONFIG_SPI_S3C64XX) += spi-s3c64xx.o diff --git a/drivers/spi/spi-rtk-nor.c b/drivers/spi/spi-rtk-nor.c new file mode 100644 index 000000000000..7f56346506b3 --- /dev/null +++ b/drivers/spi/spi-rtk-nor.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Realtek SPI Nor Flash Controller Driver (SFC) +// +// Copyright (c) 2024-2026 Realtek Semiconductor Corp. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SFC_OPCODE 0x00 +#define DUAL_MODE_EN BIT(9) + +#define SFC_CTL 0x04 +#define RW_DATAEN BIT(4) +#define ADDR_EN BIT(3) + +#define SFC_SCK 0x08 +#define FDIV_MASK GENMASK(7, 0) + +#define SFC_CE 0x0c +#define DESLT_TIME 0x1a +#define DESLT_TIME_SHIFT 16 +#define PH_CNT 0x13 +#define PH_CNT_SHIFT 8 +#define PL_CNT 0x7 + +#define WT_PROM_DONE BIT(8) + +#define SFC_POS_LATCH 0x14 +#define FALLING_EDGE_EN 0x0 + +#define SFC_WAIT_WR 0x18 +#define SFC_EN_WR 0x1c +#define WT_PROM_EN BIT(8) + +#define SFC_ADR_FOUR_BYTE_EN 0x28 + +#define MD_FDMA_DDR_SADDR 0x0c +#define MD_FDMA_FL_SADDR 0x10 + +#define MD_FDMA_CTRL2 0x14 +#define MAX_XFER_DMA_LEN (BIT(26) | BIT(27)) +#define MAX_XFER_256 BIT(26) +#define DMA_TO_FLASH BIT(25) + +#define MD_FDMA_CTRL1 0x18 +#define DMA_W_EN_START BIT(3) +#define DMA_END BIT(2) +#define DMA_W_EN BIT(1) +#define DMA_START BIT(0) + +#define MD_FDMA_DDR_SADDR1 0x20 + +#define SFC_DMA_TIMEOUT 20000 +#define SFC_DMA_MAX_LEN 0x100 + +#define SFC_CTL_DMYCNT_MASK GENMASK(31, 24) +#define SFC_CTL_DMYCNT_SHIFT 24 + +#define DMA_HIGH_BITS_MASK 0x7 + +#define RTK_SPI_OP_RDSR 0x05 +#define RTK_SPI_OP_WREN 0x06 +#define RTK_SPI_OP_EN4B 0xb7 +#define RTK_SPI_OP_EX4B 0xe9 + +#define SFC_AUTOSUSPEND_TIMEOUT 2000 + +struct rtk_spi_host { + struct device *dev; + struct clk *clk; + struct reset_control *rstc; + void __iomem *regbase; + void __iomem *iobase; + void __iomem *mdbase; + void *buffer; + bool is_4byte; + dma_addr_t dma_buffer; + resource_size_t flash_phys_base; + size_t dirmap_size; +}; + +static u32 rtk_spi_calc_dummy_cycles(const struct spi_mem_op *op) +{ + if (!op->dummy.nbytes) + return 0; + + return (op->dummy.nbytes * 8) / op->dummy.buswidth; +} + +static void rtk_spi_read_mode(struct rtk_spi_host *host, const struct spi_mem_op *op) +{ + u32 opcode = op->cmd.opcode; + u32 dummy_cycles, val; + + if (op->data.buswidth == 2) + opcode |= DUAL_MODE_EN; + + writel(opcode, host->regbase + SFC_OPCODE); + + val = readl(host->regbase + SFC_CTL); + val |= RW_DATAEN | ADDR_EN; + + dummy_cycles = rtk_spi_calc_dummy_cycles(op); + + val &= ~SFC_CTL_DMYCNT_MASK; + val |= (dummy_cycles << SFC_CTL_DMYCNT_SHIFT) & SFC_CTL_DMYCNT_MASK; + writel(val, host->regbase + SFC_CTL); + + readl(host->iobase); +} + +static void rtk_spi_write_mode(struct rtk_spi_host *host, + const struct spi_mem_op *op) +{ + u32 opcode = op->cmd.opcode; + u32 val; + + writel(opcode, host->regbase + SFC_OPCODE); + + val = readl(host->regbase + SFC_CTL); + val |= RW_DATAEN | ADDR_EN; + writel(val, host->regbase + SFC_CTL); +} + +static void rtk_spi_enable_auto_write(struct rtk_spi_host *host) +{ + u32 val; + + val = WT_PROM_DONE | RTK_SPI_OP_RDSR; + writel(val, host->regbase + SFC_WAIT_WR); + + val = WT_PROM_EN | RTK_SPI_OP_WREN; + writel(val, host->regbase + SFC_EN_WR); +} + +static void rtk_spi_disable_auto_write(struct rtk_spi_host *host) +{ + writel(RTK_SPI_OP_RDSR, host->regbase + SFC_WAIT_WR); + writel(RTK_SPI_OP_WREN, host->regbase + SFC_EN_WR); +} + +static int rtk_spi_byte_transfer(struct rtk_spi_host *host, loff_t offset, + size_t len, unsigned char *buf, bool is_read) +{ + if (offset + len > host->dirmap_size) { + dev_err(host->dev, "PIO transfer out of bounds\n"); + return -EINVAL; + } + + if (is_read) + memcpy_fromio(buf, host->iobase + offset, len); + else + memcpy_toio(host->iobase + offset, buf, len); + + return 0; +} + +static void rtk_spi_init(struct rtk_spi_host *host) +{ + u32 val; + + val = readl(host->regbase + SFC_SCK); + val &= ~FDIV_MASK; + val |= (3 << 0) & FDIV_MASK; + writel(val, host->regbase + SFC_SCK); + + val = (DESLT_TIME << DESLT_TIME_SHIFT) | (PH_CNT << PH_CNT_SHIFT) | PL_CNT; + writel(val, host->regbase + SFC_CE); + + writel(FALLING_EDGE_EN, host->regbase + SFC_POS_LATCH); + writel(RTK_SPI_OP_RDSR, host->regbase + SFC_WAIT_WR); + writel(RTK_SPI_OP_WREN, host->regbase + SFC_EN_WR); + + writel(host->is_4byte ? 0x1 : 0x0, host->regbase + SFC_ADR_FOUR_BYTE_EN); +} + +static int rtk_spi_command_read(struct rtk_spi_host *host, const struct spi_mem_op *op) +{ + size_t len = op->data.nbytes; + loff_t offset = op->addr.val; + u8 opcode = op->cmd.opcode; + u32 dummy_cycles, val; + + writel(opcode, host->regbase + SFC_OPCODE); + + val = readl(host->regbase + SFC_CTL); + val &= ~(SFC_CTL_DMYCNT_MASK | ADDR_EN); + val |= RW_DATAEN; + + if (op->addr.nbytes > 0) + val |= ADDR_EN; + + dummy_cycles = rtk_spi_calc_dummy_cycles(op); + val |= (dummy_cycles << SFC_CTL_DMYCNT_SHIFT) & SFC_CTL_DMYCNT_MASK; + writel(val, host->regbase + SFC_CTL); + + return rtk_spi_byte_transfer(host, offset, len, op->data.buf.in, true); +} + +static int rtk_spi_do_write_and_cmds(struct rtk_spi_host *host, const struct spi_mem_op *op) +{ + u8 opcode = op->cmd.opcode; + u32 ctl_val = 0; + + writel(opcode, host->regbase + SFC_OPCODE); + + if (op->data.nbytes > 0) + ctl_val |= RW_DATAEN; + + if (op->addr.nbytes > 0) + ctl_val |= ADDR_EN; + + writel(ctl_val, host->regbase + SFC_CTL); + + if (op->data.nbytes > 0) { + const u8 *buf = op->data.buf.out; + + if (op->addr.val + op->data.nbytes > host->dirmap_size) + return -EINVAL; + + writeb(*buf, host->iobase + op->addr.val); + } else { + if (op->addr.val >= host->dirmap_size) + return -EINVAL; + + readb(host->iobase + op->addr.val); + } + + return 0; +} + +static int rtk_spi_dma_transfer(struct rtk_spi_host *host, loff_t offset, + size_t len, bool is_read) +{ + u64 dma_buffer, timeout_us = SFC_DMA_TIMEOUT * 100; + u32 flash_phys_addr, val; + int ret; + + if (offset + len > host->dirmap_size) { + dev_err(host->dev, "DMA transfer out of bounds\n"); + return -EINVAL; + } + + writel(DMA_W_EN_START | DMA_W_EN, host->mdbase + MD_FDMA_CTRL1); + + dma_buffer = host->dma_buffer; + + /* Setup MD DDR address and flash address */ + writel(lower_32_bits(dma_buffer), host->mdbase + MD_FDMA_DDR_SADDR); + writel(upper_32_bits(dma_buffer) & DMA_HIGH_BITS_MASK, + host->mdbase + MD_FDMA_DDR_SADDR1); + + /* MD_FDMA_FL_SADDR is a 32-bit hardware register */ + flash_phys_addr = lower_32_bits(host->flash_phys_base + offset); + writel(flash_phys_addr, host->mdbase + MD_FDMA_FL_SADDR); + + if (is_read) + val = MAX_XFER_DMA_LEN | len; + else + val = DMA_TO_FLASH | MAX_XFER_256 | len; + + writel(val, host->mdbase + MD_FDMA_CTRL2); + + writel(DMA_W_EN | DMA_START, host->mdbase + MD_FDMA_CTRL1); + udelay(1); + + ret = readl_poll_timeout(host->mdbase + MD_FDMA_CTRL1, val, + !(val & DMA_START), 100, timeout_us); + if (ret) { + dev_err(host->dev, "DMA transfer timed out\n"); + val = readl(host->mdbase + MD_FDMA_CTRL1); + val |= DMA_END | DMA_W_EN_START; + writel(val, host->mdbase + MD_FDMA_CTRL1); + + if (readl_poll_timeout(host->mdbase + MD_FDMA_CTRL1, val, + !(val & DMA_END), 10, 1000)) { + dev_err(host->dev, "Failed to stop DMA gracefully\n"); + } + + return ret; + } + + return 0; +} + +static int rtk_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + struct rtk_spi_host *host = spi_controller_get_devdata(mem->spi->controller); + u32 opcode = op->cmd.opcode; + + /* + * Hardware Workaround: + * The controller's auto-mode engine requires SFC_ADR_FOUR_BYTE_EN to be + * explicitly updated when the flash enters or exits 4-byte mode via control + * commands. + */ + if ((op->addr.nbytes == 4 || opcode == RTK_SPI_OP_EN4B) && !host->is_4byte) { + host->is_4byte = true; + writel(0x1, host->regbase + SFC_ADR_FOUR_BYTE_EN); + } else if ((op->addr.nbytes == 3 || opcode == RTK_SPI_OP_EX4B) && host->is_4byte) { + host->is_4byte = false; + writel(0x0, host->regbase + SFC_ADR_FOUR_BYTE_EN); + } + + if (op->data.dir == SPI_MEM_DATA_IN) + return rtk_spi_command_read(host, op); + + return rtk_spi_do_write_and_cmds(host, op); +} + +static bool rtk_spi_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + if (op->cmd.buswidth != 1) + return false; + + if (op->cmd.dtr || op->addr.dtr || op->data.dtr) + return false; + + if (op->addr.nbytes != 0) { + if (op->addr.buswidth > 1) + return false; + if (op->addr.nbytes < 3 || op->addr.nbytes > 4) + return false; + } + + /* + * The data window is access-triggered: each CPU access to host->iobase + * generates one complete SPI transaction. Consecutive byte accesses + * therefore cannot be merged into a single transaction, so + * multi-byte stateless commands are not supported by this controller. + */ + if (!op->addr.nbytes && op->data.nbytes > 1) + return -EOPNOTSUPP; + + if (op->dummy.nbytes != 0) { + if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7) + return false; + } + + if (op->data.nbytes != 0 && op->data.buswidth > 2) + return false; + + return spi_mem_default_supports_op(mem, op); +} + +static int rtk_spi_dirmap_create(struct spi_mem_dirmap_desc *desc) +{ + const struct spi_mem_op *op = desc->info.op_tmpl; + + if (op->data.dir == SPI_MEM_DATA_IN && op->addr.nbytes != 3 && op->addr.nbytes != 4) + return -EOPNOTSUPP; + + if (op->data.dir != SPI_MEM_DATA_IN && op->data.dir != SPI_MEM_DATA_OUT) + return -EOPNOTSUPP; + + return 0; +} + +static ssize_t rtk_spi_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf) +{ + struct rtk_spi_host *host = spi_controller_get_devdata(desc->mem->spi->controller); + const struct spi_mem_op *op = desc->info.op_tmpl; + loff_t addr = desc->info.offset + offs; + size_t chunk_len; + int ret; + + /* + * Handle unaligned address bytes at the beginning of the read operation. + * The hardware requires 4-byte alignment for DMA transfers. + */ + if (addr & 0x3) { + rtk_spi_read_mode(host, op); + chunk_len = min_t(size_t, 4 - (addr & 0x3), len); + ret = rtk_spi_byte_transfer(host, addr, chunk_len, buf, true); + if (ret) + return ret; + + return chunk_len; + } + + rtk_spi_read_mode(host, op); + chunk_len = min_t(size_t, len, SFC_DMA_MAX_LEN); + ret = rtk_spi_dma_transfer(host, addr, chunk_len, true); + if (ret) { + dev_err(host->dev, "DMA read transfer failed: %d\n", ret); + return ret; + } + + memcpy(buf, host->buffer, chunk_len); + + return chunk_len; +} + +static ssize_t rtk_spi_dirmap_write(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, const void *buf) +{ + struct rtk_spi_host *host = spi_controller_get_devdata(desc->mem->spi->controller); + const struct spi_mem_op *op = desc->info.op_tmpl; + loff_t addr = desc->info.offset + offs; + size_t chunk_len; + int ret = 0; + + rtk_spi_enable_auto_write(host); + rtk_spi_write_mode(host, op); + + /* + * Handle unaligned address bytes at the beginning of the write operation. + * The hardware requires 4-byte alignment for DMA transfers. + */ + if (addr & 0x3) { + chunk_len = min_t(size_t, 4 - (addr & 0x3), len); + ret = rtk_spi_byte_transfer(host, addr, chunk_len, (u8 *)buf, false); + + goto out; + } + + chunk_len = min_t(size_t, len, SFC_DMA_MAX_LEN); + + memcpy(host->buffer, buf, chunk_len); + + ret = rtk_spi_dma_transfer(host, addr, chunk_len, false); + if (ret) + dev_err(host->dev, "DMA write transfer failed: %d\n", ret); + +out: + rtk_spi_disable_auto_write(host); + + return ret < 0 ? ret : chunk_len; +} + +static const struct spi_controller_mem_ops rtk_spi_mem_ops = { + .supports_op = rtk_spi_supports_op, + .exec_op = rtk_spi_exec_op, + .dirmap_create = rtk_spi_dirmap_create, + .dirmap_read = rtk_spi_dirmap_read, + .dirmap_write = rtk_spi_dirmap_write, +}; + +static int rtk_spi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spi_controller *ctrl; + struct rtk_spi_host *host; + struct resource *res; + int ret; + + ctrl = devm_spi_alloc_host(dev, sizeof(*host)); + if (!ctrl) + return -ENOMEM; + + platform_set_drvdata(pdev, ctrl); + host = spi_controller_get_devdata(ctrl); + host->dev = dev; + + host->clk = devm_clk_get(dev, NULL); + if (IS_ERR(host->clk)) + return PTR_ERR(host->clk); + + host->rstc = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(host->rstc)) + return PTR_ERR(host->rstc); + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(35)); + if (ret) { + dev_err(dev, "Failed to set dma mask\n"); + return ret; + } + + host->buffer = dmam_alloc_coherent(dev, SFC_DMA_MAX_LEN, + &host->dma_buffer, GFP_KERNEL); + if (!host->buffer) + return -ENOMEM; + + host->regbase = devm_platform_ioremap_resource_byname(pdev, "ctrl"); + if (IS_ERR(host->regbase)) + return PTR_ERR(host->regbase); + + host->mdbase = devm_platform_ioremap_resource_byname(pdev, "dma"); + if (IS_ERR(host->mdbase)) + return PTR_ERR(host->mdbase); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dirmap"); + host->iobase = devm_ioremap_resource(dev, res); + if (IS_ERR(host->iobase)) + return PTR_ERR(host->iobase); + + host->dirmap_size = resource_size(res); + host->flash_phys_base = res->start; + host->is_4byte = false; + + ret = clk_prepare_enable(host->clk); + if (ret) + return ret; + + ret = reset_control_assert(host->rstc); + usleep_range(10, 20); + ret = reset_control_deassert(host->rstc); + + rtk_spi_init(host); + + pm_runtime_set_autosuspend_delay(dev, SFC_AUTOSUSPEND_TIMEOUT); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_get_noresume(dev); + + ctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL; + ctrl->bus_num = -1; + ctrl->mem_ops = &rtk_spi_mem_ops; + ctrl->num_chipselect = 1; + ctrl->auto_runtime_pm = true; + + ret = spi_register_controller(ctrl); + if (ret < 0) { + dev_err(dev, "failed to register controller\n"); + goto err_pm_disable; + } + + pm_runtime_put_autosuspend(dev); + + return 0; + +err_pm_disable: + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); + reset_control_assert(host->rstc); + clk_disable_unprepare(host->clk); + + return ret; +} + +static void rtk_spi_remove(struct platform_device *pdev) +{ + struct spi_controller *ctrl = platform_get_drvdata(pdev); + struct rtk_spi_host *host = spi_controller_get_devdata(ctrl); + struct device *dev = &pdev->dev; + + spi_unregister_controller(ctrl); + + if (pm_runtime_resume_and_get(dev) < 0) + dev_warn(dev, "Failed to resume device during remove\n"); + + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); + reset_control_assert(host->rstc); + clk_disable_unprepare(host->clk); +} + +static const struct of_device_id rtk_spi_dt_ids[] = { + { .compatible = "realtek,rtd1625-nor" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, rtk_spi_dt_ids); + +static int rtk_spi_runtime_suspend(struct device *dev) +{ + struct spi_controller *ctlr = dev_get_drvdata(dev); + struct rtk_spi_host *host = spi_controller_get_devdata(ctlr); + + reset_control_assert(host->rstc); + + clk_disable_unprepare(host->clk); + + return 0; +} + +static int rtk_spi_runtime_resume(struct device *dev) +{ + struct spi_controller *ctlr = dev_get_drvdata(dev); + struct rtk_spi_host *host = spi_controller_get_devdata(ctlr); + int ret; + + ret = clk_prepare_enable(host->clk); + if (ret < 0) { + dev_err(dev, "clk_prepare_enable failed: %d\n", ret); + return ret; + } + + reset_control_assert(host->rstc); + usleep_range(10, 20); + reset_control_deassert(host->rstc); + + rtk_spi_init(host); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(rtk_spi_pm_ops, rtk_spi_runtime_suspend, + rtk_spi_runtime_resume, NULL); + +static struct platform_driver rtk_spi_driver = { + .driver = { + .name = "rtk-spi-nor", + .of_match_table = rtk_spi_dt_ids, + .pm = pm_ptr(&rtk_spi_pm_ops), + }, + .probe = rtk_spi_probe, + .remove = rtk_spi_remove, +}; +module_platform_driver(rtk_spi_driver); + +MODULE_DESCRIPTION("Realtek SPI Nor Controller Driver"); +MODULE_AUTHOR("Jyan Chou "); +MODULE_LICENSE("GPL"); From 52efc89ccf664dcfa331fbdf2f5c8d3acd45a863 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Thu, 24 Sep 2026 19:02:56 +0200 Subject: [PATCH 459/724] spi: spi-qpic-snand: set 'qpic_version2' flag The kerneldoc of 'struct qcom_nandc_props' says this about the 'qpic_version2' member of the structure: "flag to indicate QPIC IP version 2" Since the SNAND controller is based on QPIC v2, set the flag in the device specific properties. Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface") Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20260924-qpic-snand-set-qpic_version2-v1-1-a0a9a3e0d06a@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 00072c44db7a..001e4bd8bb4b 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -1677,6 +1677,7 @@ static void qcom_spi_remove(struct platform_device *pdev) static const struct qcom_nandc_props ipq9574_snandc_props = { .dev_cmd_reg_start = 0x7000, .bam_offset = 0x30000, + .qpic_version2 = true, .supports_bam = true, }; From 5c99cb15169d6336c446d079f803e1b13916960f Mon Sep 17 00:00:00 2001 From: Changhuang Liang Date: Wed, 30 Sep 2026 00:15:33 -0700 Subject: [PATCH 460/724] spi: dw: Convert the Tx done delay from SCK cycles to time Currently the delay in dw_spi_dma_wait_tx_done() is expressed in SCK cycles, which requires passing a valid struct spi_transfer pointer. This does not work for enhanced SPI memory operations, which are not described by a spi_transfer. Compute the delay in ns or us based on an explicit bus speed instead, so that the helper can also be used for those transfers. Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260930071535.7947-2-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-dma.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c index f7d848fec9ab..d0b1a03682e6 100644 --- a/drivers/spi/spi-dw-dma.c +++ b/drivers/spi/spi-dw-dma.c @@ -286,19 +286,27 @@ static inline bool dw_spi_dma_tx_busy(struct dw_spi *dws) (DW_SPI_SR_BUSY | DW_SPI_SR_TF_EMPT)) != DW_SPI_SR_TF_EMPT; } -static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, - struct spi_transfer *xfer) +static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, u32 speed_hz) { int retry = DW_SPI_WAIT_RETRIES; struct spi_delay delay; + unsigned long ns, us; u32 nents; nents = dw_readl(dws, DW_SPI_TXFLR); - delay.unit = SPI_DELAY_UNIT_SCK; - delay.value = nents * dws->n_bytes * BITS_PER_BYTE; + ns = DIV_ROUND_UP(NSEC_PER_SEC, speed_hz) * nents * + dws->n_bytes * BITS_PER_BYTE; + if (ns <= NSEC_PER_USEC) { + delay.unit = SPI_DELAY_UNIT_NSECS; + delay.value = ns; + } else { + us = DIV_ROUND_UP(ns, NSEC_PER_USEC); + delay.unit = SPI_DELAY_UNIT_USECS; + delay.value = clamp_val(us, 0, USHRT_MAX); + } while (dw_spi_dma_tx_busy(dws) && retry--) - spi_delay_exec(&delay, xfer); + spi_delay_exec(&delay, NULL); if (retry < 0) { dev_err(&dws->ctlr->dev, "Tx hanged up\n"); @@ -659,7 +667,7 @@ static int dw_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer) return ret; if (dws->ctlr->cur_msg->status == -EINPROGRESS) { - ret = dw_spi_dma_wait_tx_done(dws, xfer); + ret = dw_spi_dma_wait_tx_done(dws, xfer->effective_speed_hz); if (ret) return ret; } From 929b82c85fc10e18804c7743587c5ac84e07310b Mon Sep 17 00:00:00 2001 From: Changhuang Liang Date: Wed, 30 Sep 2026 00:15:34 -0700 Subject: [PATCH 461/724] spi: dw: Support DMA with a single channel Some platforms, such as StarFive JHB100, provide only one DMA channel for the DW SPI controller. dw_spi_dma_init_generic() requires both the Tx and Rx channels, so DMA is disabled entirely on such platforms. Request both channels at probe time and decide what is usable from the return values. If the first (Rx) channel request fails, fall back to non-DMA transfers as before. If only the second (Tx) channel request fails, keep the Rx channel. Each successfully requested channel is counted in dws->dma_nr_chans. -EPROBE_DEFER on the second channel is still propagated. Full-duplex standard SPI transfers need both channels, so only set ctlr->can_dma and SPI_CONTROLLER_MUST_TX when two channels are available. A single channel can still serve half-duplex transfers, which will be used by the enhanced SPI memory operations in a follow-up patch. Make dw_spi_dma_caps_init() and dw_spi_dma_maxburst_init() cope with a missing channel, and set ctlr->dma_tx/dma_rx only after the capability check has passed so that they never point to a released channel. Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260930071535.7947-3-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 3 ++ drivers/spi/spi-dw-dma.c | 88 ++++++++++++++++++++++++++++----------- drivers/spi/spi-dw.h | 1 + 3 files changed, 67 insertions(+), 25 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 206d3f9dd83d..6889f4446ecb 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -1360,6 +1360,9 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws) goto err_free_irq; } else if (ret) { dev_warn(dev, "DMA init failed\n"); + } else if (dws->dma_nr_chans == 1) { + /* Full-duplex DMA transfers need both channels */ + dev_info(dev, "Single DMA channel, full-duplex DMA disabled\n"); } else { ctlr->can_dma = dws->dma_ops->can_dma; ctlr->flags |= SPI_CONTROLLER_MUST_TX; diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c index d0b1a03682e6..76d7814f0dcf 100644 --- a/drivers/spi/spi-dw-dma.c +++ b/drivers/spi/spi-dw-dma.c @@ -42,14 +42,19 @@ static void dw_spi_dma_maxburst_init(struct dw_spi *dws) def_burst = dws->fifo_len / 2; - ret = dma_get_slave_caps(dws->rxchan, &caps); - if (!ret && caps.max_burst) - max_burst = caps.max_burst; - else - max_burst = DW_SPI_RX_BURST_LEVEL; + if (dws->rxchan) { + ret = dma_get_slave_caps(dws->rxchan, &caps); + if (!ret && caps.max_burst) + max_burst = caps.max_burst; + else + max_burst = DW_SPI_RX_BURST_LEVEL; - dws->rxburst = min(max_burst, def_burst); - dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1); + dws->rxburst = min(max_burst, def_burst); + dw_writel(dws, DW_SPI_DMARDLR, dws->rxburst - 1); + } + + if (!dws->txchan) + return; ret = dma_get_slave_caps(dws->txchan, &caps); if (!ret && caps.max_burst) @@ -74,20 +79,39 @@ static void dw_spi_dma_maxburst_init(struct dw_spi *dws) static int dw_spi_dma_caps_init(struct dw_spi *dws) { - struct dma_slave_caps tx, rx; + struct dma_slave_caps tx = {}, rx = {}; int ret; - ret = dma_get_slave_caps(dws->txchan, &tx); - if (ret) - return ret; + if (dws->txchan) { + ret = dma_get_slave_caps(dws->txchan, &tx); + if (ret) + return ret; - ret = dma_get_slave_caps(dws->rxchan, &rx); - if (ret) - return ret; + if (!(tx.directions & BIT(DMA_MEM_TO_DEV))) + return -ENXIO; + } - if (!(tx.directions & BIT(DMA_MEM_TO_DEV) && - rx.directions & BIT(DMA_DEV_TO_MEM))) - return -ENXIO; + if (dws->rxchan) { + ret = dma_get_slave_caps(dws->rxchan, &rx); + if (ret) + return ret; + + if (!(rx.directions & BIT(DMA_DEV_TO_MEM))) + return -ENXIO; + } + + /* With a single channel only one direction is available at a time */ + if (!dws->rxchan) { + dws->dma_sg_burst = tx.max_sg_burst; + dws->dma_addr_widths = tx.dst_addr_widths; + return 0; + } + + if (!dws->txchan) { + dws->dma_sg_burst = rx.max_sg_burst; + dws->dma_addr_widths = rx.src_addr_widths; + return 0; + } if (tx.max_sg_burst > 0 && rx.max_sg_burst > 0) dws->dma_sg_burst = min(tx.max_sg_burst, rx.max_sg_burst); @@ -169,40 +193,54 @@ static int dw_spi_dma_init_generic(struct device *dev, struct dw_spi *dws) { int ret; + dws->dma_nr_chans = 0; + dws->rxchan = dma_request_chan(dev, "rx"); if (IS_ERR(dws->rxchan)) { ret = PTR_ERR(dws->rxchan); dws->rxchan = NULL; goto err_exit; } + dws->dma_nr_chans++; + /* + * Some platforms have only one DMA channel for the controller. Keep + * the Rx channel in that case, it can still serve half-duplex + * transfers. + */ dws->txchan = dma_request_chan(dev, "tx"); if (IS_ERR(dws->txchan)) { ret = PTR_ERR(dws->txchan); dws->txchan = NULL; - goto free_rxchan; + if (ret == -EPROBE_DEFER) + goto free_rxchan; + } else { + dws->dma_nr_chans++; } - dws->ctlr->dma_rx = dws->rxchan; - dws->ctlr->dma_tx = dws->txchan; - - init_completion(&dws->dma_completion); - ret = dw_spi_dma_caps_init(dws); if (ret) goto free_txchan; dw_spi_dma_maxburst_init(dws); + init_completion(&dws->dma_completion); + + dws->ctlr->dma_rx = dws->rxchan; + dws->ctlr->dma_tx = dws->txchan; + return 0; free_txchan: - dma_release_channel(dws->txchan); - dws->txchan = NULL; + if (dws->txchan) { + dma_release_channel(dws->txchan); + dws->txchan = NULL; + } free_rxchan: dma_release_channel(dws->rxchan); dws->rxchan = NULL; err_exit: + dws->dma_nr_chans = 0; return ret; } diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index e62f919f1188..01eb24ae7fe0 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -227,6 +227,7 @@ struct dw_spi { u32 txburst; struct dma_chan *rxchan; u32 rxburst; + unsigned int dma_nr_chans; u32 dma_sg_burst; u32 dma_addr_widths; unsigned long dma_chan_busy; From 8cc7be24bc5e4b4074146486f434dbeada994f6a Mon Sep 17 00:00:00 2001 From: Changhuang Liang Date: Wed, 30 Sep 2026 00:15:35 -0700 Subject: [PATCH 462/724] spi: dw: Add DMA support for enhanced memory operations Implement DMA support for enhanced SPI memory operations. These operations are half-duplex, so they work with either one or two DMA channels. Add enhanced memory DMA callbacks (get_chan, setup, can_dma, transfer) to struct dw_spi_dma_ops and hook them into the generic DMA operations. They reuse the existing channel configuration, submission and wait helpers. With two channels, both are requested at probe time and kept for the lifetime of the controller. With a single channel, as on JHB100, the channel is bound to one direction by its "tx"/"rx" DMA specifier. The channel currently held (dws->txchan or dws->rxchan) records which direction it serves. It is reused as long as the direction stays the same, and only released and requested for the other direction when the transfer direction changes. In dw_spi_exec_enh_mem_op(), record the direction in dws->tx_dir and use the DMA path when a channel is available for that direction, can_dma_enh_mem() accepts the transfer, and the transfer exceeds the FIFO length; otherwise fall back to the interrupt path. Also move the udelay(5) workaround into dw_spi_enh_write_cmd_addr() so it applies to both paths, and enable the generic DMA setup in dw_spi_jhb100_init(). Signed-off-by: Changhuang Liang Link: https://patch.msgid.link/20260930071535.7947-4-changhuang.liang@starfivetech.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-core.c | 108 +++++++++++++--------- drivers/spi/spi-dw-dma.c | 182 ++++++++++++++++++++++++++++++++++++++ drivers/spi/spi-dw-mmio.c | 2 + drivers/spi/spi-dw.h | 7 ++ 4 files changed, 259 insertions(+), 40 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 6889f4446ecb..892b3a8787eb 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -937,6 +937,15 @@ static void dw_spi_enh_write_cmd_addr(struct dw_spi *dws, const struct spi_mem_o dw_spi_set_cs(mem->spi, false); } + + /* + * FIXME: The exact reason for this delay is not fully understood, + * but empirical testing shows it significantly improves the stability + * of read/write operations. Without this delay, occasional transfer + * errors or timeouts may occur under certain conditions. + * Keeping it as a safeguard based on practical validation. + */ + udelay(5); } static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op *op) @@ -948,6 +957,8 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * unsigned long long ms; int ret; + dws->dma_mapped = false; + switch (op->data.buswidth) { case 0: case 1: @@ -971,10 +982,13 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * cfg.dfs = 8; cfg.freq = clamp(op->max_freq, 0U, dws->max_mem_freq); cfg.ndf = op->data.nbytes; - if (op->data.dir == SPI_MEM_DATA_IN) + if (op->data.dir == SPI_MEM_DATA_IN) { cfg.tmode = DW_SPI_CTRLR0_TMOD_RO; - else + dws->tx_dir = false; + } else { cfg.tmode = DW_SPI_CTRLR0_TMOD_TO; + dws->tx_dir = true; + } if (op->data.buswidth == op->addr.buswidth && op->data.buswidth == op->cmd.buswidth) @@ -1010,47 +1024,61 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op * } } - dw_spi_enh_write_cmd_addr(dws, op, mem); - - /* - * FIXME: The exact reason for this delay is not fully understood, - * but empirical testing shows it significantly improves the stability - * of read/write operations. Without this delay, occasional transfer - * errors or timeouts may occur under certain conditions. - * Keeping it as a safeguard based on practical validation. - */ - udelay(5); - - dw_spi_enh_irq_setup(dws); - - /* Use timeout calculation from spi_transfer_wait() */ - ms = 8LL * MSEC_PER_SEC * (dws->rx_len ? dws->rx_len : dws->tx_len); - do_div(ms, dws->current_freq); - - /* - * Increase it twice and add 200 ms tolerance, use - * predefined maximum in case of overflow. - */ - ms += ms + 200; - if (ms > UINT_MAX) - ms = UINT_MAX; - - ms = wait_for_completion_timeout(&ctlr->xfer_completion, - msecs_to_jiffies(ms)); - if (ms == 0) { - dw_spi_mask_intr(dws, 0xff); - synchronize_irq(dws->irq); - dws->rx = NULL; - dws->tx = NULL; - dws->rx_len = 0; - dws->tx_len = 0; - dw_spi_stop_mem_op(dws, mem->spi); - return -EIO; + if (dws->dma_nr_chans && dws->dma_ops->dma_enh_mem_get_chan && + op->data.nbytes > dws->fifo_len) { + ret = dws->dma_ops->dma_enh_mem_get_chan(ctlr->dev.parent, dws); + if (ret) { + /* fall back to the PIO/IRQ path */ + dev_dbg(&ctlr->dev, "DMA enh mem channel unavailable (%d)\n", ret); + } else if (dws->dma_ops->can_dma_enh_mem(ctlr)) { + ret = dws->dma_ops->dma_enh_mem_setup(dws); + if (ret) + dev_err(&ctlr->dev, "DMA enh mem setup failed (%d)\n", ret); + else + dws->dma_mapped = true; + } } - ret = dw_spi_wait_mem_op_done(dws); + if (dws->dma_mapped) { + dw_spi_enh_write_cmd_addr(dws, op, mem); - dw_spi_stop_mem_op(dws, mem->spi); + ret = dws->dma_ops->dma_enh_mem_transfer(mem, op); + + dw_spi_stop_mem_op(dws, mem->spi); + } else { + dw_spi_enh_write_cmd_addr(dws, op, mem); + + dw_spi_enh_irq_setup(dws); + + /* Use timeout calculation from spi_transfer_wait() */ + ms = 8LL * MSEC_PER_SEC * (dws->rx_len ? dws->rx_len : dws->tx_len); + do_div(ms, dws->current_freq); + + /* + * Increase it twice and add 200 ms tolerance, use + * predefined maximum in case of overflow. + */ + ms += ms + 200; + if (ms > UINT_MAX) + ms = UINT_MAX; + + ms = wait_for_completion_timeout(&ctlr->xfer_completion, + msecs_to_jiffies(ms)); + if (ms == 0) { + dw_spi_mask_intr(dws, 0xff); + synchronize_irq(dws->irq); + dws->rx = NULL; + dws->tx = NULL; + dws->rx_len = 0; + dws->tx_len = 0; + dw_spi_stop_mem_op(dws, mem->spi); + return -EIO; + } + + ret = dw_spi_wait_mem_op_done(dws); + + dw_spi_stop_mem_op(dws, mem->spi); + } return ret; } diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c index 76d7814f0dcf..a86039057988 100644 --- a/drivers/spi/spi-dw-dma.c +++ b/drivers/spi/spi-dw-dma.c @@ -728,6 +728,183 @@ static void dw_spi_dma_stop(struct dw_spi *dws) } } +/* + * The enhanced SPI memory operations are half-duplex, so a single DMA channel + * is enough for them. On a platform with only one channel, the channel is + * bound to a direction by its "tx"/"rx" DMA specifier, and whichever channel + * is currently held tells which direction it serves. Keep it across + * operations and swap it only when the transfer direction changes. + */ +static int dw_spi_enh_mem_dma_get_chan(struct device *dev, struct dw_spi *dws) +{ + struct dma_chan **chan, **other; + const char *name; + int ret; + + if (dws->dma_nr_chans == 2) + return 0; + + if (dws->tx_dir) { + chan = &dws->txchan; + other = &dws->rxchan; + name = "tx"; + } else { + chan = &dws->rxchan; + other = &dws->txchan; + name = "rx"; + } + + if (*chan) + return 0; + + dws->ctlr->dma_tx = NULL; + dws->ctlr->dma_rx = NULL; + + /* + * With a single physical channel, the held channel must be released + * before it can be requested again with the handshake of the other + * direction. On failure both stay NULL, and the next operation + * retries the request and falls back to PIO meanwhile. + */ + + if (*other) { + dma_release_channel(*other); + *other = NULL; + } + + *chan = dma_request_chan(dev, name); + if (IS_ERR(*chan)) { + ret = PTR_ERR(*chan); + *chan = NULL; + return ret; + } + + ret = dw_spi_dma_caps_init(dws); + if (ret) { + dma_release_channel(*chan); + *chan = NULL; + return ret; + } + + dw_spi_dma_maxburst_init(dws); + + dws->ctlr->dma_tx = dws->txchan; + dws->ctlr->dma_rx = dws->rxchan; + + return 0; +} + +static int dw_spi_enh_mem_dma_setup(struct dw_spi *dws) +{ + u16 dma_ctrl, level; + int ret; + + /* Setup DMA channels */ + if (dws->tx_dir) { + ret = dw_spi_dma_config_tx(dws); + if (ret) + return ret; + + dma_ctrl = DW_SPI_DMACR_TDMAE; + } else { + ret = dw_spi_dma_config_rx(dws); + if (ret) + return ret; + + dma_ctrl = DW_SPI_DMACR_RDMAE; + } + + dw_writel(dws, DW_SPI_DMACR, dma_ctrl); + + /* Clear stale error status, it is checked after the transfer */ + dw_readl(dws, DW_SPI_ICR); + + reinit_completion(&dws->dma_completion); + + level = min_t(unsigned int, dws->fifo_len / 2, dws->tx_len); + dw_writel(dws, DW_SPI_TXFTLR, level); + + level = min_t(unsigned int, dws->fifo_len / 2, dws->rx_len); + dw_writel(dws, DW_SPI_RXFTLR, level ? level - 1 : 0); + + return 0; +} + +static bool dw_spi_enh_mem_can_dma(struct spi_controller *ctlr) +{ + struct dw_spi *dws = spi_controller_get_devdata(ctlr); + unsigned int len = dws->tx_dir ? dws->tx_len : dws->rx_len; + enum dma_slave_buswidth dma_bus_width; + + if (len <= dws->fifo_len) + return false; + + dma_bus_width = dw_spi_dma_convert_width(dws->n_bytes); + + return dws->dma_addr_widths & BIT(dma_bus_width); +} + +static void dw_spi_enh_mem_dma_stop(struct dw_spi *dws) +{ + if (dws->tx_dir) { + if (test_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy)) { + dmaengine_terminate_sync(dws->txchan); + clear_bit(DW_SPI_TX_BUSY, &dws->dma_chan_busy); + } + } else { + if (test_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy)) { + dmaengine_terminate_sync(dws->rxchan); + clear_bit(DW_SPI_RX_BUSY, &dws->dma_chan_busy); + } + } +} + +static int dw_spi_enh_mem_dma_transfer(struct spi_mem *mem, const struct spi_mem_op *op) +{ + struct spi_controller *ctlr = mem->spi->controller; + struct dw_spi *dws = spi_controller_get_devdata(ctlr); + struct sg_table sgt; + int ret; + + ret = spi_controller_dma_map_mem_op_data(ctlr, op, &sgt); + if (ret) + goto out_clear_dmac; + + if (dws->tx_dir) { + ret = dw_spi_dma_submit_tx(dws, sgt.sgl, sgt.nents); + if (ret) + goto out_unmap; + + dma_async_issue_pending(dws->txchan); + + ret = dw_spi_dma_wait(dws, dws->tx_len, dws->current_freq); + if (!ret) + ret = dw_spi_dma_wait_tx_done(dws, dws->current_freq); + } else { + ret = dw_spi_dma_submit_rx(dws, sgt.sgl, sgt.nents); + if (ret) + goto out_unmap; + + dma_async_issue_pending(dws->rxchan); + + ret = dw_spi_dma_wait(dws, dws->rx_len, dws->current_freq); + if (!ret) + ret = dw_spi_dma_wait_rx_done(dws); + } + + if (ret) + dw_spi_enh_mem_dma_stop(dws); + else + ret = dw_spi_check_status(dws, true); + +out_unmap: + spi_controller_dma_unmap_mem_op_data(ctlr, op, &sgt); +out_clear_dmac: + dw_writel(dws, DW_SPI_DMACR, 0); + + return ret; +} + static const struct dw_spi_dma_ops dw_spi_dma_mfld_ops = { .dma_init = dw_spi_dma_init_mfld, .dma_exit = dw_spi_dma_exit, @@ -750,6 +927,11 @@ static const struct dw_spi_dma_ops dw_spi_dma_generic_ops = { .can_dma = dw_spi_can_dma, .dma_transfer = dw_spi_dma_transfer, .dma_stop = dw_spi_dma_stop, + + .dma_enh_mem_get_chan = dw_spi_enh_mem_dma_get_chan, + .dma_enh_mem_setup = dw_spi_enh_mem_dma_setup, + .can_dma_enh_mem = dw_spi_enh_mem_can_dma, + .dma_enh_mem_transfer = dw_spi_enh_mem_dma_transfer, }; void dw_spi_dma_setup_generic(struct dw_spi *dws) diff --git a/drivers/spi/spi-dw-mmio.c b/drivers/spi/spi-dw-mmio.c index 8cdb0351605b..9c70947c969c 100644 --- a/drivers/spi/spi-dw-mmio.c +++ b/drivers/spi/spi-dw-mmio.c @@ -363,6 +363,8 @@ static int dw_spi_jhb100_init(struct platform_device *pdev, dw_spi_jhb100_mask_intr(&dwsmmio->dws, 0xff); + dw_spi_dma_setup_generic(&dwsmmio->dws); + return 0; } diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index 01eb24ae7fe0..bfdf371bcacb 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -183,6 +183,12 @@ struct dw_spi_dma_ops { struct spi_transfer *xfer); int (*dma_transfer)(struct dw_spi *dws, struct spi_transfer *xfer); void (*dma_stop)(struct dw_spi *dws); + + /* enh mem dma ops */ + int (*dma_enh_mem_get_chan)(struct device *dev, struct dw_spi *dws); + int (*dma_enh_mem_setup)(struct dw_spi *dws); + bool (*can_dma_enh_mem)(struct spi_controller *ctlr); + int (*dma_enh_mem_transfer)(struct spi_mem *mem, const struct spi_mem_op *op); }; struct dw_spi { @@ -228,6 +234,7 @@ struct dw_spi { struct dma_chan *rxchan; u32 rxburst; unsigned int dma_nr_chans; + bool tx_dir; u32 dma_sg_burst; u32 dma_addr_widths; unsigned long dma_chan_busy; From 5367243a37f7f74d5f595ceac780508138f1e3b0 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Thu, 1 Oct 2026 10:40:49 +0800 Subject: [PATCH 463/724] 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 464/724] 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 2d57d6fd1f569d350ab8c7fcae05dd455d9ae535 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 30 Sep 2026 10:02:54 -0400 Subject: [PATCH 465/724] spi: use dmaengine public API instead of raw ops Replace direct calls to the internal DMA device ops device->device_issue_pending() with the proper public DMA engine wrappers: dma_async_issue_pending(). Drivers must not call device ops directly; the wrappers provide locking, debug checks, and a stable interface that hides internal implementation details of the DMA controller driver. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260930140257.490019-1-Frank.Li@oss.nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-at91-usart.c | 4 ++-- drivers/spi/spi-atmel.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-at91-usart.c b/drivers/spi/spi-at91-usart.c index 804f6194c830..cdc771350db9 100644 --- a/drivers/spi/spi-at91-usart.c +++ b/drivers/spi/spi-at91-usart.c @@ -239,8 +239,8 @@ static int at91_usart_spi_dma_transfer(struct spi_controller *ctlr, if (dma_submit_error(cookie)) goto at91_usart_spi_err_dma; - rxchan->device->device_issue_pending(rxchan); - txchan->device->device_issue_pending(txchan); + dma_async_issue_pending(rxchan); + dma_async_issue_pending(txchan); return 0; diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c index c8012c82c3a7..c04113ce17f7 100644 --- a/drivers/spi/spi-atmel.c +++ b/drivers/spi/spi-atmel.c @@ -877,8 +877,8 @@ static int atmel_spi_next_xfer_dma_submit(struct spi_controller *host, cookie = txdesc->tx_submit(txdesc); if (dma_submit_error(cookie)) goto err_dma; - rxchan->device->device_issue_pending(rxchan); - txchan->device->device_issue_pending(txchan); + dma_async_issue_pending(rxchan); + dma_async_issue_pending(txchan); return 0; From 853de617c4a62403242d2032e00cb19a0d870130 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 30 Sep 2026 10:02:55 -0400 Subject: [PATCH 466/724] spi: use dmaengine_get_dma_device() instead of chan->device->dev Replace direct dma_chan::device::dev access with the proper dmaengine_get_dma_device() for consumer API chan->device->dev is not always the device used for DMA mapping. Some DMA engines support per-channel IOMMU mappings, so different channels may use different DMA devices. dmaengine_get_dma_device() returns the correct device for each channel. This also prepares for making the DMA engine provider data structures private. DMA consumers should not access DMA engine internals directly. Assisted-by: LLM Signed-off-by: Frank Li Link: https://patch.msgid.link/20260930140257.490019-2-Frank.Li@oss.nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-bcm2835.c | 25 ++++++++++++---------- drivers/spi/spi-cadence-quadspi.c | 2 +- drivers/spi/spi-ep93xx.c | 8 ++++--- drivers/spi/spi-fsl-dspi.c | 35 ++++++++++++++++++------------- drivers/spi/spi-imx.c | 10 ++++----- drivers/spi/spi-nxp-xspi.c | 16 +++++++------- drivers/spi/spi-pl022.c | 19 ++++++++++------- drivers/spi/spi-sh-msiof.c | 12 +++++------ 8 files changed, 69 insertions(+), 58 deletions(-) diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c index 23606967f7d1..cc5748973281 100644 --- a/drivers/spi/spi-bcm2835.c +++ b/drivers/spi/spi-bcm2835.c @@ -527,7 +527,7 @@ static void bcm2835_spi_transfer_prologue(struct spi_controller *ctlr, | BCM2835_SPI_CS_CLEAR_TX | BCM2835_SPI_CS_DONE); - dma_sync_single_for_device(ctlr->dma_rx->device->dev, + dma_sync_single_for_device(dmaengine_get_dma_device(ctlr->dma_rx), sg_dma_address(&tfr->rx_sg.sgl[0]), bs->rx_prologue, DMA_FROM_DEVICE); @@ -871,7 +871,7 @@ static void bcm2835_dma_release(struct spi_controller *ctlr, dmaengine_desc_free(bs->fill_tx_desc); if (bs->fill_tx_addr) - dma_unmap_page_attrs(ctlr->dma_tx->device->dev, + dma_unmap_page_attrs(dmaengine_get_dma_device(ctlr->dma_tx), bs->fill_tx_addr, sizeof(u32), DMA_TO_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); @@ -891,6 +891,7 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev, struct bcm2835_spi *bs) { struct dma_slave_config slave_config; + struct device *tx_dma_dev; const __be32 *addr; dma_addr_t dma_reg_base; int ret; @@ -932,11 +933,13 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev, if (ret) goto err_config; - bs->fill_tx_addr = dma_map_page_attrs(ctlr->dma_tx->device->dev, + tx_dma_dev = dmaengine_get_dma_device(ctlr->dma_tx); + + bs->fill_tx_addr = dma_map_page_attrs(tx_dma_dev, ZERO_PAGE(0), 0, sizeof(u32), DMA_TO_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); - if (dma_mapping_error(ctlr->dma_tx->device->dev, bs->fill_tx_addr)) { + if (dma_mapping_error(tx_dma_dev, bs->fill_tx_addr)) { dev_err(dev, "cannot map zero page - not using DMA mode\n"); bs->fill_tx_addr = 0; ret = -ENOMEM; @@ -1157,7 +1160,7 @@ static void bcm2835_spi_cleanup(struct spi_device *spi) dmaengine_desc_free(target->clear_rx_desc); if (target->clear_rx_addr) - dma_unmap_single(ctlr->dma_rx->device->dev, + dma_unmap_single(dmaengine_get_dma_device(ctlr->dma_rx), target->clear_rx_addr, sizeof(u32), DMA_TO_DEVICE); @@ -1174,16 +1177,16 @@ static int bcm2835_spi_setup_dma(struct spi_controller *ctlr, struct bcm2835_spi *bs, struct bcm2835_spidev *target) { + struct device *rx_dev; int ret; if (!ctlr->dma_rx) return 0; - target->clear_rx_addr = dma_map_single(ctlr->dma_rx->device->dev, - &target->clear_rx_cs, - sizeof(u32), - DMA_TO_DEVICE); - if (dma_mapping_error(ctlr->dma_rx->device->dev, target->clear_rx_addr)) { + rx_dev = dmaengine_get_dma_device(ctlr->dma_rx); + target->clear_rx_addr = dma_map_single(rx_dev, &target->clear_rx_cs, + sizeof(u32), DMA_TO_DEVICE); + if (dma_mapping_error(rx_dev, target->clear_rx_addr)) { dev_err(&spi->dev, "cannot map clear_rx_cs\n"); target->clear_rx_addr = 0; return -ENOMEM; @@ -1268,7 +1271,7 @@ static int bcm2835_spi_setup(struct spi_device *spi) target->clear_rx_cs = cs | BCM2835_SPI_CS_TA | BCM2835_SPI_CS_DMAEN | BCM2835_SPI_CS_CLEAR_RX; - dma_sync_single_for_device(ctlr->dma_rx->device->dev, + dma_sync_single_for_device(dmaengine_get_dma_device(ctlr->dma_rx), target->clear_rx_addr, sizeof(u32), DMA_TO_DEVICE); diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index ecb0be394696..fe5b094f0b6b 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -1382,7 +1382,7 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata, return 0; } - ddev = cqspi->rx_chan->device->dev; + ddev = dmaengine_get_dma_device(cqspi->rx_chan); dma_dst = dma_map_single(ddev, buf, len, DMA_FROM_DEVICE); if (dma_mapping_error(ddev, dma_dst)) { dev_err(dev, "dma mapping failed\n"); diff --git a/drivers/spi/spi-ep93xx.c b/drivers/spi/spi-ep93xx.c index bf389d7590d3..d8f4aa38685f 100644 --- a/drivers/spi/spi-ep93xx.c +++ b/drivers/spi/spi-ep93xx.c @@ -273,6 +273,7 @@ ep93xx_spi_dma_prepare(struct spi_controller *host, struct dma_async_tx_descriptor *txd; enum dma_slave_buswidth buswidth; struct dma_slave_config conf; + struct device *dma_dev; struct scatterlist *sg; struct sg_table *sgt; struct dma_chan *chan; @@ -348,14 +349,15 @@ ep93xx_spi_dma_prepare(struct spi_controller *host, return ERR_PTR(-EINVAL); } - nents = dma_map_sg(chan->device->dev, sgt->sgl, sgt->nents, dir); + dma_dev = dmaengine_get_dma_device(chan); + nents = dma_map_sg(dma_dev, sgt->sgl, sgt->nents, dir); if (!nents) return ERR_PTR(-ENOMEM); txd = dmaengine_prep_slave_sg(chan, sgt->sgl, nents, conf.direction, DMA_CTRL_ACK); if (!txd) { - dma_unmap_sg(chan->device->dev, sgt->sgl, sgt->nents, dir); + dma_unmap_sg(dma_dev, sgt->sgl, sgt->nents, dir); return ERR_PTR(-ENOMEM); } return txd; @@ -384,7 +386,7 @@ static void ep93xx_spi_dma_finish(struct spi_controller *host, sgt = &espi->tx_sgt; } - dma_unmap_sg(chan->device->dev, sgt->sgl, sgt->nents, dir); + dma_unmap_sg(dmaengine_get_dma_device(chan), sgt->sgl, sgt->nents, dir); } static void ep93xx_spi_dma_callback(void *callback_param) diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c index 23e6bc5f202e..f03488870d02 100644 --- a/drivers/spi/spi-fsl-dspi.c +++ b/drivers/spi/spi-fsl-dspi.c @@ -670,6 +670,7 @@ static void dspi_dma_xfer(struct fsl_dspi *dspi) static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) { struct device *dev = &dspi->pdev->dev; + struct device *tx_dev, *rx_dev; struct dma_slave_config cfg; struct fsl_dspi_dma *dma; int ret; @@ -688,20 +689,23 @@ static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) goto err_tx_channel; } + tx_dev = dmaengine_get_dma_device(dma->chan_tx); + rx_dev = dmaengine_get_dma_device(dma->chan_rx); + if (spi_controller_is_target(dspi->ctlr)) { /* * In target mode we have to be ready to receive the maximum * that can possibly be transferred at once by EDMA without any * FIFO underflows. */ - dma->bufsize = min(dma_get_max_seg_size(dma->chan_rx->device->dev), - dma_get_max_seg_size(dma->chan_tx->device->dev)) * + dma->bufsize = min(dma_get_max_seg_size(rx_dev), + dma_get_max_seg_size(tx_dev)) * DMA_SLAVE_BUSWIDTH_4_BYTES; } else { dma->bufsize = PAGE_SIZE; } - dma->tx_dma_buf = dma_alloc_noncoherent(dma->chan_tx->device->dev, + dma->tx_dma_buf = dma_alloc_noncoherent(tx_dev, dma->bufsize, &dma->tx_dma_phys, DMA_TO_DEVICE, GFP_KERNEL); if (!dma->tx_dma_buf) { @@ -709,7 +713,7 @@ static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) goto err_tx_dma_buf; } - dma->rx_dma_buf = dma_alloc_noncoherent(dma->chan_rx->device->dev, + dma->rx_dma_buf = dma_alloc_noncoherent(rx_dev, dma->bufsize, &dma->rx_dma_phys, DMA_FROM_DEVICE, GFP_KERNEL); if (!dma->rx_dma_buf) { @@ -746,12 +750,11 @@ static int dspi_request_dma(struct fsl_dspi *dspi, phys_addr_t phy_addr) return 0; err_slave_config: - dma_free_noncoherent(dma->chan_rx->device->dev, dma->bufsize, - dma->rx_dma_buf, dma->rx_dma_phys, - DMA_FROM_DEVICE); + dma_free_noncoherent(rx_dev, dma->bufsize, dma->rx_dma_buf, + dma->rx_dma_phys, DMA_FROM_DEVICE); err_rx_dma_buf: - dma_free_noncoherent(dma->chan_tx->device->dev, dma->bufsize, - dma->tx_dma_buf, dma->tx_dma_phys, DMA_TO_DEVICE); + dma_free_noncoherent(tx_dev, dma->bufsize, dma->tx_dma_buf, + dma->tx_dma_phys, DMA_TO_DEVICE); err_tx_dma_buf: dma_release_channel(dma->chan_tx); err_tx_channel: @@ -771,16 +774,18 @@ static void dspi_release_dma(struct fsl_dspi *dspi) return; if (dma->chan_tx) { - dma_free_noncoherent(dma->chan_tx->device->dev, dma->bufsize, - dma->tx_dma_buf, dma->tx_dma_phys, - DMA_TO_DEVICE); + struct device *tx_dev = dmaengine_get_dma_device(dma->chan_tx); + + dma_free_noncoherent(tx_dev, dma->bufsize, dma->tx_dma_buf, + dma->tx_dma_phys, DMA_TO_DEVICE); dma_release_channel(dma->chan_tx); } if (dma->chan_rx) { - dma_free_noncoherent(dma->chan_rx->device->dev, dma->bufsize, - dma->rx_dma_buf, dma->rx_dma_phys, - DMA_FROM_DEVICE); + struct device *rx_dev = dmaengine_get_dma_device(dma->chan_rx); + + dma_free_noncoherent(rx_dev, dma->bufsize, dma->rx_dma_buf, + dma->rx_dma_phys, DMA_FROM_DEVICE); dma_release_channel(dma->chan_rx); } } diff --git a/drivers/spi/spi-imx.c b/drivers/spi/spi-imx.c index 6f9686df0486..868f963e6137 100644 --- a/drivers/spi/spi-imx.c +++ b/drivers/spi/spi-imx.c @@ -1467,8 +1467,8 @@ static int spi_imx_calculate_timeout(struct spi_imx_data *spi_imx, int size) static void spi_imx_dma_unmap(struct spi_imx_data *spi_imx, struct dma_data_package *dma_data) { - struct device *tx_dev = spi_imx->controller->dma_tx->device->dev; - struct device *rx_dev = spi_imx->controller->dma_rx->device->dev; + struct device *tx_dev = dmaengine_get_dma_device(spi_imx->controller->dma_tx); + struct device *rx_dev = dmaengine_get_dma_device(spi_imx->controller->dma_rx); dma_unmap_single(tx_dev, dma_data->dma_tx_addr, DMA_CACHE_ALIGNED_LEN(dma_data->dma_len), @@ -1526,8 +1526,8 @@ static int spi_imx_dma_map(struct spi_imx_data *spi_imx, struct dma_data_package *dma_data) { struct spi_controller *controller = spi_imx->controller; - struct device *tx_dev = controller->dma_tx->device->dev; - struct device *rx_dev = controller->dma_rx->device->dev; + struct device *tx_dev = dmaengine_get_dma_device(controller->dma_tx); + struct device *rx_dev = dmaengine_get_dma_device(controller->dma_rx); int ret; dma_data->dma_tx_addr = dma_map_single(tx_dev, dma_data->dma_tx_buf, @@ -1919,7 +1919,7 @@ static int spi_imx_dma_package_transfer(struct spi_imx_data *spi_imx, return ret; /* Trim the DMA RX buffer and copy the actual data to rx_buf */ - dma_sync_single_for_cpu(controller->dma_rx->device->dev, dma_data->dma_rx_addr, + dma_sync_single_for_cpu(dmaengine_get_dma_device(controller->dma_rx), dma_data->dma_rx_addr, dma_data->dma_len, DMA_FROM_DEVICE); spi_imx_dma_rx_data_handle(spi_imx, dma_data, transfer->rx_buf + spi_imx->rx_offset, word_delay); diff --git a/drivers/spi/spi-nxp-xspi.c b/drivers/spi/spi-nxp-xspi.c index 70cbc3ef658b..38c8d6269aad 100644 --- a/drivers/spi/spi-nxp-xspi.c +++ b/drivers/spi/spi-nxp-xspi.c @@ -841,23 +841,23 @@ static int nxp_xspi_ahb_dma_read(struct nxp_xspi *xspi, { struct dma_async_tx_descriptor *desc; struct dma_chan *chan = xspi->dma_rx; + struct device *dma_dev = dmaengine_get_dma_device(chan); unsigned long timeout; dma_addr_t src_addr, dst_addr; int ret = 0; - dst_addr = dma_map_single(chan->device->dev, - op->data.buf.in, op->data.nbytes, + dst_addr = dma_map_single(dma_dev, op->data.buf.in, op->data.nbytes, DMA_FROM_DEVICE); - if (dma_mapping_error(chan->device->dev, dst_addr)) { + if (dma_mapping_error(dma_dev, dst_addr)) { dev_err(xspi->dev, "failed to map DMA dst buffer\n"); return -ENOMEM; } - src_addr = dma_map_resource(chan->device->dev, + src_addr = dma_map_resource(dma_dev, (phys_addr_t)xspi->memmap_phy + op->addr.val, op->data.nbytes, DMA_TO_DEVICE, 0); - if (dma_mapping_error(chan->device->dev, src_addr)) { + if (dma_mapping_error(dma_dev, src_addr)) { dev_err(xspi->dev, "failed to map DMA src resource\n"); ret = -ENOMEM; goto err_unmap_dst; @@ -886,12 +886,10 @@ static int nxp_xspi_ahb_dma_read(struct nxp_xspi *xspi, } err_unmap_src: - dma_unmap_resource(chan->device->dev, src_addr, - op->data.nbytes, DMA_TO_DEVICE, 0); + dma_unmap_resource(dma_dev, src_addr, op->data.nbytes, DMA_TO_DEVICE, 0); err_unmap_dst: - dma_unmap_single(chan->device->dev, dst_addr, - op->data.nbytes, DMA_FROM_DEVICE); + dma_unmap_single(dma_dev, dst_addr, op->data.nbytes, DMA_FROM_DEVICE); return ret; } diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 4877ab909c51..3ff30486b289 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -684,9 +684,9 @@ static void readwriter(struct pl022 *pl022) static void unmap_free_dma_scatter(struct pl022 *pl022) { /* Unmap and free the SG tables */ - dma_unmap_sg(pl022->dma_tx_channel->device->dev, pl022->sgt_tx.sgl, + dma_unmap_sg(dmaengine_get_dma_device(pl022->dma_tx_channel), pl022->sgt_tx.sgl, pl022->sgt_tx.nents, DMA_TO_DEVICE); - dma_unmap_sg(pl022->dma_rx_channel->device->dev, pl022->sgt_rx.sgl, + dma_unmap_sg(dmaengine_get_dma_device(pl022->dma_rx_channel), pl022->sgt_rx.sgl, pl022->sgt_rx.nents, DMA_FROM_DEVICE); sg_free_table(&pl022->sgt_rx); sg_free_table(&pl022->sgt_tx); @@ -811,6 +811,7 @@ static int configure_dma(struct pl022 *pl022) struct dma_chan *txchan = pl022->dma_tx_channel; struct dma_async_tx_descriptor *rxdesc; struct dma_async_tx_descriptor *txdesc; + struct device *rx_dev, *tx_dev; /* Check that the channels are available */ if (!rxchan || !txchan) @@ -924,14 +925,16 @@ static int configure_dma(struct pl022 *pl022) setup_dma_scatter(pl022, pl022->tx, pl022->cur_transfer->len, &pl022->sgt_tx); + rx_dev = dmaengine_get_dma_device(rxchan); /* Map DMA buffers */ - rx_sglen = dma_map_sg(rxchan->device->dev, pl022->sgt_rx.sgl, - pl022->sgt_rx.nents, DMA_FROM_DEVICE); + rx_sglen = dma_map_sg(rx_dev, pl022->sgt_rx.sgl, + pl022->sgt_rx.nents, DMA_FROM_DEVICE); if (!rx_sglen) goto err_rx_sgmap; - tx_sglen = dma_map_sg(txchan->device->dev, pl022->sgt_tx.sgl, - pl022->sgt_tx.nents, DMA_TO_DEVICE); + tx_dev = dmaengine_get_dma_device(txchan); + tx_sglen = dma_map_sg(tx_dev, pl022->sgt_tx.sgl, + pl022->sgt_tx.nents, DMA_TO_DEVICE); if (!tx_sglen) goto err_tx_sgmap; @@ -969,10 +972,10 @@ static int configure_dma(struct pl022 *pl022) dmaengine_terminate_all(txchan); err_rxdesc: dmaengine_terminate_all(rxchan); - dma_unmap_sg(txchan->device->dev, pl022->sgt_tx.sgl, + dma_unmap_sg(tx_dev, pl022->sgt_tx.sgl, pl022->sgt_tx.nents, DMA_TO_DEVICE); err_tx_sgmap: - dma_unmap_sg(rxchan->device->dev, pl022->sgt_rx.sgl, + dma_unmap_sg(rx_dev, pl022->sgt_rx.sgl, pl022->sgt_rx.nents, DMA_FROM_DEVICE); err_rx_sgmap: sg_free_table(&pl022->sgt_tx); diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c index 90a671e9de70..89fbdde46cfe 100644 --- a/drivers/spi/spi-sh-msiof.c +++ b/drivers/spi/spi-sh-msiof.c @@ -674,7 +674,7 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx, if (tx) { ier_bits |= SIIER_TDREQE | SIIER_TDMAE; - dma_sync_single_for_device(p->ctlr->dma_tx->device->dev, + dma_sync_single_for_device(dmaengine_get_dma_device(p->ctlr->dma_tx), p->tx_dma_addr, len, DMA_TO_DEVICE); desc_tx = dmaengine_prep_slave_single(p->ctlr->dma_tx, p->tx_dma_addr, len, DMA_MEM_TO_DEV, @@ -754,7 +754,7 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx, } if (rx) - dma_sync_single_for_cpu(p->ctlr->dma_rx->device->dev, + dma_sync_single_for_cpu(dmaengine_get_dma_device(p->ctlr->dma_rx), p->rx_dma_addr, len, DMA_FROM_DEVICE); return 0; @@ -1142,13 +1142,13 @@ static int sh_msiof_request_dma(struct sh_msiof_spi_priv *p) if (!p->rx_dma_page) goto free_tx_page; - tx_dev = ctlr->dma_tx->device->dev; + tx_dev = dmaengine_get_dma_device(ctlr->dma_tx); p->tx_dma_addr = dma_map_single(tx_dev, p->tx_dma_page, PAGE_SIZE, DMA_TO_DEVICE); if (dma_mapping_error(tx_dev, p->tx_dma_addr)) goto free_rx_page; - rx_dev = ctlr->dma_rx->device->dev; + rx_dev = dmaengine_get_dma_device(ctlr->dma_rx); p->rx_dma_addr = dma_map_single(rx_dev, p->rx_dma_page, PAGE_SIZE, DMA_FROM_DEVICE); if (dma_mapping_error(rx_dev, p->rx_dma_addr)) @@ -1178,9 +1178,9 @@ static void sh_msiof_release_dma(struct sh_msiof_spi_priv *p) if (!ctlr->dma_tx) return; - dma_unmap_single(ctlr->dma_rx->device->dev, p->rx_dma_addr, PAGE_SIZE, + dma_unmap_single(dmaengine_get_dma_device(ctlr->dma_rx), p->rx_dma_addr, PAGE_SIZE, DMA_FROM_DEVICE); - dma_unmap_single(ctlr->dma_tx->device->dev, p->tx_dma_addr, PAGE_SIZE, + dma_unmap_single(dmaengine_get_dma_device(ctlr->dma_tx), p->tx_dma_addr, PAGE_SIZE, DMA_TO_DEVICE); free_page((unsigned long)p->rx_dma_page); free_page((unsigned long)p->tx_dma_page); From 53cdc4266b935724bb2f89e9229c6f92476a1575 Mon Sep 17 00:00:00 2001 From: Han Gao Date: Thu, 1 Oct 2026 02:22:50 +0800 Subject: [PATCH 467/724] spi: sg2044-nor: Return transfer errors The transfer helpers return errors, but both dispatchers discard them. Return these errors to the SPI memory layer instead of reporting success. Fixes: de16c322eefb ("spi: sophgo: add SG2044 SPI NOR controller driver") Signed-off-by: Han Gao Link: https://patch.msgid.link/20261001-fix-sg204xspi-v2-1-014dbaf22b5e@iscas.ac.cn Signed-off-by: Mark Brown --- drivers/spi/spi-sg2044-nor.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-sg2044-nor.c b/drivers/spi/spi-sg2044-nor.c index f3bcb8a1b92b..ff026ca0f766 100644 --- a/drivers/spi/spi-sg2044-nor.c +++ b/drivers/spi/spi-sg2044-nor.c @@ -307,15 +307,15 @@ static ssize_t sg2044_spifmc_tran_cmd(struct sg2044_spifmc *spifmc, return 0; } -static void sg2044_spifmc_trans(struct sg2044_spifmc *spifmc, - const struct spi_mem_op *op) +static int sg2044_spifmc_trans(struct sg2044_spifmc *spifmc, + const struct spi_mem_op *op) { if (op->data.dir == SPI_MEM_DATA_IN) - sg2044_spifmc_read(spifmc, op); + return sg2044_spifmc_read(spifmc, op); else if (op->data.dir == SPI_MEM_DATA_OUT) - sg2044_spifmc_write(spifmc, op); + return sg2044_spifmc_write(spifmc, op); else - sg2044_spifmc_tran_cmd(spifmc, op); + return sg2044_spifmc_tran_cmd(spifmc, op); } static ssize_t sg2044_spifmc_trans_reg(struct sg2044_spifmc *spifmc, @@ -389,19 +389,20 @@ static int sg2044_spifmc_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { struct sg2044_spifmc *spifmc; + int ret; spifmc = spi_controller_get_devdata(mem->spi->controller); mutex_lock(&spifmc->lock); if (op->addr.nbytes == 0) - sg2044_spifmc_trans_reg(spifmc, op); + ret = sg2044_spifmc_trans_reg(spifmc, op); else - sg2044_spifmc_trans(spifmc, op); + ret = sg2044_spifmc_trans(spifmc, op); mutex_unlock(&spifmc->lock); - return 0; + return ret; } static const struct spi_controller_mem_ops sg2044_spifmc_mem_ops = { From ed400c241b3cd80fcec6974b87eefdad1b1dd146 Mon Sep 17 00:00:00 2001 From: Han Gao Date: Thu, 1 Oct 2026 02:22:51 +0800 Subject: [PATCH 468/724] spi: sg2044-nor: Honor SPI clock limits The fixed divider ignores device and operation clock limits. Calculate the divider before each transfer so SCK does not exceed either limit. Advertise the controller maximum speed so the SPI core supplies a default when the device omits its maximum speed. Use the fastest divider when no speed limit is supplied. Fixes: de16c322eefb ("spi: sophgo: add SG2044 SPI NOR controller driver") Signed-off-by: Han Gao Link: https://patch.msgid.link/20261001-fix-sg204xspi-v2-2-014dbaf22b5e@iscas.ac.cn Signed-off-by: Mark Brown --- drivers/spi/spi-sg2044-nor.c | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/drivers/spi/spi-sg2044-nor.c b/drivers/spi/spi-sg2044-nor.c index ff026ca0f766..bc9a8ee9bce4 100644 --- a/drivers/spi/spi-sg2044-nor.c +++ b/drivers/spi/spi-sg2044-nor.c @@ -385,6 +385,29 @@ static ssize_t sg2044_spifmc_trans_reg(struct sg2044_spifmc *spifmc, return 0; } +static int sg2044_spifmc_set_speed(struct sg2044_spifmc *spifmc, + unsigned int speed_hz) +{ + unsigned long rate = clk_get_rate(spifmc->clk); + u64 div; + u32 reg; + + if (!rate) + return -EINVAL; + + /* SCK = HCLK / (2 * (SckDiv + 1)); round down the resulting SCK. */ + div = speed_hz ? DIV_ROUND_UP_ULL(rate, 2ULL * speed_hz) : 1; + if (div > SPIFMC_CTRL_SCK_DIV_MASK + 1) + return -EINVAL; + + reg = readl(spifmc->io_base + SPIFMC_CTRL); + reg &= ~SPIFMC_CTRL_SCK_DIV_MASK; + reg |= div - 1; + writel(reg, spifmc->io_base + SPIFMC_CTRL); + + return 0; +} + static int sg2044_spifmc_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { @@ -395,11 +418,18 @@ static int sg2044_spifmc_exec_op(struct spi_mem *mem, mutex_lock(&spifmc->lock); + ret = sg2044_spifmc_set_speed(spifmc, op->max_freq ? + min(op->max_freq, mem->spi->max_speed_hz) : + mem->spi->max_speed_hz); + if (ret) + goto out_unlock; + if (op->addr.nbytes == 0) ret = sg2044_spifmc_trans_reg(spifmc, op); else ret = sg2044_spifmc_trans(spifmc, op); +out_unlock: mutex_unlock(&spifmc->lock); return ret; @@ -409,6 +439,10 @@ static const struct spi_controller_mem_ops sg2044_spifmc_mem_ops = { .exec_op = sg2044_spifmc_exec_op, }; +static const struct spi_controller_mem_caps sg2044_spifmc_mem_caps = { + .per_op_freq = true, +}; + static void sg2044_spifmc_init(struct sg2044_spifmc *spifmc) { u32 tran_csr; @@ -448,6 +482,10 @@ static int sg2044_spifmc_probe(struct platform_device *pdev) if (IS_ERR(spifmc->clk)) return dev_err_probe(dev, PTR_ERR(spifmc->clk), "Cannot get and enable AHB clock\n"); + ctrl->max_speed_hz = clk_get_rate(spifmc->clk) / 2; + if (!ctrl->max_speed_hz) + return dev_err_probe(dev, -EINVAL, "Invalid AHB clock rate\n"); + spifmc->dev = &pdev->dev; spifmc->ctrl = ctrl; @@ -459,6 +497,7 @@ static int sg2044_spifmc_probe(struct platform_device *pdev) ctrl->bits_per_word_mask = SPI_BPW_MASK(8); ctrl->auto_runtime_pm = false; ctrl->mem_ops = &sg2044_spifmc_mem_ops; + ctrl->mem_caps = &sg2044_spifmc_mem_caps; ctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL | SPI_RX_QUAD | SPI_TX_QUAD; ret = devm_mutex_init(dev, &spifmc->lock); From d532e2f5c288556075dc429941861391118787a0 Mon Sep 17 00:00:00 2001 From: Yuri Zaporozhets Date: Thu, 1 Oct 2026 21:25:52 +0200 Subject: [PATCH 469/724] 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 470/724] 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 d220c9443db6e5f20a60cad55c6a0ae7a75c63b8 Mon Sep 17 00:00:00 2001 From: Jesse Jeronimus Date: Fri, 2 Oct 2026 13:06:46 +0200 Subject: [PATCH 471/724] Documentation: devres: Remove non-existent SPI register helpers Commit 9b894d65e978 ("Documentation: devres: add missing SPI helpers") added devm_spi_register_host() and devm_spi_register_target() to the list of managed interfaces. These functions do not exist and never existed in the tree. The only managed SPI register helper is devm_spi_register_controller(), which is already in the list. Remove the two entries. Fixes: 9b894d65e978 ("Documentation: devres: add missing SPI helpers") Signed-off-by: Jesse Jeronimus Link: https://patch.msgid.link/20261002110752.89529-1-jesse.jeronimus@gmail.com Signed-off-by: Mark Brown --- Documentation/driver-api/driver-model/devres.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/driver-api/driver-model/devres.rst b/Documentation/driver-api/driver-model/devres.rst index 5067500ded5c..4f0b90e7e138 100644 --- a/Documentation/driver-api/driver-model/devres.rst +++ b/Documentation/driver-api/driver-model/devres.rst @@ -460,8 +460,6 @@ SPI devm_spi_alloc_target() devm_spi_optimize_message() devm_spi_register_controller() - devm_spi_register_host() - devm_spi_register_target() WATCHDOG devm_watchdog_register_device() From 7d6adf283a83c9e4fce05a97599f8e6a006bd737 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 2 Oct 2026 12:39:52 +0100 Subject: [PATCH 472/724] 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 664824782df63013ba8686b649c625cac4793248 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Sat, 26 Sep 2026 14:25:58 +0200 Subject: [PATCH 473/724] 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 474/724] 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 475/724] 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 476/724] 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 477/724] 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 478/724] 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 479/724] 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 480/724] 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 481/724] 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 482/724] 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 483/724] 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 58e7bca5cf264e910ad9199cfc40246e0a2834f4 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 3 Oct 2026 21:07:38 +0200 Subject: [PATCH 484/724] 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 485/724] 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 1b207c85551fd74854d632116849295b2305245f Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 4 Oct 2026 00:34:51 -0400 Subject: [PATCH 486/724] 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 487/724] 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 488/724] 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 9867312e9fd2a29f69c9b425e10a7be555b773d3 Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Tue, 6 Oct 2026 09:53:15 +0530 Subject: [PATCH 489/724] 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 490/724] 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 491/724] 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 492/724] 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 493/724] 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 494/724] 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 495/724] 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 5b81127873aebf55cf55b6bd6462b1056f9b97e1 Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Sat, 3 Oct 2026 13:56:53 -0400 Subject: [PATCH 496/724] 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 497/724] 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 498/724] 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 499/724] 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 500/724] 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 501/724] 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 502/724] 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 503/724] 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 504/724] 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 505/724] 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 506/724] 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 507/724] 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 508/724] 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 509/724] 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 510/724] 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 511/724] 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 512/724] 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 513/724] 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 514/724] 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 515/724] 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 516/724] 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 517/724] 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 518/724] 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 519/724] 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 520/724] 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 521/724] 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 522/724] 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 523/724] 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 524/724] 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 525/724] 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 526/724] 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 527/724] 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 528/724] 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 529/724] 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 530/724] 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 531/724] 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 532/724] 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 533/724] 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 534/724] 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 535/724] 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 536/724] 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 537/724] 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 45c2b3c936c7ab75756d28f9d0876912b84b5a3d Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 7 Oct 2026 14:30:36 -0500 Subject: [PATCH 538/724] 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 539/724] 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 540/724] 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 541/724] 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 542/724] 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 543/724] 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 544/724] 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 545/724] 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 546/724] 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 0688699b3612ebd44118a033ab3101f087d7a832 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 9 Oct 2026 08:29:42 +0300 Subject: [PATCH 547/724] 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 50fb7f7fc7e4341bd5779905972fa94539ea6a49 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 9 Oct 2026 18:22:03 +0700 Subject: [PATCH 548/724] 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 549/724] 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 550/724] 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 551/724] 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 552/724] 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 553/724] 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 554/724] 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 555/724] 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 556/724] 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 557/724] 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 ad6be54d13496a0b5c6d1edeea99504e438cdb7a Mon Sep 17 00:00:00 2001 From: Mingxuan Guo Date: Tue, 13 Oct 2026 18:23:58 +0800 Subject: [PATCH 558/724] 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 559/724] 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 560/724] 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 561/724] 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 562/724] 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 563/724] 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 564/724] 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 565/724] 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 566/724] 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 567/724] 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 568/724] 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 569/724] 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 570/724] 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 571/724] 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 572/724] 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 573/724] 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 574/724] 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 575/724] 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 576/724] 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 577/724] 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 578/724] 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 579/724] 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 580/724] 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 581/724] 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 582/724] 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 583/724] 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 584/724] 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 585/724] 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 586/724] 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 587/724] 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 588/724] 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 589/724] 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 590/724] 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 591/724] 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 592/724] 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 593/724] 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 594/724] 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 595/724] 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 596/724] 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 597/724] 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 598/724] 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 599/724] 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 600/724] 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 601/724] 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 602/724] 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 603/724] 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 604/724] 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 605/724] 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 606/724] 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 607/724] 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 608/724] 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 609/724] 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 610/724] 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 611/724] 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 612/724] 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 613/724] 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 614/724] 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 615/724] 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 684c52dd270286dde0685e4bbfbd1b731c050de9 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Wed, 14 Oct 2026 17:14:45 +0530 Subject: [PATCH 616/724] 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 617/724] 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 5b4d158b309a151979fce2334fec616d71506b77 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Wed, 14 Oct 2026 15:01:47 +0300 Subject: [PATCH 618/724] 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 619/724] 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 620/724] 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 621/724] 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 622/724] 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 623/724] 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 624/724] 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 625/724] 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 626/724] 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 627/724] 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 628/724] 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 629/724] 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 630/724] 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 631/724] 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 632/724] 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 633/724] 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 f0ecc305442f20a6df2575943e9897825d80cc5d Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Wed, 14 Oct 2026 22:51:10 -0400 Subject: [PATCH 634/724] 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 635/724] 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 636/724] 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 637/724] 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 638/724] 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 639/724] 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 640/724] 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 641/724] 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 642/724] 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 643/724] 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 644/724] 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 645/724] 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 646/724] 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 647/724] 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 648/724] 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 85bc213f96ac03d74a3e531968c68861e5598281 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Thu, 15 Oct 2026 21:02:38 +0200 Subject: [PATCH 649/724] 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 650/724] 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 651/724] 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 652/724] 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 653/724] 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 654/724] 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 f15b2df0b940cb2c01797f78791de3279d514086 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 9 Oct 2026 02:14:59 +0800 Subject: [PATCH 655/724] 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 656/724] 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 657/724] 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 658/724] 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 659/724] 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 660/724] 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 661/724] 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 662/724] 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 663/724] 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 664/724] 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 665/724] 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 666/724] 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 667/724] 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 668/724] 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 669/724] 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 670/724] 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 671/724] 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 672/724] 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 673/724] 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 674/724] 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 675/724] 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 676/724] 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 677/724] 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 678/724] 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 679/724] 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 680/724] 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 681/724] 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 682/724] 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 683/724] 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 684/724] 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 685/724] 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 686/724] 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 687/724] 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 688/724] 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 689/724] 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 690/724] 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 691/724] 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 692/724] 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 38cb2ef26b6411277826da54c6bcf94523541058 Mon Sep 17 00:00:00 2001 From: Art O Cathain Date: Fri, 16 Oct 2026 11:29:30 +0200 Subject: [PATCH 693/724] 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 694/724] 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 695/724] 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 1ba2cb30eeab2b9b9d576f87a80a750f04b4bfa7 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 16 Oct 2026 11:09:36 +0700 Subject: [PATCH 696/724] 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 697/724] 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 698/724] 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 699/724] 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 700/724] 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 701/724] 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 702/724] 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 703/724] 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 704/724] 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 705/724] 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 706/724] 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 707/724] 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 708/724] 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 709/724] 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 710/724] 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 711/724] 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 712/724] 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 713/724] 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 714/724] 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 715/724] 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 716/724] 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 717/724] 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 718/724] 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 719/724] 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 720/724] 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 721/724] 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 e06bf60ee5bffa9eeacff0bf8b03d4572b04f67d Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sun, 18 Oct 2026 15:47:44 +0900 Subject: [PATCH 722/724] 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 723/724] 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 724/724] 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)